Initial commit: Terrafier v0.1.0
- Core world model (World, Dimension, Tile, Terrain, Platform) - NBT reader/writer with full tag support - Anvil region (.mca) format reader with chunk parsing - Block palette compression and export - Biome database with Minecraft 1.21 biomes - CLI commands: new, import, export, info, render - GUI application with egui (viewport, tools, undo/redo) - Noise-based heightmap generation and editing operations License: GPL-3.0-or-later
This commit is contained in:
commit
91835402b3
94 changed files with 12372 additions and 0 deletions
11
crates/nbt/Cargo.toml
Executable file
11
crates/nbt/Cargo.toml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "terrafier-nbt"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Zero-copy NBT (Named Binary Tag) parser and writer for Minecraft data"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, optional = true }
|
||||
thiserror.workspace = true
|
||||
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
|
||||
4
crates/nbt/src/io/mod.rs
Executable file
4
crates/nbt/src/io/mod.rs
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
//! NBT I/O — binary reader and writer for Java Edition (Big Endian).
|
||||
|
||||
pub mod reader;
|
||||
pub mod writer;
|
||||
230
crates/nbt/src/io/reader.rs
Executable file
230
crates/nbt/src/io/reader.rs
Executable file
|
|
@ -0,0 +1,230 @@
|
|||
//! NBT binary reader for Java Edition (Big Endian).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Cursor, Read};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::tag::Tag;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ReadError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Unknown tag type: {0}")]
|
||||
UnknownTagType(u8),
|
||||
#[error("Invalid string length: {0}")]
|
||||
InvalidStringLength(usize),
|
||||
#[error("Invalid array length: {0}")]
|
||||
InvalidArrayLength(usize),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ReadError>;
|
||||
|
||||
pub fn read_bytes(data: &[u8]) -> Result<Tag> {
|
||||
let cursor = Cursor::new(data);
|
||||
let mut de = NbtReader::new(cursor);
|
||||
de.read_tag_compound_root()
|
||||
}
|
||||
|
||||
pub fn read_gzip(data: &[u8]) -> Result<Tag> {
|
||||
let mut dec = flate2::read::GzDecoder::new(data);
|
||||
let mut buf = Vec::new();
|
||||
dec.read_to_end(&mut buf)?;
|
||||
read_bytes(&buf)
|
||||
}
|
||||
|
||||
struct NbtReader<R: Read> {
|
||||
inner: R,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<R: Read> NbtReader<R> {
|
||||
fn new(inner: R) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
buf: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_exact(&mut self, len: usize) -> Result<&[u8]> {
|
||||
self.buf.clear();
|
||||
self.buf.resize(len, 0);
|
||||
self.inner.read_exact(&mut self.buf)?;
|
||||
Ok(&self.buf)
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> Result<u8> {
|
||||
let mut byte = [0u8; 1];
|
||||
self.inner.read_exact(&mut byte)?;
|
||||
Ok(byte[0])
|
||||
}
|
||||
|
||||
fn read_i16_be(&mut self) -> Result<i16> {
|
||||
let b = self.read_exact(2)?;
|
||||
Ok(i16::from_be_bytes([b[0], b[1]]))
|
||||
}
|
||||
|
||||
fn read_i32_be(&mut self) -> Result<i32> {
|
||||
let b = self.read_exact(4)?;
|
||||
Ok(i32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn read_i64_be(&mut self) -> Result<i64> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(i64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_f32_be(&mut self) -> Result<f32> {
|
||||
let b = self.read_exact(4)?;
|
||||
Ok(f32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn read_f64_be(&mut self) -> Result<f64> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(f64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String> {
|
||||
let len = self.read_i16_be()? as u16 as usize;
|
||||
let bytes = self.read_exact(len)?.to_vec();
|
||||
String::from_utf8(bytes).map_err(|_| ReadError::InvalidStringLength(len))
|
||||
}
|
||||
|
||||
fn read_tag_payload(&mut self, tag_type: u8) -> Result<Tag> {
|
||||
match tag_type {
|
||||
0 => Ok(Tag::End),
|
||||
1 => Ok(Tag::Byte(self.read_u8()? as i8)),
|
||||
2 => Ok(Tag::Short(self.read_i16_be()?)),
|
||||
3 => Ok(Tag::Int(self.read_i32_be()?)),
|
||||
4 => Ok(Tag::Long(self.read_i64_be()?)),
|
||||
5 => Ok(Tag::Float(self.read_f32_be()?)),
|
||||
6 => Ok(Tag::Double(self.read_f64_be()?)),
|
||||
7 => Ok(Tag::String(self.read_string()?)),
|
||||
8 => {
|
||||
let elem_type = self.read_u8()?;
|
||||
let len = self.read_i32_be()? as usize;
|
||||
let mut items = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
items.push(self.read_tag_payload(elem_type)?);
|
||||
}
|
||||
Ok(Tag::List(items))
|
||||
}
|
||||
9 => {
|
||||
let mut map = HashMap::new();
|
||||
loop {
|
||||
let t = self.read_u8()?;
|
||||
if t == 0 {
|
||||
break;
|
||||
}
|
||||
let name = self.read_string()?;
|
||||
let val = self.read_tag_payload(t)?;
|
||||
map.insert(name, val);
|
||||
}
|
||||
Ok(Tag::Compound(map))
|
||||
}
|
||||
10 => {
|
||||
let len = self.read_i32_be()? as usize;
|
||||
let bytes = self.read_exact(len)?.to_vec();
|
||||
Ok(Tag::ByteArray(bytes.into_iter().map(|b| b as i8).collect()))
|
||||
}
|
||||
11 => {
|
||||
let len = self.read_i32_be()? as usize;
|
||||
let mut vals = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vals.push(self.read_i32_be()?);
|
||||
}
|
||||
Ok(Tag::IntArray(vals))
|
||||
}
|
||||
12 => {
|
||||
let len = self.read_i32_be()? as usize;
|
||||
let mut vals = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vals.push(self.read_i64_be()?);
|
||||
}
|
||||
Ok(Tag::LongArray(vals))
|
||||
}
|
||||
_ => Err(ReadError::UnknownTagType(tag_type)),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_tag_compound_root(&mut self) -> Result<Tag> {
|
||||
let t = self.read_u8()?;
|
||||
if t == 0 {
|
||||
return Ok(Tag::Compound(HashMap::new()));
|
||||
}
|
||||
if t != 9 {
|
||||
return Err(ReadError::UnknownTagType(t));
|
||||
}
|
||||
let _name = self.read_string()?;
|
||||
self.read_tag_payload(9)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn root_envelope(data: &[u8]) -> Vec<u8> {
|
||||
let mut buf = vec![0x09, 0x00, 0x00];
|
||||
buf.extend(data);
|
||||
buf.push(0x00);
|
||||
buf
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_byte() {
|
||||
let payload = vec![0x01, 0x00, 0x04, b't', b'e', b's', b't', 0x2a, 0x00];
|
||||
let tag = read_bytes(&root_envelope(&payload)).unwrap();
|
||||
let expected = Tag::Compound(HashMap::from([("test".into(), Tag::Byte(42))]));
|
||||
assert_eq!(tag, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_string() {
|
||||
let payload = vec![
|
||||
0x07, 0x00, 0x04, b'n', b'a', b'm', b'e', 0x00, 0x05, b'H', b'e', b'l', b'l', b'o',
|
||||
];
|
||||
let tag = read_bytes(&root_envelope(&payload)).unwrap();
|
||||
let expected = Tag::Compound(HashMap::from([(
|
||||
"name".into(),
|
||||
Tag::String("Hello".into()),
|
||||
)]));
|
||||
assert_eq!(tag, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_compound_nested() {
|
||||
let inner = vec![0x01, 0x00, 0x03, b'k', b'e', b'y', 0x07, 0x00];
|
||||
let mut payload = vec![0x09, 0x00, 0x05, b'c', b'h', b'i', b'l', b'd'];
|
||||
payload.extend(inner);
|
||||
payload.push(0x00);
|
||||
let tag = read_bytes(&root_envelope(&payload)).unwrap();
|
||||
let inner_map = HashMap::from([("key".into(), Tag::Byte(7))]);
|
||||
let expected = Tag::Compound(HashMap::from([("child".into(), Tag::Compound(inner_map))]));
|
||||
assert_eq!(tag, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_int_array() {
|
||||
let mut payload = vec![0x0b, 0x00, 0x03, b'a', b'r', b'r', 0x00, 0x00, 0x00, 0x02];
|
||||
payload.extend(&[0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02]);
|
||||
payload.push(0x00);
|
||||
let tag = read_bytes(&root_envelope(&payload)).unwrap();
|
||||
let expected = Tag::Compound(HashMap::from([("arr".into(), Tag::IntArray(vec![1, 2]))]));
|
||||
assert_eq!(tag, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_long_array() {
|
||||
let mut payload = vec![0x0c, 0x00, 0x03, b'l', b'n', b'g', 0x00, 0x00, 0x00, 0x01];
|
||||
payload.extend(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a]);
|
||||
payload.push(0x00);
|
||||
let tag = read_bytes(&root_envelope(&payload)).unwrap();
|
||||
let expected = Tag::Compound(HashMap::from([("lng".into(), Tag::LongArray(vec![42]))]));
|
||||
assert_eq!(tag, expected);
|
||||
}
|
||||
}
|
||||
227
crates/nbt/src/io/writer.rs
Executable file
227
crates/nbt/src/io/writer.rs
Executable file
|
|
@ -0,0 +1,227 @@
|
|||
//! NBT binary writer for Java Edition (Big Endian).
|
||||
|
||||
use std::io::Write;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::tag::Tag;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WriteError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Unsupported tag type in list: {0}")]
|
||||
UnsupportedListType(u8),
|
||||
#[error("Empty list cannot determine element type")]
|
||||
EmptyList,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, WriteError>;
|
||||
|
||||
/// Serialize a Tag tree to bytes (Big Endian, no compression).
|
||||
pub fn to_bytes(tag: &Tag) -> Result<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
let mut w = NbtWriter::new(&mut buf);
|
||||
w.write_tag_compound_root(tag)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Serialize a Tag tree to gzip-compressed bytes.
|
||||
pub fn to_gzip_bytes(tag: &Tag) -> Result<Vec<u8>> {
|
||||
let raw = to_bytes(tag)?;
|
||||
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
encoder.write_all(&raw)?;
|
||||
Ok(encoder.finish()?)
|
||||
}
|
||||
|
||||
struct NbtWriter<W: Write> {
|
||||
inner: W,
|
||||
}
|
||||
|
||||
impl<W: Write> NbtWriter<W> {
|
||||
fn new(inner: W) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, val: u8) -> Result<()> {
|
||||
self.inner.write_all(&[val])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_i16_be(&mut self, val: i16) -> Result<()> {
|
||||
self.inner.write_all(&val.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_i32_be(&mut self, val: i32) -> Result<()> {
|
||||
self.inner.write_all(&val.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_i64_be(&mut self, val: i64) -> Result<()> {
|
||||
self.inner.write_all(&val.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_f32_be(&mut self, val: f32) -> Result<()> {
|
||||
self.inner.write_all(&val.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_f64_be(&mut self, val: f64) -> Result<()> {
|
||||
self.inner.write_all(&val.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_string(&mut self, s: &str) -> Result<()> {
|
||||
let bytes = s.as_bytes();
|
||||
if bytes.len() > u16::MAX as usize {
|
||||
return Err(WriteError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"String too long for NBT",
|
||||
)));
|
||||
}
|
||||
self.write_i16_be(bytes.len() as i16)?;
|
||||
self.inner.write_all(bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_tag(&mut self, tag: &Tag, name: Option<&str>) -> Result<()> {
|
||||
self.write_u8(tag.id())?;
|
||||
if let Some(n) = name {
|
||||
self.write_string(n)?;
|
||||
}
|
||||
self.write_tag_payload(tag)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_tag_payload(&mut self, tag: &Tag) -> Result<()> {
|
||||
match tag {
|
||||
Tag::End => {}
|
||||
Tag::Byte(v) => self.write_u8(*v as u8)?,
|
||||
Tag::Short(v) => self.write_i16_be(*v)?,
|
||||
Tag::Int(v) => self.write_i32_be(*v)?,
|
||||
Tag::Long(v) => self.write_i64_be(*v)?,
|
||||
Tag::Float(v) => self.write_f32_be(*v)?,
|
||||
Tag::Double(v) => self.write_f64_be(*v)?,
|
||||
Tag::String(v) => self.write_string(v)?,
|
||||
Tag::List(items) => {
|
||||
if items.is_empty() {
|
||||
self.write_u8(1)?; // TAG_Byte as fallback
|
||||
self.write_i32_be(0)?;
|
||||
} else {
|
||||
let elem_type = items[0].id();
|
||||
self.write_u8(elem_type)?;
|
||||
self.write_i32_be(items.len() as i32)?;
|
||||
for item in items {
|
||||
self.write_tag_payload(item)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Tag::Compound(map) => {
|
||||
let mut keys: Vec<&String> = map.keys().collect();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
if let Some(val) = map.get(key) {
|
||||
self.write_tag(val, Some(key))?;
|
||||
}
|
||||
}
|
||||
self.write_u8(0)?; // TAG_End
|
||||
}
|
||||
Tag::ByteArray(v) => {
|
||||
self.write_i32_be(v.len() as i32)?;
|
||||
for b in v {
|
||||
self.inner.write_all(&[*b as u8])?;
|
||||
}
|
||||
}
|
||||
Tag::IntArray(v) => {
|
||||
self.write_i32_be(v.len() as i32)?;
|
||||
for n in v {
|
||||
self.write_i32_be(*n)?;
|
||||
}
|
||||
}
|
||||
Tag::LongArray(v) => {
|
||||
self.write_i32_be(v.len() as i32)?;
|
||||
for n in v {
|
||||
self.write_i64_be(*n)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_tag_compound_root(&mut self, tag: &Tag) -> Result<()> {
|
||||
match tag {
|
||||
Tag::Compound(map) => {
|
||||
self.write_u8(9)?; // TAG_Compound
|
||||
self.write_string("")?; // empty root name
|
||||
let mut keys: Vec<&String> = map.keys().collect();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
if let Some(val) = map.get(key) {
|
||||
self.write_tag(val, Some(key))?;
|
||||
}
|
||||
}
|
||||
self.write_u8(0)?; // TAG_End
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(WriteError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"Root tag must be Compound",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::io::reader;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_byte() {
|
||||
let tag = Tag::Compound(HashMap::from([("val".into(), Tag::Byte(42))]));
|
||||
let bytes = to_bytes(&tag).unwrap();
|
||||
let parsed = reader::read_bytes(&bytes).unwrap();
|
||||
assert_eq!(tag, parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_nested() {
|
||||
let inner = Tag::Compound(HashMap::from([
|
||||
("x".into(), Tag::Int(100)),
|
||||
("y".into(), Tag::Int(200)),
|
||||
]));
|
||||
let tag = Tag::Compound(HashMap::from([("pos".into(), inner)]));
|
||||
let bytes = to_bytes(&tag).unwrap();
|
||||
let parsed = reader::read_bytes(&bytes).unwrap();
|
||||
assert_eq!(tag, parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_all_types() {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("byte".into(), Tag::Byte(1));
|
||||
map.insert("short".into(), Tag::Short(2));
|
||||
map.insert("int".into(), Tag::Int(3));
|
||||
map.insert("long".into(), Tag::Long(4));
|
||||
map.insert("float".into(), Tag::Float(5.0));
|
||||
map.insert("double".into(), Tag::Double(6.0));
|
||||
map.insert("string".into(), Tag::String("hello".into()));
|
||||
map.insert("bytearray".into(), Tag::ByteArray(vec![1, 2, 3]));
|
||||
map.insert("intarray".into(), Tag::IntArray(vec![4, 5, 6]));
|
||||
map.insert("longarray".into(), Tag::LongArray(vec![7, 8, 9]));
|
||||
let tag = Tag::Compound(map);
|
||||
let bytes = to_bytes(&tag).unwrap();
|
||||
let parsed = reader::read_bytes(&bytes).unwrap();
|
||||
assert_eq!(tag, parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gzip_roundtrip() {
|
||||
let tag = Tag::Compound(HashMap::from([("val".into(), Tag::Int(12345))]));
|
||||
let gz = to_gzip_bytes(&tag).unwrap();
|
||||
let parsed = reader::read_gzip(&gz).unwrap();
|
||||
assert_eq!(tag, parsed);
|
||||
}
|
||||
}
|
||||
20
crates/nbt/src/lib.rs
Executable file
20
crates/nbt/src/lib.rs
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
//! Zero-copy NBT (Named Binary Tag) parser and writer.
|
||||
//!
|
||||
//! Implements the Minecraft NBT format with optional serde support.
|
||||
//! Uses zero-copy deserialization where possible to minimize allocations.
|
||||
//!
|
||||
//! ## Format support
|
||||
//!
|
||||
//! - All tag types: Byte, Short, Int, Long, Float, Double, String,
|
||||
//! List, Compound, IntArray, LongArray, ByteArray
|
||||
//! - GZip compressed streams
|
||||
//! - Java edition (Big Endian) and Bedrock edition (Little Endian) variants
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub mod io;
|
||||
pub mod tag;
|
||||
|
||||
pub use io::reader;
|
||||
pub use io::writer;
|
||||
pub use tag::Tag;
|
||||
58
crates/nbt/src/tag.rs
Executable file
58
crates/nbt/src/tag.rs
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
//! NBT tag types.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Tag {
|
||||
End,
|
||||
Byte(i8),
|
||||
Short(i16),
|
||||
Int(i32),
|
||||
Long(i64),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
String(String),
|
||||
List(Vec<Tag>),
|
||||
Compound(HashMap<String, Tag>),
|
||||
ByteArray(Vec<i8>),
|
||||
IntArray(Vec<i32>),
|
||||
LongArray(Vec<i64>),
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Tag::End => "TAG_End",
|
||||
Tag::Byte(_) => "TAG_Byte",
|
||||
Tag::Short(_) => "TAG_Short",
|
||||
Tag::Int(_) => "TAG_Int",
|
||||
Tag::Long(_) => "TAG_Long",
|
||||
Tag::Float(_) => "TAG_Float",
|
||||
Tag::Double(_) => "TAG_Double",
|
||||
Tag::String(_) => "TAG_String",
|
||||
Tag::List(_) => "TAG_List",
|
||||
Tag::Compound(_) => "TAG_Compound",
|
||||
Tag::ByteArray(_) => "TAG_Byte_Array",
|
||||
Tag::IntArray(_) => "TAG_Int_Array",
|
||||
Tag::LongArray(_) => "TAG_Long_Array",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u8 {
|
||||
match self {
|
||||
Tag::End => 0,
|
||||
Tag::Byte(_) => 1,
|
||||
Tag::Short(_) => 2,
|
||||
Tag::Int(_) => 3,
|
||||
Tag::Long(_) => 4,
|
||||
Tag::Float(_) => 5,
|
||||
Tag::Double(_) => 6,
|
||||
Tag::String(_) => 7,
|
||||
Tag::List(_) => 8,
|
||||
Tag::Compound(_) => 9,
|
||||
Tag::ByteArray(_) => 10,
|
||||
Tag::IntArray(_) => 11,
|
||||
Tag::LongArray(_) => 12,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue