1 module nbtd.NBTList; 2 3 import std.bitmanip; 4 import std.zlib; 5 import std.format; 6 7 import nbtd; 8 9 /// A List containing unnamed NBT Items. 10 class NBTList : INBTItem 11 { 12 private: 13 string _name; 14 NBTType _elementType; 15 INBTItem[] _items; 16 public: 17 this(INBTItem[] items = [], string name = "") 18 { 19 _name = name; 20 if(items.length > 0) 21 { 22 _elementType = items[0].type; 23 value = items; 24 } 25 } 26 27 @property NBTType type() { return NBTType.List; } 28 @property int size() { int len = 0; for(int i = 0; i < _items.length; i++) len += _items[i].size; return 5 + len; } 29 30 @property string name() { return _name; } 31 @property void name(string name) { assert(name.length < short.max, "Name is too long! (%s)".format(name.length)); _name = name; } 32 33 @property INBTItem[] value() { return _items; } 34 @property void value(INBTItem[] value) 35 { 36 if(_items.length == 0 && value.length > 0) 37 elementType = value[0].type; 38 for(int i = 0; i < value.length; i++) 39 assert(value[i].type == elementType); 40 _items = value[]; 41 } 42 43 /// Gets/Sets the type of the array. Will automatically get overwritten when array length is 0 and new values are assigned. 44 @property ref elementType() { return _elementType; } 45 46 /// Returns: the item at the index `index` 47 T get(T : INBTItem = INBTItem)(size_t index) 48 { 49 return cast(T)_items[index]; 50 } 51 52 ubyte[] encode(bool compressed = true, bool hasName = true) 53 { 54 ubyte[] data; 55 if(hasName) data = new ubyte[3 + name.length + size]; 56 else data = new ubyte[size]; 57 58 int dataIndex = 0; 59 60 if(hasName) 61 { 62 data[0] = cast(ubyte)type; 63 64 data.write!short(cast(short)name.length, 1); 65 data[3 .. 3 + name.length] = cast(ubyte[])name; 66 67 data[3 + name.length] = cast(ubyte)elementType; 68 data.write!int(cast(int)value.length, 4 + name.length); 69 dataIndex = 8 + name.length; 70 } 71 else 72 { 73 data[0] = cast(ubyte)elementType; 74 data.write!int(cast(int)value.length, 1); 75 dataIndex = 5; 76 } 77 78 for(int i = 0; i < value.length; i++) 79 { 80 auto buffer = value[i].encode(false, false); 81 data[dataIndex .. dataIndex + buffer.length] = buffer; 82 dataIndex += buffer.length; 83 } 84 85 if(compressed) 86 { 87 return compressGZip(data); 88 } 89 return data; 90 } 91 92 void decode(ubyte[] data, bool compressed = true, bool hasName = true) 93 { 94 if(compressed) 95 { 96 data = uncompressGZip(data); 97 } 98 read(data, hasName); 99 } 100 101 void read(ref ubyte[] stream, bool hasName = true) 102 { 103 _name = ""; 104 if(hasName) 105 { 106 assert(stream.read!ubyte == type); 107 short nameLength = stream.read!short; 108 _name = cast(string)stream[0 .. nameLength]; 109 stream = stream[nameLength .. $]; 110 } 111 elementType = cast(NBTType)stream.read!ubyte; 112 int arrLength = stream.read!int; 113 _items.length = 0; 114 for(int i = 0; i < arrLength; i++) 115 _items ~= parseElement(elementType, stream, false); 116 } 117 118 @property INBTItem dup() 119 { 120 auto copy = new NBTList(); 121 copy.name = name; 122 copy.value = value; 123 return copy; 124 } 125 126 /// Returns: the list with at most 100 characters width 127 override string toString() 128 { 129 return toString(100); 130 } 131 132 /// Returns: the list with a specified line width. 133 /// Returns: `"NBTList('name') = [each element.toString]"` 134 /// Returns: if longer than `lineLength` will return `"NBTList('name') = [each el..."` 135 string toString(int lineLength) 136 { 137 string items = format("%s", value); 138 if(items.length > lineLength - 21) 139 return format("NBTList('%s') = %s...", name, items[0 .. lineLength - 21]); 140 return format("NBTList('%s') = %s", name, items); 141 } 142 143 /// Duplicates the List and appends an item to it if operator is `~`. 144 /// Otherwise `static assert(0)` 145 NBTList opBinary(string op)(INBTItem item) 146 { 147 static if(op == "~") 148 { 149 NBTList copy = new NBTList(); 150 copy.name = name; 151 copy.value = value ~ item; 152 return copy; 153 } 154 else static assert(0, "Operator " ~ op ~ " is not implemented!"); 155 } 156 157 /// Appends an item to `this` if operator is `~`. 158 /// Otherwise `static assert(0)` 159 NBTList opOpAssign(string op)(INBTItem item) 160 { 161 static if(op == "~") 162 { 163 if(_value.length == 0) 164 elementType = item.type; 165 assert(item.type == elementType); 166 _value ~= item; 167 return this; 168 } 169 else static assert(0, "Operator " ~ op ~ " is not implemented!"); 170 } 171 172 /// Appends multiple items to `this` if operator is `~`. 173 /// Otherwise `static assert(0)` 174 NBTList opOpAssign(string op)(INBTItem[] items) 175 { 176 static if(op == "~") 177 { 178 if(items.length == 0) 179 return this; 180 if(_value.length == 0) 181 elementType = items[0].type; 182 foreach(item; items) 183 assert(item.type == elementType); 184 _value ~= items; 185 return this; 186 } 187 else static assert(0, "Operator " ~ op ~ " is not implemented!"); 188 } 189 190 /// Returns: the item at index `index` 191 INBTItem opIndex(size_t index) 192 { 193 return _items[index]; 194 } 195 196 /// Returns: a duplicate of `this.value` 197 INBTItem[] opIndex() 198 { 199 return _items[]; 200 } 201 202 /// Returns: a slice of `this.value` 203 INBTItem[] opSlice(size_t start, size_t end) 204 { 205 return _items[start .. end]; 206 } 207 208 /// Returns: the length of `this.value` 209 /// See_Also: length 210 size_t opDollar() 211 { 212 return _items.length; 213 } 214 215 /// Returns: the length of `this.value` 216 @property size_t length() 217 { 218 return _items.length; 219 } 220 }