Come funziona il formato del tag NBT Minecraft?

Sto avendo problemi a capire il formato del tag NBT.

Conosco TAG_INT e TAG_CHAR . In effetti, ho fatto alcune programmazioni di base.

Voglio creare un editor di inventario per Minecraft. Ma il file di dati del mondo è in un formato binario e non riesco a trovare un programma (con codice sorgente per C) che possa aiutarmi a tradurre tutti questi dati.

Come funziona il formato NBT?

Per creare un editor di inventario non è necessario sapere in che modo la struttura binaria di NBT è sufficiente per utilizzare una delle librerie NBT esistenti.

Ad esempio, controlla il seguente sito Web che elenca alcune librerie per 11 diversi linguaggi di programmazione popolari.

Quel sito Web riassume sostanzialmente le basi del sistema NBT.

Se si desidera comunque creare da "niente", è necessario tenere presente che è ansible incontrare dati NBT in formati diversi (non compressi o compressi con gzip o zlib).

Per quanto ne so, un file NBT normalmente inizia sempre con 1 elemento composto, tuttavia questo non è richiesto dalle specifiche.

Ogni elemento inizia con 1 byte, che specifica il tipo (chiamato anche il tipo di tag).

 id name 0 TAG_End 1 TAG_Byte 2 TAG_Short 3 TAG_Int 4 TAG_Long 5 TAG_Float 6 TAG_Double 7 TAG_Byte_Array 8 TAG_String 9 TAG_List 10 TAG_Compound 11 TAG_Int_Array 

Ogni tag / elemento ad exception del tag End ha un nome. Quando ha un nome, il byte id del tag è seguito da 2 byte (big endian, ad esempio 00 0A (hex) significa lunghezza 10) che specifica la lunghezza della string. Questa lunghezza è quindi seguita da N byte, questi byte sono i byte della string.

Questi byte di nome sono quindi seguiti dai dati effettivi del tag. I dati di TAG_Byte, TAG_Short, TAG_Int, TAG_Long sono numbers big-endian memorizzati rispettivamente in 1,2,4 e 8 byte. Nota: Java non ha tipi di dati non firmati, quindi supponiamo che questi siano tipi firmati

TAG_Float e TAG_Double sono 4 e 8 byte. Secondo 1 sono memorizzati come big endian IEEE-754 numbers a virgola mobile di precisione singola / double. Come analizzare questi potrebbe dipendere dal linguaggio di programmazione scelto.

I dati dei tag di arrays (TAG_Byte_Array / TAG_Int_Array) iniziano con un numero integer a 32 bit a 4 byte che indica la lunghezza dell'arrays. Dopo la lunghezza contiene C * N byte, where N è la lunghezza letta e C la quantità di byte necessaria per elemento (So 1 per byte, 4 per numbers interi)

I dati di TAG_String sono 2 byte (brevi) che indicano la lunghezza e quindi i byte di lunghezza per i caratteri di string.

TAG_Compound è essenzialmente un contenitore per più nodes. I dati sono altri tag e tutti i tag futuri sono figli di questo tag, finché non viene letto un TAG_End.

Il tag TAG_List è un elenco di valori di un tipo specifico. I dati contengono 1 byte che indica il tipo (fare riferimento ai TAG elencati sopra) seguiti da 4 byte che specificano la quantità di elementi. Ogni elemento viene letto leggendo solo la sezione dei dati del tag associato. (Quindi, escludendo il byte TagId la lunghezza del nome e i caratteri del nome)

