Completion Part 4

master
random-geek 2021-03-26 00:08:54 -07:00
parent b18b0e96cc
commit 661889b852
9 changed files with 128 additions and 93 deletions

View File

@ -174,27 +174,26 @@ mapblocks can be copied verbatim.
Usage: `replaceininv [--delete] [--deletemeta] [--nodes <nodes>] [--p1 x y z] [--p2 x y z] [--invert] <item> [new_item]` Usage: `replaceininv [--delete] [--deletemeta] [--nodes <nodes>] [--p1 x y z] [--p2 x y z] [--invert] <item> [new_item]`
Replace or delete certain items in node inventories. Replace, delete, or modify items in certain node inventories.
Arguments: Arguments:
- `item`: Name of item to replace/delete - `<item>`: Name of the item to replace/delete
- `new_item`: Name of new item, if replacing items. - `[new_item]`: Name of the new item, if replacing items.
- `--delete`: Delete items instead of replacing them. - `--delete`: Delete items instead of replacing them.
- `--deletemeta`: Delete metadata of items. May be used with or without - `--deletemeta`: Delete metadata of affected items. May be used with or
`new_item`, depending on whether items should also be replaced. without `new_item`, depending on whether items should also be replaced.
- `--nodes`: Names of one or more nodes to replace in. If not specified, the - `--nodes <nodes>`: Names of one or more nodes to modify inventories of. If
item will be replaced in all node inventories. not specified, items will be modified in any node with an inventory.
- `--p1, --p2`: Area in which to modify node inventories. If not specified, - `--p1, --p2`: Area in which to modify node inventories. If not specified,
items will be replaced in all node inventories. items will be modified everywhere.
- `--invert`: Only modify node inventories *outside* the given area. - `--invert`: Modify node inventories *outside* the given area.
Examples: Examples:
Replace all written books in chests with unwritten books, deleting metadata: Replace all written books in chests with unwritten books, deleting metadata:
`replaceininv default:book_written default:book --deletemeta --nodes `replaceininv default:book_written default:book --deletemeta --nodes default:chest default:chest_locked`
default:chest default:chest_locked`
### replacenodes ### replacenodes
@ -207,41 +206,45 @@ This command does not affect param2, metadata, etc.
Arguments: Arguments:
- `node`: Name of node to replace. - `<node>`: Name of node to replace.
- `new_node`: Name of node to replace with. - `<new_node>`: Name of node to replace with.
- `--p1, --p2`: Area in which to replace nodes. If not specified, nodes - `--p1, --p2`: Area in which to replace nodes. If not specified, nodes will be
will be replaced across the entire map. replaced across the entire map.
- `--invert`: Only replace nodes *outside* the given area. - `--invert`: Replace nodes *outside* the given area.
### setmetavar ### setmetavar
Usage: `setmetavar [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <key> <value>` Usage: `setmetavar [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <key> <value>`
Set a variable in node metadata. This only works on metadata where the variable Set or delete a variable in node metadata of certain nodes. This only works on metadata
is already set. where the variable is already set.
Arguments: Arguments:
- `key`: Name of variable to set, e.g. `infotext`, `formspec`, etc. - `<key>`: Name of variable to set/delete, e.g. `infotext`, `formspec`, etc.
- `value`: Value to set variable to. This should be a string. - `<value>`: Value to set variable to, if setting a value. This should be a
- `--node`: Name of node to modify. If not specified, the variable will be string.
set for all nodes that have it. - `--delete`: Delete the variable.
- `--p1, --p2`: Area in which to modify nodes. - `--nodes <nodes>`: Names of one or more nodes to modify. If not specified,
- `--invert`: Only modify nodes *outside* the given area. any node with the given variable will be modified.
- `--p1, --p2`: Area in which to modify node metadata.
- `--invert`: Modify node metadata *outside* the given area.
### setparam2 ### setparam2
Usage: `setparam2 [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <param2_val>` Usage: `setparam2 [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <param2>`
Set param2 values of a certain node and/or within a certain area. Set param2 values of certain nodes.
Arguments: Arguments:
- `param2_val`: Param2 value to set, between 0 and 255. - `<param2>`: New param2 value, between 0 and 255.
- `--node`: Name of node to modify. If not specified, the param2 values of - `--node <node>`: Name of node to modify. If not specified, the param2 values
all nodes will be set. of any node will be set.
- `--p1, --p2`: Area in which to set param2. - `--p1, --p2`: Area in which to set param2 values.
- `--invert`: Only set param2 *outside* the given area. - `--invert`: Set param2 values *outside* the given area.
An area and/or node is required for setparam2.
### vacuum ### vacuum

