1 /* 2 Copyright (c) 2013 Andrey Penechko 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license the "Software" to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 module anchovy.utils.nbt; 30 31 private import std.bitmanip; 32 private import std.conv; 33 private import std.string; 34 private import std.zlib; 35 36 private import anchovy.utils.streamutils; 37 38 NbtBase ReadNbt(in ubyte[] inData){ 39 UnCompress uncompressor = new UnCompress(HeaderFormat.gzip); 40 const(void)[] decData; 41 try{ 42 decData = uncompressor.uncompress(inData); 43 decData ~= uncompressor.flush(); 44 }catch(ZlibException){ 45 NbtException("Wrong Gzip data format"); 46 } 47 48 ubyte[] data = cast(ubyte[])decData; 49 return ReadTag( data ); 50 } 51 52 void WriteNbt(NbtBase nbtbase, ref ubyte[] outData){ 53 ubyte[] data; 54 Compress compressor=new Compress(9,HeaderFormat.gzip); 55 56 outData ~= cast(ubyte[])compressor.compress(cast(const(void)[]) WriteTag(nbtbase)); 57 outData ~= cast(ubyte[])compressor.flush(); 58 } 59 60 NbtBase ReadTag(ref ubyte[] inData){ 61 byte byte0= Read!byte( inData ); 62 if(byte0 == 0){ 63 return new NbtTagEnd(); 64 } 65 else 66 { 67 NbtBase nbtbase = CreateTagOfType( byte0 ); 68 nbtbase.key = ReadString( inData ); 69 nbtbase.readTagValue( inData ); 70 return nbtbase; 71 } 72 } 73 74 ref ubyte[] WriteTag( NbtBase nbtbase){ 75 ubyte[] data; 76 return WriteTag(nbtbase, data); 77 } 78 79 ref ubyte[] WriteTag( NbtBase nbtbase,ref ubyte[] outData){ 80 outData~=nbtbase.getType(); 81 if(nbtbase.getType()==0) 82 { 83 NbtException("Empty data given"); 84 }else 85 { 86 WriteString(nbtbase.key,outData); 87 nbtbase.writeTagValue(outData); 88 } 89 return outData; 90 } 91 92 NbtBase CreateTagOfType(ubyte type){ 93 switch(type){ 94 case 0: 95 return new NbtTagEnd(); 96 case 1: 97 return new NbtTagByte(); 98 case 2: 99 return new NbtTagShort(); 100 case 3: 101 return new NbtTagInt(); 102 case 4: 103 return new NbtTagLong(); 104 case 5: 105 return new NbtTagFloat(); 106 case 6: 107 return new NbtTagDouble(); 108 case 7: 109 return new NbtTagByteArray(); 110 case 8: 111 return new NbtTagString(); 112 case 9: 113 return new NbtTagList(); 114 case 10: 115 return new NbtTagCompound(); 116 default: 117 return null; 118 } 119 } 120 121 class NbtException:Exception{ 122 this(string message){ 123 super("NbtException: "~message); 124 } 125 126 static void opCall(string message){ 127 throw(new NbtException(message)); 128 } 129 } 130 131 abstract class NbtBase{ 132 133 abstract void readTagValue(ref ubyte[] inData); 134 abstract void writeTagValue(ref ubyte[] outData); 135 abstract byte getType(); 136 override abstract string toString(); 137 public: 138 @property string key(){return _key;} 139 @property string key(string key){return _key=key;} 140 141 private: 142 string _key; 143 byte _type; 144 } 145 146 class NbtTagEnd:NbtBase{ 147 public: 148 override byte getType(){return 0;} 149 override void readTagValue(ref ubyte[] inData){} 150 override void writeTagValue(ref ubyte[] outData){} 151 override string toString(){return "END";} 152 } 153 154 private string GenTag(string type, string typeId){ 155 return "class NbtTag"~type~":NbtBase{ 156 157 override void readTagValue(ref ubyte[] inData){ 158 "~toLower(type)~"Value = Read!"~toLower(type)~"( inData ); 159 } 160 override void writeTagValue(ref ubyte[] outData){ 161 Write!"~toLower(type)~"(outData, "~toLower(type)~"Value); 162 } 163 164 public: 165 override byte getType(){ return "~typeId~"; } 166 override string toString(){return to!string("~toLower(type)~"Value);} 167 private: 168 "~toLower(type)~" "~toLower(type)~"Value; 169 }"; 170 } 171 172 mixin(GenTag("Byte","1")); 173 mixin(GenTag("Short","2")); 174 mixin(GenTag("Int","3")); 175 mixin(GenTag("Long","4")); 176 mixin(GenTag("Float","5")); 177 mixin(GenTag("Double","6")); 178 179 class NbtTagByteArray:NbtBase{ 180 181 override void readTagValue(ref ubyte[] inData){ 182 byteArrayValue = ReadArray!(byte)( inData, Read!int( inData ) ); 183 } 184 override void writeTagValue(ref ubyte[] outData){ 185 Write!int(outData, byteArrayValue.length); 186 outData~=cast(ubyte[])byteArrayValue; 187 } 188 public: 189 override byte getType(){ return 7; } 190 override string toString(){return "["~to!string(byteArrayValue.length)~" bytes]";} 191 192 private: 193 byte[] byteArrayValue; 194 } 195 196 class NbtTagString:NbtBase{ 197 198 override void readTagValue(ref ubyte[] inData){ 199 StringValue = ReadString( inData ); 200 } 201 override void writeTagValue(ref ubyte[] outData){ 202 WriteString(StringValue,outData); 203 } 204 public: 205 override byte getType(){ return 8; } 206 override string toString(){return StringValue;} 207 208 private: 209 string StringValue; 210 } 211 212 class NbtTagList:NbtBase{ 213 214 override void readTagValue(ref ubyte[] inData){ 215 tagType = Read!byte(inData ); 216 int lng = Read!int( inData ); 217 NbtBase nbtbase; 218 for(size_t i=0;i<lng;++i){ 219 nbtbase = CreateTagOfType(tagType); 220 nbtbase.readTagValue(inData); 221 tagList~=nbtbase; 222 } 223 } 224 225 override void writeTagValue(ref ubyte[] outData){ 226 Write!byte(outData, tagType); 227 Write!int(outData, tagList.length); 228 foreach(tag;tagList){ 229 tag.writeTagValue(outData); 230 } 231 } 232 233 234 public: 235 override byte getType(){ return 9; } 236 237 override string toString(){return to!string(tagList.length)~" entries of type"~NbtTagNames[tagType];} 238 239 @property size_t length(){return tagList.length;} 240 241 NbtBase opIndex(size_t index){return tagList[index];} 242 243 ref NbtTagList opOpAssign(string op)(NbtBase rhs) 244 if (op == "~"){ 245 tagList ~= rhs; 246 return this; 247 } 248 NbtBase opIndexAssign(NbtBase rhs, size_t index){ 249 tagList[index]=rhs; 250 return this; 251 } 252 private: 253 NbtBase[] tagList; 254 byte tagType; 255 } 256 257 class NbtTagCompound:NbtBase{ 258 259 override void readTagValue(ref ubyte[] inData){ 260 NbtBase nbtbase; 261 while((nbtbase=ReadTag( inData )).getType()!=0){ 262 tagMap[nbtbase.key]=nbtbase; 263 } 264 } 265 override void writeTagValue(ref ubyte[] outData){ 266 foreach(tag;tagMap.byValue()){ 267 WriteTag(tag,outData); 268 } 269 outData~=0; 270 } 271 272 public: 273 override byte getType(){ return 10; } 274 override string toString(){return to!string(tagMap.length)~" entries";} 275 276 NbtBase opIndexAssign(NbtBase rhs, string index){ 277 tagMap[index]=rhs; 278 return this; 279 } 280 NbtBase opIndex(string index){return tagMap[index];} 281 282 @property size_t length(){return tagMap.length;} 283 private: 284 NbtBase[string] tagMap; 285 } 286 287 string[11] NbtTagNames= 288 ["Nbt_Tag_End", 289 "Nbt_Tag_Byte", 290 "Nbt_Tag_Short", 291 "Nbt_Tag_Int", 292 "Nbt_Tag_Long", 293 "Nbt_Tag_Float", 294 "Nbt_Tag_Double", 295 "Nbt_Tag_Byte_Array" , 296 "Nbt_Tag_String", 297 "Nbt_Tag_List", 298 "Nbt_Tag_Compound" ]; 299 300 private string ReadString(ref ubyte[] data){ 301 302 ubyte[2] shrt= data[0..2]; 303 short lng = bigEndianToNative!short(shrt); 304 scope(exit){ 305 data=data[lng+2..$]; 306 } 307 return cast(string)cast(char[])data[2..lng+2]; 308 } 309 310 private void WriteString( string str,ref ubyte[] outData ){ 311 312 Write!short( outData, cast(short)str.length ); 313 outData~=str; 314 }