""" NBT Parser for Godot Engine Copyright (C) 2022 Jason Table This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ class_name NBT # note to self. make nbt load from buffer function enum { TAG_End, TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Long_Array, } var stream := StreamPeerBuffer.new() #var parsed_dict := {} func load_file(path:String,compressed:bool=true): var file = File.new() file.open(path,file.READ) var bytes:PoolByteArray = file.get_buffer(file.get_len()) if compressed: #bytes = bytes.decompress_dynamic(-1,file.COMPRESSION_DEFLATE) bytes = bytes.decompress_dynamic(-1,file.COMPRESSION_GZIP) stream.data_array = bytes stream.big_endian = true # Here is where it starts to read return _read_compound().data func _read_byte(): var value = stream.get_8() return {"type":TAG_Byte,"data":value} func _read_short(): var value = stream.get_16() return {"type":TAG_Short,"data":value} func _read_int(): var value = stream.get_32() return {"type":TAG_Int,"data":value} func _read_long(): var value = stream.get_64() return {"type":TAG_Long,"data":value} func _read_float(): var value = stream.get_float() return {"type":TAG_Float,"data":value} func _read_double(): var value = stream.get_double() return {"type":TAG_Double,"data":value} func _read_byte_array(): var size = stream.get_32() var get = stream.get_data(size) if get[0] != OK: push_error( "Error reading NBT at %s, error code %s"%[stream.get_position(),get[0]] ) var value:PoolByteArray = get[1] return {"type":TAG_Byte_Array,"data":value} func _read_string(): var size = stream.get_u16() var value = stream.get_utf8_string(size) return {"type":TAG_String,"data":value} func _read_list(): var valuetype = stream.get_8() var size = stream.get_32() var values = [] for _i in range(0,size): values.append(_read_type(valuetype)) return {"type":TAG_List,"valuetype":valuetype,"data":values} func _read_compound(): var parsed:Dictionary = {} var reading = true while reading: # get element type var type = stream.get_u8() if type == TAG_End: reading = false break # get element key var keylen = stream.get_u16() var key = stream.get_utf8_string(keylen) # get data parsed[key] = _read_type(type) return {"type":TAG_Compound,"data":parsed} func _read_int_array(): var size = stream.get_32() var value = PoolIntArray() for _i in range(0,size): value.append(stream.get_32()) return {"type":TAG_Int_Array,"data":value} func _read_long_array(): var size = stream.get_32() var value := [] for _i in range(0,size): value.append(stream.get_64()) return {"type":TAG_Long_Array,"data":value} # calls the read function depending on its parameter: func _read_type(type): if type == TAG_Byte: return _read_byte() elif type == TAG_Short: return _read_short() elif type == TAG_Int: return _read_int() elif type == TAG_Long: return _read_long() elif type == TAG_Float: return _read_float() elif type == TAG_Double: return _read_double() elif type == TAG_Byte_Array: return _read_byte_array() elif type == TAG_String: return _read_string() elif type == TAG_List: return _read_list() elif type == TAG_Compound: return _read_compound() elif type == TAG_Int_Array: return _read_int_array() elif type == TAG_Long_Array: return _read_long_array() #code for writing nbt files is below func save_file(path:String,data:Dictionary,compressed:bool=true): #create stream stream.data_array = [] stream.big_endian = true stream.seek(0) #validate and parse dictionary data if data.size() != 1: printerr("Invalid. Root dictonary must contain one entry") return false _write_compound(data,true) #write file var bytes = stream.data_array var file = File.new() file.open(path,file.WRITE) if compressed: bytes = bytes.compress(file.COMPRESSION_GZIP) #print(bytes) file.store_buffer(bytes) file.close() return true func _write_data_type(data:Dictionary): var type = data.type if type == TAG_Byte: return _write_byte(data.data) elif type == TAG_Short: return _write_short(data.data) elif type == TAG_Int: return _write_int(data.data) elif type == TAG_Long: return _write_long(data.data) elif type == TAG_Float: return _write_float(data.data) elif type == TAG_Double: return _write_double(data.data) elif type == TAG_Byte_Array: return _write_byte_array(data.data) elif type == TAG_String: return _write_string(data.data) elif type == TAG_List: return _write_list(data.data,data.valuetype) elif type == TAG_Compound: return _write_compound(data.data) elif type == TAG_Int_Array: return _write_int_array(data.data) elif type == TAG_Long_Array: return _write_long_array(data.data) func _write_byte(data:int): stream.put_8(data) func _write_short(data:int): stream.put_16(data) func _write_int(data:int): stream.put_32(data) func _write_long(data:int): stream.put_64(data) func _write_float(data:float): stream.put_float(data) func _write_double(data:float): stream.put_double(data) func _write_byte_array(data:PoolByteArray): var size = data.size() stream.put_32(size) stream.put_data(data) func _write_string(data:String): var size = data.to_utf8().size() stream.put_u16(size) stream.put_data(data.to_utf8()) func _write_list(data:Array,valuetype): var size = data.size() stream.put_8(valuetype) stream.put_32(size) for i in data: if i.type != valuetype: push_error("NBT Error: NBT lists contained different types of values.") return _write_data_type(i) func _write_compound(data:Dictionary,root=false): for each in data.keys(): var key:String = each var keysize = key.length() var type = data[key].type stream.put_u8(type) stream.put_u16(keysize) stream.put_data(key.to_utf8()) _write_data_type(data[key]) # Write end of compound tag: if not root: stream.put_u8(TAG_End) func _write_int_array(data:PoolIntArray): stream.put_32(data.size()) for i in data: stream.put_32(i) func _write_long_array(data:Array): stream.put_32(data.size()) for i in data: stream.put_64(i) static func list2array(input:Dictionary) -> Array: # converts a loaded NBT list type to an Array var output := [] if input.type == TAG_List and input.valuetype == TAG_String: for i in input.data: if i.type == TAG_String and typeof(i.data) == TYPE_STRING: output.append(i.data) else: push_error("Cannot convert list to array.") return output static func array2list(input:Array,type = TAG_Int) -> Dictionary: # converts a Array to a dictionry that can be saved to NBT # as a list of integers saved as bytes, shorts, ints, or longs # This will not validate the values to make sure they match the type var output := {"type":TAG_List,"valuetype":type,"data":[]} for i in input: output.data.append({"type":type,"data":i}) return output