View File

@ -48,7 +48,7 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
.help(help_msg) .help(help_msg)
]; ];
} }
// TODO: Ensure arguments are correctly defined.
let arg = match arg_type { let arg = match arg_type {
ArgType::Area(_) => unreachable!(), ArgType::Area(_) => unreachable!(),
ArgType::InputMapPath => ArgType::InputMapPath =>
@ -65,10 +65,9 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
.value_names(&["x", "y", "z"]) .value_names(&["x", "y", "z"])
.required(req), .required(req),
ArgType::Node(req) => { ArgType::Node(req) => {
let a = Arg::with_name("node") let a = Arg::with_name("node");
.required(req);
if req { if req {
a a.required(true)
} else { } else {
a.long("node").takes_value(true) a.long("node").takes_value(true)
} }
@ -96,22 +95,21 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
ArgType::NewItem => ArgType::NewItem =>
Arg::with_name("new_item") Arg::with_name("new_item")
.takes_value(true), .takes_value(true),
ArgType::Delete =>
Arg::with_name("delete")
.long("delete"),
ArgType::DeleteMeta => ArgType::DeleteMeta =>
Arg::with_name("delete_meta") Arg::with_name("delete_meta")
.long("deletemeta"), .long("deletemeta"),
ArgType::DeleteItem =>
Arg::with_name("delete_item")
.long("delete"),
ArgType::Key => ArgType::Key =>
Arg::with_name("key") Arg::with_name("key")
.takes_value(true) .takes_value(true)
.required(true), .required(true),
ArgType::Value => ArgType::Value =>
Arg::with_name("value") Arg::with_name("value")
.takes_value(true) .takes_value(true),
.required(true), ArgType::Param2 =>
ArgType::Param2Val => Arg::with_name("param2")
Arg::with_name("param2_val")
.required(true), .required(true),
}.help(help_msg); }.help(help_msg);
@ -129,6 +127,7 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
SubCommand::with_name(cmd_name) SubCommand::with_name(cmd_name)
.about(cmd.help) .about(cmd.help)
.args(&args) .args(&args)
.after_help("For additional information, see the manual.")
}); });
let app = App::new("MapEditr") let app = App::new("MapEditr")
@ -144,10 +143,9 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
.global(true) .global(true)
.help("Skip the default confirmation prompt.") .help("Skip the default confirmation prompt.")
) )
// TODO: Move map arg to subcommands?
.arg(Arg::with_name("map") .arg(Arg::with_name("map")
.required(true) .required(true)
.help("Path to world directory or map database to edit.") .help("Path to world directory or map database to edit")
) )
.setting(AppSettings::SubcommandRequired) .setting(AppSettings::SubcommandRequired)
.subcommands(app_commands); .subcommands(app_commands);
@ -164,9 +162,9 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
input_map_path: sub_matches.value_of("input_map").map(str::to_string), input_map_path: sub_matches.value_of("input_map").map(str::to_string),
area: { area: {
let p1_maybe = sub_matches.values_of("p1").map(arg_to_pos) let p1_maybe = sub_matches.values_of("p1").map(arg_to_pos)
.transpose().context("Invalid p1 value")?; .transpose().context("Invalid p1 value.")?;
let p2_maybe = sub_matches.values_of("p2").map(arg_to_pos) let p2_maybe = sub_matches.values_of("p2").map(arg_to_pos)
.transpose().context("Invalid p2 value")?; .transpose().context("Invalid p2 value.")?;
if let (Some(p1), Some(p2)) = (p1_maybe, p2_maybe) { if let (Some(p1), Some(p2)) = (p1_maybe, p2_maybe) {
Some(Area::from_unsorted(p1, p2)) Some(Area::from_unsorted(p1, p2))
} else { } else {
@ -175,7 +173,7 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
}, },
invert: sub_matches.is_present("invert"), invert: sub_matches.is_present("invert"),
offset: sub_matches.values_of("offset").map(arg_to_pos).transpose() offset: sub_matches.values_of("offset").map(arg_to_pos).transpose()
.context("Invalid offset value")?, .context("Invalid offset value.")?,
node: sub_matches.value_of("node").map(str::to_string), node: sub_matches.value_of("node").map(str::to_string),
nodes: sub_matches.values_of("nodes").iter_mut().flatten() nodes: sub_matches.values_of("nodes").iter_mut().flatten()
.map(str::to_string).collect(), .map(str::to_string).collect(),
@ -185,11 +183,11 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
items: sub_matches.values_of("items") items: sub_matches.values_of("items")
.map(|v| v.map(str::to_string).collect()), .map(|v| v.map(str::to_string).collect()),
new_item: sub_matches.value_of("new_item").map(str::to_string), new_item: sub_matches.value_of("new_item").map(str::to_string),
delete: sub_matches.is_present("delete"),
delete_meta: sub_matches.is_present("delete_meta"), delete_meta: sub_matches.is_present("delete_meta"),
delete_item: sub_matches.is_present("delete_item"),
key: sub_matches.value_of("key").map(str::to_string), key: sub_matches.value_of("key").map(str::to_string),
value: sub_matches.value_of("value").map(str::to_string), value: sub_matches.value_of("value").map(str::to_string),
param2_val: sub_matches.value_of("param2_val").map(|val| val.parse()) param2: sub_matches.value_of("param2_val").map(|val| val.parse())
.transpose().context("Invalid param2 value.")?, .transpose().context("Invalid param2 value.")?,
}) })
} }