Per riassumere: Consente di specificare [NAME_BLOCK] come i 2 byte contenenti la lunghezza e i byte (lunghezza) contenenti i caratteri.

 TAG_ID FORMAT total length (bytes) data length TAG_End [TAG_ID] 1 0 TAG_Byte [TAG_ID] [NAME_BLOCK] [VALUE] 4 + name.length 1 TAG_Short [TAG_ID] [NAME_BLOCK] [VALUE] 5 + name.length 2 TAG_Int [TAG_ID] [NAME_BLOCK] [VALUE] 7 + name.length 4 TAG_Long [TAG_ID] [NAME_BLOCK] [VALUE] 11 + name.length 8 TAG_Float [TAG_ID] [NAME_BLOCK] [VALUE] 7 + name.length 4 TAG_Double [TAG_ID] [NAME_BLOCK] [VALUE] 11 + name.length 8 TAG_String [TAG_ID] [NAME_BLOCK] [VALUE_LENGTH] [VALUE] 5 + name.length + value.length 2 + value.length TAG_Byte_Array [TAG_ID] [NAME_BLOCK] [NUM_ELEMENTS] [ELEMENTS] 7 + name.length + num_elements 4 + (1 * num_elements) TAG_Int_Array [TAG_ID] [NAME_BLOCK] [NUM_ELEMENTS] [ELEMENTS] 7 + (4 * num_elements) 4 + (4 * num_elements) TAG_List [TAG_ID] [NAME_BLOCK] [TYPE] [NUM_ELEMENTS] [ELEMENTS] 8 + name.length + elements.bytes() 5 + elements.bytes() TAG_Compound [TAG_ID] [NAME_BLOCK] [TAGS.....] 4 + name.length + tags.bytes() tags.bytes() 

Alcuni esempi:

 bytes What it is 05 TAG -> TAG_Int 00 05 Length of name tag => 5-characters 48 65 6C 6C 6F The characters spelling Hello 00 00 01 02 The value of the Integer tag, 4 bytes (value = 258) bytes What it is 10 TAG -> TAG_Compound 00 04 Length of name tag => 4-characters 48 65 6C 6C 6F The characters spelling world 05 TAG -> TAG_Int 00 05 Length of name tag => 5-characters 48 65 6C 6C 6F The characters spelling Hello 00 00 01 02 The value of the Integer tag, 4 bytes (value = 258) 00 TAG -> TAG_End, the end of the compound tag body/value. (The int is part of the value of the compound.) 

Secondo questo sito web i file del player sono compressi con GZip. Inoltre potresti trovare utile questa pagina

Nel caso abbiate bisogno di un esempio, ecco un esempio di java (in grado di leggere, uscite come JSON (con tipi di tag ecc.), Java 1.8+, richiede GSon).

 import com.google.gson.*; import java.io.*; import java.util.*; import java.util.zip.GZIPInputStream; public class SimpleNBTReader { interface Helper{ Object apply(DataInput t) throws Exception; } static class Node{ TagType type; String name; Object value; public Node(TagType type,String name, Object value){ this.type = type; this.name = name; this.value = value; } } enum TagType { TAG_End(s -> null), TAG_Byte(DataInput::readByte), TAG_Short(DataInput::readShort), TAG_Int(DataInput::readInt), TAG_Long(DataInput::readLong), TAG_Float(DataInput::readFloat), TAG_Double(DataInput::readDouble), TAG_Byte_Array(in -> { int len = in.readInt(); byte[] bytes = new byte[len]; in.readFully(bytes); return bytes; }), TAG_String(in -> { int len = in.readShort(); byte[] bytes = new byte[len]; in.readFully(bytes); return new String(bytes,"UTF-8"); }), TAG_List(in ->{ TagType type = TagType.values()[in.readByte()]; int len = in.readInt(); Object[] values = new Object[len]; for(int i=0; i < len; i++) values[i] = type.read(in); return values; }), TAG_Compound(in -> { List<Object> values = new LinkedList<>(); while(true){ TagType type = TagType.values()[in.readByte()]; if(type == TagType.TAG_End) break; values.add(readTag(type,in)); } return values; }), TAG_Int_Array(in -> { int len = in.readInt(); int[] values = new int[len]; for(int i=0; i < len; i++) values[i] = in.readInt(); return values; }) ; private Helper body; TagType(Helper body) { this.body = body; } public Object read(DataInput in) throws Exception { return body.apply(in); } } private static Node readTag(TagType type, DataInput in) throws Exception{ if(type == TagType.TAG_End) throw new Exception("TAG_END has no name data."); int nameLength = in.readShort(); byte[] buffer = new byte[nameLength]; in.readFully(buffer); String name = new String(buffer,"UTF-8"); return new Node(type,name,type.read(in)); } public static Object read(DataInput in) throws Exception{ TagType type = TagType.values()[in.readByte()]; return readTag(type,in); } public static void main(String[] args) throws Exception { Gson gs = (new GsonBuilder()).setPrettyPrinting().create(); DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream("player_save_file.dat"))); Object v = read(in); System.out.println(gs.toJson(v)); } }