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 }