View File

@ -6,15 +6,15 @@ use crate::instance::{ArgType, InstArgs, InstBundle};
use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt}; use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt};
use crate::utils::{query_keys, to_bytes, fmt_big_num}; use crate::utils::{query_keys, to_bytes, fmt_big_num};
const NEWLINE: u8 = b'\n';
const SPACE: u8 = b' ';
fn do_replace(inv: &mut Vec<u8>, item: &[u8], new_item: &[u8], del_meta: bool) fn do_replace(inv: &mut Vec<u8>, item: &[u8], new_item: &[u8], del_meta: bool)
-> u64 -> u64
{ {
const NEWLINE: u8 = b'\n';
const SPACE: u8 = b' ';
let delete = new_item.is_empty(); let delete = new_item.is_empty();
let mut new_inv = Vec::with_capacity(inv.len()); let mut new_inv = Vec::new();
let mut mods = 0; let mut mods = 0;
for line in inv.split(|&x| x == NEWLINE) { for line in inv.split(|&x| x == NEWLINE) {
@ -66,7 +66,7 @@ fn do_replace(inv: &mut Vec<u8>, item: &[u8], new_item: &[u8], del_meta: bool)
fn replace_in_inv(inst: &mut InstBundle) { fn replace_in_inv(inst: &mut InstBundle) {
let item = to_bytes(inst.args.item.as_ref().unwrap()); let item = to_bytes(inst.args.item.as_ref().unwrap());
let new_item = inst.args.new_item.as_ref().map(to_bytes) let new_item = inst.args.new_item.as_ref().map(to_bytes)
.unwrap_or(if inst.args.delete_item { vec![] } else { item.clone() }); .unwrap_or(if inst.args.delete { vec![] } else { item.clone() });
let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect();
let keys = query_keys(&mut inst.db, &mut inst.status, let keys = query_keys(&mut inst.db, &mut inst.status,
@ -132,12 +132,14 @@ fn replace_in_inv(inst: &mut InstBundle) {
fn verify_args(args: &InstArgs) -> ArgResult { fn verify_args(args: &InstArgs) -> ArgResult {
if args.new_item.is_none() && !args.delete_item && !args.delete_meta { if args.new_item.is_none() && !args.delete && !args.delete_meta {
return ArgResult::error( return ArgResult::error(
"new_item is required unless --delete or --deletemeta is used."); "new_item is required unless --delete or --deletemeta is used.");
} else if args.new_item.is_some() && args.delete_item { } else if args.new_item.is_some() && args.delete {
return ArgResult::error( return ArgResult::error(
"Cannot delete items if new_item is specified."); "Cannot delete items if new_item is specified.");
} else if args.item == args.new_item && !args.delete_meta {
return ArgResult::error("item and new_item cannot be the same.");
} }
ArgResult::Ok ArgResult::Ok
} }
@ -150,13 +152,15 @@ pub fn get_command() -> Command {
args: vec![ args: vec![
(ArgType::Item, "Name of the item to replace/delete"), (ArgType::Item, "Name of the item to replace/delete"),
(ArgType::NewItem, "Name of the new item, if replacing items."), (ArgType::NewItem, "Name of the new item, if replacing items."),
(ArgType::Delete, "Delete items instead of replacing them."),
(ArgType::DeleteMeta, "Delete metadata of affected items."), (ArgType::DeleteMeta, "Delete metadata of affected items."),
(ArgType::DeleteItem, "Delete items instead of replacing them."), (ArgType::Nodes,
(ArgType::Area(false), "Area in which to modify inventories"), "Names of one or more nodes to modify inventories of"),
(ArgType::Invert, "Modify inventories outside the given area."), (ArgType::Area(false), "Area in which to modify node inventories"),
(ArgType::Nodes, "Names of nodes to modify inventories of"), (ArgType::Invert,
"Modify node inventories *outside* the given area."),
], ],
help: "Replace or delete items in node inventories." help: "Replace, delete, or modify items in certain node inventories."
} }
} }

View File

@ -19,7 +19,7 @@ fn do_replace(
let block_pos = Vec3::from_block_key(key); let block_pos = Vec3::from_block_key(key);
let mut replaced = 0; let mut replaced = 0;
// Replace nodes in a portion of a mapblock. // Replace nodes in a portion of the mapblock.
if area if area
.filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos)) .filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos))
.is_some() .is_some()
@ -144,7 +144,7 @@ pub fn get_command() -> Command {
(ArgType::Node(true), "Name of node to replace"), (ArgType::Node(true), "Name of node to replace"),
(ArgType::NewNode, "Name of node to replace with"), (ArgType::NewNode, "Name of node to replace with"),
(ArgType::Area(false), "Area in which to replace nodes"), (ArgType::Area(false), "Area in which to replace nodes"),
(ArgType::Invert, "Replace nodes outside the given area") (ArgType::Invert, "Replace nodes *outside* the given area.")
], ],
help: "Replace all of one node with another node." help: "Replace all of one node with another node."
} }

View File

@ -1,16 +1,30 @@
use super::Command; use super::{Command, ArgResult};
use crate::unwrap_or; use crate::unwrap_or;
use crate::spatial::Vec3; use crate::spatial::Vec3;
use crate::instance::{ArgType, InstBundle}; use crate::instance::{ArgType, InstArgs, InstBundle};
use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt}; use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt};
use crate::utils::{query_keys, to_bytes, fmt_big_num}; use crate::utils::{query_keys, to_bytes, fmt_big_num};
fn verify_args(args: &InstArgs) -> ArgResult {
if args.value.is_none() && !args.delete {
return ArgResult::error(
"value is required unless deleting the variable.");
} else if args.value.is_some() && args.delete {
return ArgResult::error(
"value cannot be used when deleting the variable.");
} else if args.value == Some(String::new()) {
return ArgResult::error("Metadata value cannot be empty.");
}
ArgResult::Ok
}
fn set_meta_var(inst: &mut InstBundle) { fn set_meta_var(inst: &mut InstBundle) {
// TODO: Bytes input, create/delete variables // TODO: Bytes input
let key = to_bytes(inst.args.key.as_ref().unwrap()); let key = to_bytes(inst.args.key.as_ref().unwrap());
let value = to_bytes(inst.args.value.as_ref().unwrap()); let value = to_bytes(inst.args.value.as_ref().unwrap_or(&String::new()));
let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect();
let keys = query_keys(&mut inst.db, &mut inst.status, let keys = query_keys(&mut inst.db, &mut inst.status,
@ -41,10 +55,9 @@ fn set_meta_var(inst: &mut InstBundle) {
for (&idx, data) in &mut meta { for (&idx, data) in &mut meta {
let pos = Vec3::from_u16_key(idx); let pos = Vec3::from_u16_key(idx);
let abs_pos = pos + block_corner;
if let Some(a) = inst.args.area { if let Some(a) = inst.args.area {
if a.contains(abs_pos) == inst.args.invert { if a.contains(pos + block_corner) == inst.args.invert {
continue; continue;
} }
} }
@ -54,8 +67,13 @@ fn set_meta_var(inst: &mut InstBundle) {
continue; continue;
} }
if let Some(val) = data.vars.get_mut(&key) { if data.vars.contains_key(&key) {
val.0 = value.clone(); if inst.args.delete {
// Note: serialize() will cull any newly empty metadata.
data.vars.remove(&key);
} else {
data.vars.get_mut(&key).unwrap().0 = value.clone();
}
modified = true; modified = true;
count += 1; count += 1;
} }
@ -76,17 +94,18 @@ fn set_meta_var(inst: &mut InstBundle) {
pub fn get_command() -> Command { pub fn get_command() -> Command {
Command { Command {
func: set_meta_var, func: set_meta_var,
verify_args: None, verify_args: Some(verify_args),
args: vec![ args: vec![
(ArgType::Key, "Name of key to set in metadata"), (ArgType::Key, "Name of variable to set/delete"),
(ArgType::Value, "Value to set in metadata"), (ArgType::Value, "Value to set variable to, if setting a value"),
(ArgType::Area(false), (ArgType::Delete, "Delete the variable."),
"Optional area in which to modify node metadata"),
(ArgType::Invert, "Modify node metadata outside the given area."),
(ArgType::Nodes, (ArgType::Nodes,
"Names of one or more nodes to modify. If not specified, all \ "Names of one or more nodes to modify. If not specified, any \
nodes with the specified variable will be modified.") node with the given variable will be modified."),
(ArgType::Area(false),
"Area in which to modify node metadata"),
(ArgType::Invert, "Modify node metadata *outside* the given area."),
], ],
help: "Set a variable in node metadata." help: "Set or delete a variable in node metadata of certain nodes."
} }
} }

View File

@ -51,7 +51,7 @@ fn set_param2_partial(block: &mut MapBlock, area: Area, invert: bool,
fn set_param2(inst: &mut InstBundle) { fn set_param2(inst: &mut InstBundle) {
let param2_val = inst.args.param2_val.unwrap(); let param2_val = inst.args.param2.unwrap();
let node = inst.args.node.as_ref().map(to_bytes); let node = inst.args.node.as_ref().map(to_bytes);
let keys = query_keys(&mut inst.db, &mut inst.status, let keys = query_keys(&mut inst.db, &mut inst.status,
@ -103,13 +103,14 @@ fn set_param2(inst: &mut InstBundle) {
inst.status.end_editing(); inst.status.end_editing();
tk.print(&mut inst.status); tk.print(&mut inst.status);
inst.status.log_info(format!("{} nodes set.", fmt_big_num(count))); inst.status.log_info(format!("Set param2 of {} nodes.",
fmt_big_num(count)));
} }
fn verify_args(args: &InstArgs) -> ArgResult { fn verify_args(args: &InstArgs) -> ArgResult {
if args.area.is_none() && args.node.is_none() { if args.area.is_none() && args.node.is_none() {
return ArgResult::error("An area and/or node must be provided."); return ArgResult::error("An area and/or node is required.");
} }
ArgResult::Ok ArgResult::Ok
@ -121,11 +122,11 @@ pub fn get_command() -> Command {
func: set_param2, func: set_param2,
verify_args: Some(verify_args), verify_args: Some(verify_args),
args: vec![ args: vec![
(ArgType::Area(false), "Area in which to set param2 values"), (ArgType::Param2, "New param2 value, between 0 and 255"),
(ArgType::Invert, "Set param2 values outside the given area."),
(ArgType::Node(false), "Node to set param2 values of"), (ArgType::Node(false), "Node to set param2 values of"),
(ArgType::Param2Val, "New param2 value") (ArgType::Area(false), "Area in which to set param2 values"),
(ArgType::Invert, "Set param2 values *outside* the given area."),
], ],
help: "Set param2 value of certain nodes." help: "Set param2 values of certain nodes."
} }
} }

View File

@ -25,6 +25,6 @@ pub fn get_command() -> Command {
func: vacuum, func: vacuum,
verify_args: None, verify_args: None,
args: Vec::new(), args: Vec::new(),
help: "Rebuild map database to reduce its size." help: "Rebuild the map database to reduce its size."
} }
} }

View File

@ -23,11 +23,11 @@ pub enum ArgType {
Item, Item,
Items, Items,
NewItem, NewItem,
Delete,
DeleteMeta, DeleteMeta,
DeleteItem,
Key, Key,
Value, Value,
Param2Val, Param2,
} }
@ -47,11 +47,11 @@ pub struct InstArgs {
pub item: Option<String>, pub item: Option<String>,
pub items: Option<Vec<String>>, pub items: Option<Vec<String>>,
pub new_item: Option<String>, pub new_item: Option<String>,
pub delete: bool,
pub delete_meta: bool, pub delete_meta: bool,
pub delete_item: bool,
pub key: Option<String>, pub key: Option<String>,
pub value: Option<String>, pub value: Option<String>,
pub param2_val: Option<u8>, pub param2: Option<u8>,
} }

View File

@ -6,6 +6,9 @@ use std::cmp::min;
use memmem::{Searcher, TwoWaySearcher}; use memmem::{Searcher, TwoWaySearcher};
const END_STR: &[u8; 13] = b"EndInventory\n";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NodeMetadata { pub struct NodeMetadata {
pub vars: HashMap<Vec<u8>, (Vec<u8>, bool)>, pub vars: HashMap<Vec<u8>, (Vec<u8>, bool)>,
@ -29,7 +32,6 @@ impl NodeMetadata {
vars.insert(name.clone(), (val, private)); vars.insert(name.clone(), (val, private));
} }
const END_STR: &[u8; 13] = b"EndInventory\n";
let end_finder = TwoWaySearcher::new(END_STR); let end_finder = TwoWaySearcher::new(END_STR);
let end = end_finder let end = end_finder
.search_in(&data.get_ref()[data.position() as usize ..]) .search_in(&data.get_ref()[data.position() as usize ..])
@ -54,6 +56,11 @@ impl NodeMetadata {
data.write_all(&self.inv).unwrap(); data.write_all(&self.inv).unwrap();
} }
/// Return `true` if the metadata contains no variables or inventory lists.
fn is_empty(&self) -> bool {
self.vars.is_empty() && self.inv.starts_with(END_STR)
}
} }
@ -102,6 +109,9 @@ impl NodeMetadataListExt for NodeMetadataList {
data.write_u16::<BigEndian>(self.len() as u16).unwrap(); data.write_u16::<BigEndian>(self.len() as u16).unwrap();
for (&pos, meta) in self { for (&pos, meta) in self {
if meta.is_empty() {
continue; // Skip empty metadata.
}
data.write_u16::<BigEndian>(pos).unwrap(); data.write_u16::<BigEndian>(pos).unwrap();
meta.serialize(&mut data, version); meta.serialize(&mut data, version);
} }