1 /// Generic File Format (gff)
2 module nwn.gff;
3 
4 
5 import std.stdio: File;
6 import std.stdint;
7 import std.conv: to;
8 import std.string;
9 import std.exception: enforce;
10 import std.base64: Base64;
11 import std.algorithm;
12 import std.array;
13 import std.traits;
14 import std.math;
15 import std.meta;
16 
17 import nwnlibd.orderedaa;
18 import nwnlibd.orderedjson;
19 import nwnlibd.parseutils;
20 
21 debug import std.stdio;
22 version(unittest) import std.exception: assertThrown, assertNotThrown;
23 
24 /// Parsing exception
25 class GffParseException : Exception{
26 	@safe pure nothrow this(string msg, string f=__FILE__, size_t l=__LINE__, Throwable t=null){
27 		super(msg, f, l, t);
28 	}
29 }
30 /// Value doesn't match constraints (ex: label too long)
31 class GffValueSetException : Exception{
32 	@safe pure nothrow this(string msg, string f=__FILE__, size_t l=__LINE__, Throwable t=null){
33 		super(msg, f, l, t);
34 	}
35 }
36 /// Type mismatch (ex: trying to add a $(D GffNode) to a $(D GffType.Int))
37 class GffTypeException : Exception{
38 	@safe pure nothrow this(string msg, string f=__FILE__, size_t l=__LINE__, Throwable t=null){
39 		super(msg, f, l, t);
40 	}
41 }
42 /// Child node not found
43 class GffNotFoundException : Exception{
44 	@safe pure nothrow this(string msg, string f=__FILE__, size_t l=__LINE__, Throwable t=null){
45 		super(msg, f, l, t);
46 	}
47 }
48 /// Child node not found
49 class GffJsonParseException : Exception{
50 	@safe pure nothrow this(string msg, string f=__FILE__, size_t l=__LINE__, Throwable t=null){
51 		super(msg, f, l, t);
52 	}
53 }
54 
55 /// Type of data owned by a `GffNode`
56 /// See_Also: `gffTypeToNative`
57 enum GffType{
58 	Invalid   = -1, /// Init value
59 	Byte      = 0,  /// Signed 8-bit int
60 	Char      = 1,  /// Unsigned 8-bit int
61 	Word      = 2,  /// Signed 16-bit int
62 	Short     = 3,  /// Unsigned 16-bit int
63 	DWord     = 4,  /// Signed 32-bit int
64 	Int       = 5,  /// Unsigned 32-bit int
65 	DWord64   = 6,  /// Signed 64-bit int
66 	Int64     = 7,  /// Unsigned 64-bit int
67 	Float     = 8,  /// 32-bit float
68 	Double    = 9,  /// 64-bit float
69 	String    = 10, /// String
70 	ResRef    = 11, /// String with width <= 16 (32 for NWN2)
71 	LocString = 12, /// Localized string
72 	Void      = 13, /// Binary data
73 	Struct    = 14, /// Map of other $(D GffNode)
74 	List      = 15  /// Array of other $(D GffNode)
75 }
76 
77 /// List of all dlang types that can be stored in a GFF value
78 alias GffNativeTypes = AliasSeq!(GffByte, GffChar, GffWord, GffShort, GffDWord, GffInt, GffDWord64, GffInt64, GffFloat, GffDouble, GffString, GffResRef, GffLocString, GffVoid, GffStruct, GffList, GffValue);
79 
80 /// Maps $(D GffType) to native D type
81 template gffTypeToNative(GffType t){
82 	import std.typecons: Tuple;
83 	static if     (t==GffType.Invalid)   static assert(0, "No native type for GffType.Invalid");
84 	else static if(t==GffType.Byte)      alias gffTypeToNative = GffByte;
85 	else static if(t==GffType.Char)      alias gffTypeToNative = GffChar;
86 	else static if(t==GffType.Word)      alias gffTypeToNative = GffWord;
87 	else static if(t==GffType.Short)     alias gffTypeToNative = GffShort;
88 	else static if(t==GffType.DWord)     alias gffTypeToNative = GffDWord;
89 	else static if(t==GffType.Int)       alias gffTypeToNative = GffInt;
90 	else static if(t==GffType.DWord64)   alias gffTypeToNative = GffDWord64;
91 	else static if(t==GffType.Int64)     alias gffTypeToNative = GffInt64;
92 	else static if(t==GffType.Float)     alias gffTypeToNative = GffFloat;
93 	else static if(t==GffType.Double)    alias gffTypeToNative = GffDouble;
94 	else static if(t==GffType.String)    alias gffTypeToNative = GffString;
95 	else static if(t==GffType.ResRef)    alias gffTypeToNative = GffResRef;
96 	else static if(t==GffType.LocString) alias gffTypeToNative = GffLocString;
97 	else static if(t==GffType.Void)      alias gffTypeToNative = GffVoid;
98 	else static if(t==GffType.Struct)    alias gffTypeToNative = GffStruct;
99 	else static if(t==GffType.List)      alias gffTypeToNative = GffList;
100 	else static assert(0);
101 }
102 /// Converts a native type $(D T) to the associated $(D GffType). Types must match exactly
103 template nativeToGffType(T){
104 	import std.typecons: Tuple;
105 	static if     (is(T == GffByte))      alias nativeToGffType = GffType.Byte;
106 	else static if(is(T == GffChar))      alias nativeToGffType = GffType.Char;
107 	else static if(is(T == GffWord))      alias nativeToGffType = GffType.Word;
108 	else static if(is(T == GffShort))     alias nativeToGffType = GffType.Short;
109 	else static if(is(T == GffDWord))     alias nativeToGffType = GffType.DWord;
110 	else static if(is(T == GffInt))       alias nativeToGffType = GffType.Int;
111 	else static if(is(T == GffDWord64))   alias nativeToGffType = GffType.DWord64;
112 	else static if(is(T == GffInt64))     alias nativeToGffType = GffType.Int64;
113 	else static if(is(T == GffFloat))     alias nativeToGffType = GffType.Float;
114 	else static if(is(T == GffDouble))    alias nativeToGffType = GffType.Double;
115 	else static if(is(T == GffString))    alias nativeToGffType = GffType.String;
116 	else static if(is(T == GffResRef))    alias nativeToGffType = GffType.ResRef;
117 	else static if(is(T == GffLocString)) alias nativeToGffType = GffType.LocString;
118 	else static if(is(T == GffVoid))      alias nativeToGffType = GffType.Void;
119 	else static if(is(T == GffStruct))    alias nativeToGffType = GffType.Struct;
120 	else static if(is(T == GffList))      alias nativeToGffType = GffType.List;
121 	else static assert(0, "Type "~T.stringof~" is not a valid GffType");
122 }
123 
124 /// returns true if T is implicitly convertible to a GFF storage type
125 template isGffNativeType(T){
126 	bool isGffNativeType(){
127 		bool ret = false;
128 		foreach(TYP ; GffNativeTypes){
129 			static if(is(T: TYP)){
130 				ret = true;
131 				break;
132 			}
133 		}
134 		return ret;
135 	}
136 }
137 
138 
139 /// Converts a GFF type to a string compatible with niv tools
140 string gffTypeToCompatStr(in GffType t){
141 	final switch(t) with(GffType) {
142 		case Invalid:   assert(0, "Invalid GffType");
143 		case Byte:      return "byte";
144 		case Char:      return "char";
145 		case Word:      return "word";
146 		case Short:     return "short";
147 		case DWord:     return "dword";
148 		case Int:       return "int";
149 		case DWord64:   return "dword64";
150 		case Int64:     return "int64";
151 		case Float:     return "float";
152 		case Double:    return "double";
153 		case String:    return "cexostr";
154 		case ResRef:    return "resref";
155 		case LocString: return "cexolocstr";
156 		case Void:      return "void";
157 		case Struct:    return "struct";
158 		case List:      return "list";
159 	}
160 }
161 /// Converts a string compatible with niv tools to a GFF type
162 GffType compatStrToGffType(in string t){
163 	switch(t) with(GffType) {
164 		case "byte":       return Byte;
165 		case "char":       return Char;
166 		case "word":       return Word;
167 		case "short":      return Short;
168 		case "dword":      return DWord;
169 		case "int":        return Int;
170 		case "dword64":    return DWord64;
171 		case "int64":      return Int64;
172 		case "float":      return Float;
173 		case "double":     return Double;
174 		case "cexostr":    return String;
175 		case "resref":     return ResRef;
176 		case "cexolocstr": return LocString;
177 		case "void":       return Void;
178 		case "struct":     return Struct;
179 		case "list":       return List;
180 		default:           return GffType.Invalid;
181 	}
182 
183 }
184 
185 
186 
187 alias GffByte    = uint8_t; //GFF type Byte ( $(D uint8_t) )
188 alias GffChar    = int8_t; //GFF type Char ( $(D int8_t) )
189 alias GffWord    = uint16_t; //GFF type Word ( $(D uint16_t) )
190 alias GffShort   = int16_t; //GFF type Short ( $(D int16_t) )
191 alias GffDWord   = uint32_t; //GFF type DWord ( $(D uint32_t) )
192 alias GffInt     = int32_t; //GFF type Int ( $(D int32_t) )
193 alias GffDWord64 = uint64_t; //GFF type DWord64 ( $(D uint64_t) )
194 alias GffInt64   = int64_t; //GFF type Int64 ( $(D int64_t) )
195 alias GffFloat   = float; //GFF type Float ( $(D float) )
196 alias GffDouble  = double; //GFF type Double ( $(D double) )
197 alias GffString  = string; //GFF type String ( $(D string) )
198 
199 /// Gff type ResRef (32-char string)
200 struct GffResRef {
201 	///
202 	this(in string str){
203 		value = str;
204 	}
205 
206 	@property @safe {
207 		/// Accessor for manipulating GffResRef like a string
208 		string value() const {
209 			return data.charArrayToString();
210 		}
211 		/// ditto
212 		void value(in string str){
213 			assert(str.length <= 32, "Resref cannot be longer than 32 characters");
214 			data = str.stringToCharArray!(char[32]);
215 		}
216 	}
217 	///
218 	alias value this;
219 
220 
221 	auto ref opAssign(in string _value){
222 		return value = _value;
223 	}
224 private:
225 	char[32] data;
226 }
227 
228 /// Gff type LocString (TLK ID + translations)
229 struct GffLocString{
230 	/// String ref linking to a TLK entry
231 	uint32_t strref;
232 	/// Language => string pairs
233 	string[int32_t] strings;
234 
235 	///
236 	this(uint32_t strref, string[int32_t] strings = null){
237 		this.strref = strref;
238 		this.strings = strings;
239 	}
240 
241 	/// Duplicate GffLocString content
242 	GffLocString dup() const {
243 		string[int32_t] newStrings;
244 		foreach(k, v ; strings)
245 			newStrings[k] = v;
246 		return GffLocString(strref, newStrings);
247 	}
248 
249 	/// Get the string value without attempting to resolve strref using TLKs
250 	string toString() const {
251 		if(strings.length > 0){
252 			foreach(lang ; EnumMembers!LanguageGender){
253 				if(auto str = lang in strings)
254 					return *str;
255 			}
256 		}
257 
258 		if(strref != strref.max){
259 			return format!"{{STRREF:%d}}"(strref);
260 		}
261 		return "";
262 	}
263 
264 	/// Get the string value without attempting to resolve strref using TLKs
265 	string toPrettyString() const {
266 		return format!"{%d, %s}"(strref == strref.max ? -1 : cast(long)strref, strings);
267 	}
268 
269 	/// JSON to LocString
270 	this(in nwnlibd.orderedjson.JSONValue json){
271 		enforce!GffJsonParseException(json.type == JSONType.object, "json value " ~ json.toPrettyString ~ " is not an object");
272 		enforce!GffJsonParseException(json["type"].str == "cexolocstr", "json object "~ json.toPrettyString ~" is not a GffLocString");
273 		enforce!GffJsonParseException(json["value"].type == JSONType.object, "json .value "~ json.toPrettyString ~" is not an object");
274 		strref = json["str_ref"].get!uint32_t;
275 		foreach(lang, text ; json["value"].object){
276 			strings[lang.to!int32_t] = text.str;
277 		}
278 	}
279 	/// LocString to JSON
280 	nwnlibd.orderedjson.JSONValue toJson() const {
281 		JSONValue ret;
282 		ret["type"] = "cexolocstr";
283 		ret["str_ref"] = strref;
284 		ret["value"] = JSONValue(cast(JSONValue[string])null);
285 		foreach(ref lang, ref str ; strings)
286 			ret["value"][lang.to!string] = str;
287 		return ret;
288 	}
289 
290 	/// Remove all localization and set its value to a unique string
291 	auto ref opAssign(in string value){
292 		strref = strref.max;
293 		strings = [0: value];
294 	}
295 
296 	/// Set the strref
297 	auto ref opAssign(in uint32_t value){
298 		strref = value;
299 	}
300 
301 
302 	import nwn.tlk: StrRefResolver, LanguageGender, TlkOutOfBoundsException;
303 	/// Resolve the localized string using TLKs
304 	string resolve(in StrRefResolver resolver) const {
305 		if(strings.length > 0){
306 			immutable preferedLang = resolver.standartTable.language * 2;
307 			if(auto str = preferedLang in strings)
308 				return *str;
309 			if(auto str = preferedLang + 1 in strings)
310 				return *str;
311 			if(auto str = -2 in strings) // SetFirstName sets the -2 language
312 				return *str;
313 
314 			foreach(lang ; EnumMembers!LanguageGender){
315 				if(auto str = lang in strings)
316 					return *str;
317 			}
318 		}
319 
320 		if(strref != strref.max){
321 			try return resolver[strref];
322 			catch(TlkOutOfBoundsException){
323 				return "invalid_strref";
324 			}
325 		}
326 
327 		return "";
328 	}
329 }
330 unittest {
331 	// TLK resolving
332 	import nwn.tlk;
333 	auto resolv = new StrRefResolver(
334 		new Tlk(cast(ubyte[])import("dialog.tlk")),
335 		new Tlk(cast(ubyte[])import("user.tlk"))
336 	);
337 
338 	auto locStr = GffLocString();
339 	locStr.strref = Tlk.UserTlkIndexOffset + 1;
340 	locStr.strings = [0:"male english", 1:"female english", 2:"male french"];
341 
342 	assert(locStr.resolve(resolv) == "male french");
343 	assert(locStr.to!string == "male english");// without TLK, use english
344 
345 	locStr.strings = [0:"male english", 1:"female english"];
346 	assert(locStr.resolve(resolv) == "male english");
347 	assert(locStr.to!string == "male english");
348 
349 	locStr.strings = [4:"male german", 6:"female italian"];
350 	assert(locStr.resolve(resolv) == "male german");
351 	assert(locStr.to!string == "male german");
352 
353 	locStr.strings.clear;
354 	assert(locStr.resolve(resolv) == "Café liégeois");
355 	assert(locStr.to!string == "{{STRREF:16777217}}");
356 
357 	locStr.strref = StrRef.max;
358 	assert(locStr.resolve(resolv) == "");
359 	assert(locStr.to!string == "");
360 }
361 
362 /// Gff type Void ( $(D ubyte[]) )
363 alias GffVoid = ubyte[];
364 
365 
366 /// Gff type Struct ( `GffValue[string]` )
367 struct GffStruct {
368 	///
369 	this(GffValue[string] children, uint32_t id){
370 		foreach(k, ref v ; children){
371 			this[k] = v;
372 		}
373 		this.id = id;
374 	}
375 	///
376 	this(OrderedAA!(string, GffValue) children, uint32_t id){
377 		this.children = children;
378 		this.id = id;
379 	}
380 	/// Copy constructor
381 	this(GffStruct other){
382 		m_children = other.m_children;
383 		id = other.id;
384 	}
385 
386 	/// Duplicate GffStruct content
387 	GffStruct dup() const {
388 		auto dup_children = children.dup();
389 		foreach(ref GffValue c ; dup_children){
390 			switch(c.type) {
391 				case GffType.Struct:
392 					c = GffValue(c.get!GffStruct.dup());
393 					break;
394 				case GffType.List:
395 					c = GffValue(c.get!GffList.dup());
396 					break;
397 				case GffType.LocString:
398 					c = GffValue(c.get!GffLocString.dup());
399 					break;
400 				case GffType.Void:
401 					c = GffValue(c.get!GffVoid.dup());
402 					break;
403 				default:
404 					break;
405 			}
406 		}
407 		return GffStruct(dup_children, this.id);
408 	}
409 
410 
411 	/// Struct ID
412 	uint32_t id = 0;
413 
414 
415 	@property{
416 		/// GffStruct children associative array
417 		ref inout(ChildrenAA) children() inout {
418 			return *cast(inout(ChildrenAA)*)(&m_children);
419 		}
420 		/// ditto
421 		void children(ChildrenAA children){
422 			m_children = *cast(ChildrenStor*)&children;
423 		}
424 	}
425 	///
426 	alias children this;
427 
428 
429 	/// Automatically encapsulate and add a GFF native type
430 	ref GffValue opIndexAssign(T)(T rhs, in string label) if(isGffNativeType!T) {
431 		children[label] = GffValue(rhs);
432 		return children[label];
433 	}
434 
435 	/// Converts a GffStruct to a user-friendly string
436 	string toPrettyString(string tabs = null) const {
437 		string ret = format!"%s(Struct %s)"(tabs, id == id.max ? "-1" : id.to!string);
438 		foreach(ref kv ; children.byKeyValue){
439 			const innerTabs = tabs ~ "|  ";
440 			const type = (kv.value.type != GffType.Struct && kv.value.type != GffType.List) ? " (" ~ kv.value.type.to!string ~ ")" : null;
441 
442 			ret ~= format!"\n%s├╴ %-16s = %s%s"(
443 				tabs,
444 				kv.key, kv.value.toPrettyString(innerTabs)[innerTabs.length .. $], type
445 			);
446 		}
447 		return ret;
448 	}
449 
450 
451 	/// JSON to GffStruct
452 	this(in nwnlibd.orderedjson.JSONValue json){
453 		assert(json.type == JSONType.object, "json value " ~ json.toPrettyString ~ " is not an object");
454 		assert(json["type"].str == "struct", "json object "~ json.toPrettyString ~" is not a GffStruct");
455 		assert(json["value"].type == JSONType.object, "json .value "~ json.toPrettyString ~" is not an object");
456 		if(auto structId = ("__struct_id" in json))
457 			id = structId.get!uint32_t;
458 		foreach(ref label ; json["value"].objectKeyOrder){
459 			children[label] = GffValue(json["value"][label]);
460 		}
461 	}
462 	/// GffStruct to JSON
463 	nwnlibd.orderedjson.JSONValue toJson() const {
464 		JSONValue ret;
465 		ret["type"] = "struct";
466 		ret["__struct_id"] = id;
467 		ret["value"] = JSONValue();
468 		foreach(ref kv ; children.byKeyValue){
469 			ret["value"][kv.key] = kv.value.toJson();
470 		}
471 		return ret;
472 	}
473 
474 
475 private:
476 	alias ChildrenAA = OrderedAA!(string, GffValue);
477 	alias ChildrenStor = OrderedAA!(string, ubyte[_gffValueSize]);
478 
479 	// We store children as ubyte[5] instead of GffValue, because GffValue
480 	// needs a complete GffStruct definition
481 	enum _gffValueSize = 5;
482 	static assert(ceil(GffValue.sizeof / 8.0) * 8 == 8 * _gffValueSize);
483 	OrderedAA!(string, ubyte[_gffValueSize]) m_children;
484 }
485 
486 /// Gff type List ( `GffStruct[]` )
487 struct GffList {
488 	///
489 	this(GffStruct[] children){
490 		this.children = children;
491 	}
492 
493 	/// Copy constructor
494 	this(GffList other){
495 		children = other.children;
496 	}
497 
498 	/// Duplicate GffList content
499 	GffList dup() const {
500 		return GffList(children.map!(a => a.dup()).array());
501 	}
502 
503 	/// GffList children list
504 	GffStruct[] children;
505 	alias children this;
506 
507 	/// Converts a GffStruct to a user-friendly string
508 	string toPrettyString(string tabs = null) const {
509 		string ret = format!"%s(List)"(tabs);
510 		foreach(i, ref child ; children){
511 			auto innerTabs = tabs ~ "|  ";
512 
513 			ret ~= format!"\n%s├╴ %s"(
514 				tabs,
515 				child.toPrettyString(innerTabs)[innerTabs.length .. $]
516 			);
517 		}
518 		return ret;
519 	}
520 
521 	/// JSON to GffList
522 	this(in nwnlibd.orderedjson.JSONValue json){
523 		assert(json.type == JSONType.object, "json value " ~ json.toPrettyString ~ " is not an object");
524 		assert(json["type"].str == "list", "json object "~ json.toPrettyString ~" is not a GffList");
525 		assert(json["value"].type == JSONType.array, "json .value "~ json.toPrettyString ~" is not an array");
526 		children.length = json["value"].array.length;
527 		foreach(i, ref child ; json["value"].array){
528 			children[i] = GffStruct(child);
529 		}
530 	}
531 	/// GffList to JSON
532 	nwnlibd.orderedjson.JSONValue toJson() const {
533 		JSONValue ret;
534 		ret["type"] = "list";
535 		ret["value"] = JSONValue(cast(JSONValue[])null);
536 		ret["value"].array.length = children.length;
537 		foreach(i, ref child ; children){
538 			ret["value"][i] = child.toJson();
539 		}
540 		return ret;
541 	}
542 }
543 
544 
545 
546 
547 /// GFF value that can contain any type of GFF node
548 struct GffValue {
549 	import std.variant: VariantN;
550 	///
551 	alias Value = VariantN!(32,
552 		GffByte, GffChar,
553 		GffWord, GffShort,
554 		GffDWord, GffInt,
555 		GffDWord64, GffInt64,
556 		GffFloat, GffDouble,
557 		GffString, GffResRef, GffLocString, GffVoid,
558 		GffStruct, GffList,
559 	);
560 
561 	///
562 	Value value;
563 	///
564 	alias value this;
565 
566 	///
567 	this(T)(T _value) if(isGffNativeType!T) {
568 		value = _value;
569 	}
570 
571 	///
572 	this(GffType _type) {
573 		final switch(_type) with(GffType) {
574 			case Byte:      value = GffByte.init;      break;
575 			case Char:      value = GffChar.init;      break;
576 			case Word:      value = GffWord.init;      break;
577 			case Short:     value = GffShort.init;     break;
578 			case DWord:     value = GffDWord.init;     break;
579 			case Int:       value = GffInt.init;       break;
580 			case DWord64:   value = GffDWord64.init;   break;
581 			case Int64:     value = GffInt64.init;     break;
582 			case Float:     value = GffFloat.init;     break;
583 			case Double:    value = GffDouble.init;    break;
584 			case String:    value = GffString.init;    break;
585 			case ResRef:    value = GffResRef.init;    break;
586 			case LocString: value = GffLocString.init; break;
587 			case Void:      value = GffVoid.init;      break;
588 			case Struct:    value = GffStruct.init;    break;
589 			case List:      value = GffList.init;      break;
590 			case Invalid:   assert(0);
591 		}
592 	}
593 
594 	@property {
595 		/// Get currently stored GFF type
596 		GffType type() const {
597 			static GffType[ulong] lookupTable = null;
598 			if(lookupTable is null){
599 				import std.traits: EnumMembers;
600 				static foreach(gffType ; EnumMembers!GffType){
601 					static if(gffType != GffType.Invalid)
602 						lookupTable[typeid(gffTypeToNative!gffType).toHash] = gffType;
603 					else
604 						lookupTable[typeid(void).toHash] = gffType;
605 				}
606 			}
607 			return lookupTable[value.type.toHash];
608 		}
609 	}
610 
611 	/// Retrieve a reference to the GFF value or throws
612 	/// Throws: GffTypeException if the types don't match
613 	ref inout(T) get(T)() inout {
614 		if(auto ptr = value.peek!T)
615 			return *cast(inout T*)ptr;
616 		throw new GffTypeException(format!"GFF Type mismatch: node is %s, trying to get it as %s"(value.type, T.stringof));
617 	}
618 
619 	/// Shorthand for modifying the GffValue as a GffStruct
620 	auto ref opIndex(in string label){
621 		return value.get!GffStruct[label];
622 	}
623 	/// ditto
624 	auto ref opIndexAssign(T)(T rhs, in string label) if(is(T: GffValue) || isGffNativeType!T){
625 		static if(isGffNativeType!T)
626 			return value.get!GffStruct[label] = GffValue(rhs);
627 		else
628 			return value.get!GffStruct[label] = rhs;
629 	}
630 
631 	/// Shorthand for modifying the GffValue as a GffList
632 	auto ref opIndex(size_t index){
633 		return value.get!GffList[index];
634 	}
635 	/// ditto
636 	auto ref opIndexAssign(GffStruct rhs, size_t index){
637 		return value.get!GffList[index] = rhs;
638 	}
639 
640 	/// Converts the stored value into the given type
641 	T to(T)() const {
642 		final switch(type) with(GffType) {
643 			case Byte:      static if(__traits(compiles, get!GffByte.to!T))      return get!GffByte.to!T;      else break;
644 			case Char:      static if(__traits(compiles, get!GffChar.to!T))      return get!GffChar.to!T;      else break;
645 			case Word:      static if(__traits(compiles, get!GffWord.to!T))      return get!GffWord.to!T;      else break;
646 			case Short:     static if(__traits(compiles, get!GffShort.to!T))     return get!GffShort.to!T;     else break;
647 			case DWord:     static if(__traits(compiles, get!GffDWord.to!T))     return get!GffDWord.to!T;     else break;
648 			case Int:       static if(__traits(compiles, get!GffInt.to!T))       return get!GffInt.to!T;       else break;
649 			case DWord64:   static if(__traits(compiles, get!GffDWord64.to!T))   return get!GffDWord64.to!T;   else break;
650 			case Int64:     static if(__traits(compiles, get!GffInt64.to!T))     return get!GffInt64.to!T;     else break;
651 			case Float:     static if(__traits(compiles, get!GffFloat.to!T))     return get!GffFloat.to!T;     else break;
652 			case Double:    static if(__traits(compiles, get!GffDouble.to!T))    return get!GffDouble.to!T;    else break;
653 			case String:    static if(__traits(compiles, get!GffString.to!T))    return get!GffString.to!T;    else break;
654 			case ResRef:    static if(__traits(compiles, get!GffResRef.to!T))    return get!GffResRef.to!T;    else break;
655 			case LocString: static if(__traits(compiles, get!GffLocString.to!T)) return get!GffLocString.to!T; else break;
656 			case Void:      static if(__traits(compiles, get!GffVoid.to!T))      return get!GffVoid.to!T;      else break;
657 			case Struct:    static if(__traits(compiles, get!GffStruct.to!T))    return get!GffStruct.to!T;    else break;
658 			case List:      static if(__traits(compiles, get!GffList.to!T))      return get!GffList.to!T;      else break;
659 			case Invalid:   assert(0, "No type set");
660 		}
661 		assert(0, format!"Cannot convert GFFType %s to %s"(type, T.stringof));
662 	}
663 	/// Create a GffValue by parsing JSON.
664 	/// JSON format should be like `{"type": "resref", "value": "hello world"}`
665 	this(in nwnlibd.orderedjson.JSONValue json){
666 		assert(json.type == JSONType.object, "json value " ~ json.toPrettyString ~ " is not an object");
667 		switch(json["type"].str.compatStrToGffType) with(GffType) {
668 			case Byte:      value = cast(GffByte)json["value"].get!GffByte;       break;
669 			case Char:      value = cast(GffChar)json["value"].get!GffChar;       break;
670 			case Word:      value = cast(GffWord)json["value"].get!GffWord;       break;
671 			case Short:     value = cast(GffShort)json["value"].get!GffShort;     break;
672 			case DWord:     value = cast(GffDWord)json["value"].get!GffDWord;     break;
673 			case Int:       value = cast(GffInt)json["value"].get!GffInt;         break;
674 			case DWord64:   value = cast(GffDWord64)json["value"].get!GffDWord64; break;
675 			case Int64:     value = cast(GffInt64)json["value"].get!GffInt64;     break;
676 			case Float:     value = cast(GffFloat)json["value"].get!GffFloat;     break;
677 			case Double:    value = cast(GffDouble)json["value"].get!GffDouble;   break;
678 			case String:    value = json["value"].str;                break;
679 			case ResRef:    value = GffResRef(json["value"].str);     break;
680 			case LocString: value = GffLocString(json);               break;
681 			case Void:      value = Base64.decode(json["value"].str); break;
682 			case Struct:    value = GffStruct(json);                  break;
683 			case List:      value = GffList(json);                    break;
684 			default: throw new GffJsonParseException("Unknown Gff type string: '"~json["type"].str~"'");
685 		}
686 	}
687 	/// Converts to JSON
688 	nwnlibd.orderedjson.JSONValue toJson() const {
689 		JSONValue ret;
690 		final switch(type) with(GffType) {
691 			case Byte:      ret["value"] = get!GffByte; break;
692 			case Char:      ret["value"] = get!GffChar; break;
693 			case Word:      ret["value"] = get!GffWord; break;
694 			case Short:     ret["value"] = get!GffShort; break;
695 			case DWord:     ret["value"] = get!GffDWord; break;
696 			case Int:       ret["value"] = get!GffInt; break;
697 			case DWord64:   ret["value"] = get!GffDWord64; break;
698 			case Int64:     ret["value"] = get!GffInt64; break;
699 			case Float:     ret["value"] = get!GffFloat; break;
700 			case Double:    ret["value"] = get!GffDouble; break;
701 			case String:    ret["value"] = get!GffString; break;
702 			case ResRef:    ret["value"] = get!GffResRef.value; break;
703 			case LocString: return get!GffLocString.toJson;
704 			case Void:      ret["value"] = Base64.encode(get!GffVoid).to!string; break;
705 			case Struct:    return get!GffStruct.toJson;
706 			case List:      return get!GffList.toJson;
707 			case Invalid:   assert(0, "No type set");
708 		}
709 		ret["type"] = type.gffTypeToCompatStr;
710 		return ret;
711 	}
712 
713 	/// Converts to a user-readable string
714 	string toPrettyString(string tabs = null) const {
715 		final switch(type) with(GffType) {
716 			case Byte, Char, Word, Short, DWord, Int, DWord64, Int64, Float, Double, String:
717 			case ResRef:
718 				return tabs ~ to!string;
719 			case LocString:
720 				return tabs ~ get!GffLocString.toPrettyString;
721 			case Void:
722 				import std.base64: Base64;
723 				return tabs ~ Base64.encode(get!GffVoid).to!string;
724 			case Struct:
725 				return get!GffStruct.toPrettyString(tabs);
726 			case List:
727 				return get!GffList.toPrettyString(tabs);
728 			case Invalid:
729 				assert(0);
730 		}
731 	}
732 }
733 
734 
735 
736 
737 /// Complete GFF file
738 class Gff{
739 	/// Empty GFF
740 	this(){}
741 
742 	/// Create a Gff by parsing the binary format
743 	this(in ubyte[] data){
744 		GffRawParser(data).parse(this);
745 	}
746 	/// Create a Gff by parsing a file in binary format
747 	this(File file){
748 		ubyte[] data;
749 		data.length = GffRawParser.RawHeader.sizeof;
750 		auto readCount = file.rawRead(data).length;
751 		enforce!GffParseException(readCount >= GffRawParser.RawHeader.sizeof,
752 			"File is too small to be GFF: "~readCount.to!string~" bytes read, "~GffRawParser.RawHeader.sizeof.to!string~" needed !");
753 
754 		auto header = cast(GffRawParser.RawHeader*)data.ptr;
755 		immutable size_t fileLength =
756 			header.list_indices_offset + header.list_indices_count;
757 
758 		data.length = fileLength;
759 		readCount += file.rawRead(data[GffRawParser.RawHeader.sizeof..$]).length;
760 		enforce!GffParseException(readCount >= fileLength,
761 			"File is too small to be GFF: "~readCount.to!string~" bytes read, "~fileLength.to!string~" needed !");
762 
763 		this(data);
764 	}
765 	// ditto
766 	this(in string path){
767 		this(File(path, "r"));
768 	}
769 
770 	/// Convert to binary format
771 	ubyte[] serialize(){
772 		return GffRawSerializer().serialize(this);
773 	}
774 
775 	/// Create a Gff by parsing JSON
776 	this(in nwnlibd.orderedjson.JSONValue json){
777 		fileType = json["__data_type"].str;
778 		fileVersion = "__data_version" in json ? json["__data_version"].str : "V3.2";
779 		root = GffStruct(json);
780 	}
781 
782 	/// Convert to JSON
783 	nwnlibd.orderedjson.JSONValue toJson() const {
784 		auto ret = root.toJson();
785 		ret["__data_type"] = fileType;
786 		ret["__data_version"] = fileVersion;
787 		return ret;
788 	}
789 
790 	/// Converts the GFF into a user-friendly string
791 	string toPrettyString() const {
792 		return "========== GFF-"~fileType~"-"~fileVersion~" ==========\n"
793 			~ root.toPrettyString;
794 	}
795 
796 	@property{
797 		/// GFF type name stored in the GFF file
798 		/// Max width: 4 chars
799 		const string fileType(){return m_fileType.idup.stripRight;}
800 		/// ditto
801 		void fileType(in string type){
802 			const len = type.length;
803 			enforce!GffValueSetException(type.length <= 4, "fileType length must be <= 4");
804 			m_fileType[0 .. len] = type;
805 			if(len < 4)
806 				m_fileType[len .. $] = ' ';
807 		}
808 		/// GFF version stored in the GFF file. Usually "V3.2"
809 		/// Max width: 4 chars
810 		const string fileVersion(){return m_fileVersion.idup.stripRight;}
811 		/// ditto
812 		void fileVersion(in string ver){
813 			const len = ver.length;
814 			enforce!GffValueSetException(ver.length <= 4, "fileVersion length must be <= 4");
815 			m_fileVersion[0 .. len] = ver;
816 			if(len < 4)
817 				m_fileVersion[len .. $] = ' ';
818 		}
819 	}
820 
821 
822 	///
823 	alias root this;
824 	/// Root $(D GffStruct)
825 	GffStruct root;
826 
827 private:
828 	char[4] m_fileType, m_fileVersion;
829 }
830 
831 
832 
833 private struct GffRawParser{
834 	@disable this();
835 	this(in ubyte[] _data){
836 		assert(_data.length >= RawHeader.sizeof, "Data length is so small it cannot even contain the header");
837 
838 		data = _data;
839 		headerPtr       = cast(const RawHeader*)      (data.ptr);
840 		structsPtr      = cast(const RawStruct*)      (data.ptr + headerPtr.struct_offset);
841 		fieldsPtr       = cast(const RawField*)       (data.ptr + headerPtr.field_offset);
842 		labelsPtr       = cast(const RawLabel*)       (data.ptr + headerPtr.label_offset);
843 		fieldDatasPtr   = cast(const RawFieldData*)   (data.ptr + headerPtr.field_data_offset);
844 		fieldIndicesPtr = cast(const RawFieldIndices*)(data.ptr + headerPtr.field_indices_offset);
845 		listIndicesPtr  = cast(const RawListIndices*) (data.ptr + headerPtr.list_indices_offset);
846 
847 		assert(data.length == headerPtr.list_indices_offset+headerPtr.list_indices_count,
848 			"Data length do not match header");
849 	}
850 
851 	static align(1) struct RawHeader{
852 		char[4]  file_type;
853 		char[4]  file_version;
854 		uint32_t struct_offset;
855 		uint32_t struct_count;
856 		uint32_t field_offset;
857 		uint32_t field_count;
858 		uint32_t label_offset;
859 		uint32_t label_count;
860 		uint32_t field_data_offset;
861 		uint32_t field_data_count;
862 		uint32_t field_indices_offset;
863 		uint32_t field_indices_count;
864 		uint32_t list_indices_offset;
865 		uint32_t list_indices_count;
866 	}
867 	static align(1) struct RawStruct{
868 		uint32_t id;
869 		uint32_t data_or_data_offset;
870 		uint32_t field_count;
871 		debug string toString() const {
872 			return format!"Struct(id=%d dodo=%d fc=%d)"(id, data_or_data_offset, field_count);
873 		}
874 	}
875 	static align(1) struct RawField{
876 		uint32_t type;
877 		uint32_t label_index;
878 		uint32_t data_or_data_offset;
879 		debug string toString() const {
880 			return format!"Field(t=%s lblidx=%d dodo=%d)"(type.to!GffType, label_index, data_or_data_offset);
881 		}
882 	}
883 	static align(1) struct RawLabel{
884 		char[16] value;
885 		debug string toString() const {
886 			return format!"Label(%s)"(value.charArrayToString);
887 		}
888 	}
889 	static align(1) struct RawFieldData{
890 		uint8_t first_data;//First byte of data. Other follows
891 	}
892 	static align(1) struct RawFieldIndices{
893 		uint32_t field_index;
894 	}
895 	static align(1) struct RawListIndices{
896 		uint32_t length;
897 		uint32_t first_struct_index;
898 		debug string toString() const {
899 			return format!"ListIndices(len=%d start=%d)"(length, first_struct_index);
900 		}
901 	}
902 
903 	const ubyte[] data;
904 	const RawHeader* headerPtr;
905 	const RawStruct* structsPtr;
906 	const RawField*  fieldsPtr;
907 	const RawLabel*  labelsPtr;
908 	const void*         fieldDatasPtr;
909 	const void*         fieldIndicesPtr;
910 	const void*         listIndicesPtr;
911 
912 	const(RawStruct*) getRawStruct(in size_t index) const {
913 		assert(index < headerPtr.struct_count, "index "~index.to!string~" out of bounds");
914 		return &structsPtr[index];
915 	}
916 	const(RawField*) getRawField(in size_t index) const {
917 		assert(index < headerPtr.field_count, "index "~index.to!string~" out of bounds");
918 		return &fieldsPtr[index];
919 	}
920 	const(RawLabel*) getRawLabel(in size_t index) const {
921 		assert(index < headerPtr.label_count, "index "~index.to!string~" out of bounds");
922 		return &labelsPtr[index];
923 	}
924 	const(RawFieldData*) getRawFieldData(in size_t offset) const {
925 		assert(offset < headerPtr.field_data_count, "offset "~offset.to!string~" out of bounds");
926 		return cast(const RawFieldData*)(fieldDatasPtr + offset);
927 	}
928 	const(RawFieldIndices*) getRawFieldIndices(in size_t offset) const {
929 		assert(offset < headerPtr.field_indices_count, "offset "~offset.to!string~" out of bounds");
930 		return cast(const RawFieldIndices*)(fieldIndicesPtr + offset);
931 	}
932 	const(RawListIndices*) getRawListIndices(in size_t offset) const {
933 		assert(offset < headerPtr.list_indices_count, "offset "~offset.to!string~" out of bounds");
934 		return cast(const RawListIndices*)(listIndicesPtr + offset);
935 	}
936 
937 	void parse(Gff gff){
938 		gff.fileType = headerPtr.file_type.to!string;
939 		gff.fileVersion = headerPtr.file_version.to!string;
940 		gff.root = buildStruct(0);
941 	}
942 
943 	GffStruct buildStruct(size_t structIndex){
944 		GffStruct ret;
945 
946 		auto s = getRawStruct(structIndex);
947 		ret.id = s.id;
948 
949 		version(gff_verbose_parse){
950 			stderr.writefln("%sParsing struct: index=%d %s",
951 				gff_verbose_rtIndent, structIndex, *s
952 			);
953 			gff_verbose_rtIndent ~= "│ ";
954 		}
955 
956 		if(s.field_count==1){
957 			const fieldIndex = s.data_or_data_offset;
958 
959 			auto f = getRawField(fieldIndex);
960 			const label = charArrayToString(getRawLabel(f.label_index).value);
961 			version(gff_verbose_parse){
962 				stderr.writefln("%s%d: %s ↴",
963 					gff_verbose_rtIndent, 0, label
964 				);
965 				gff_verbose_rtIndent ~= "   ";
966 			}
967 			ret.dirtyAppendKeyValue(label, buildValue(fieldIndex));
968 			version(gff_verbose_parse) gff_verbose_rtIndent = gff_verbose_rtIndent[0 .. $ - 3];
969 		}
970 		else if(s.field_count > 1){
971 			auto fi = getRawFieldIndices(s.data_or_data_offset);
972 
973 			foreach(i ; 0 .. s.field_count){
974 				const fieldIndex = fi[i].field_index;
975 				auto f = getRawField(fieldIndex);
976 				const label = charArrayToString(getRawLabel(f.label_index).value);
977 
978 				version(gff_verbose_parse){
979 					stderr.writefln("%s%d: %s ↴",
980 						gff_verbose_rtIndent, i, label
981 					);
982 					gff_verbose_rtIndent ~= "   ";
983 				}
984 
985 				ret.dirtyAppendKeyValue(label, buildValue(fieldIndex));
986 
987 				version(gff_verbose_parse) gff_verbose_rtIndent = gff_verbose_rtIndent[0 .. $ - 3];
988 			}
989 		}
990 
991 		version(gff_verbose_parse) gff_verbose_rtIndent = gff_verbose_rtIndent[0 .. $ - 4];
992 
993 		return ret;
994 	}
995 
996 
997 	GffList buildList(size_t listIndex){
998 		GffList ret;
999 		auto li = getRawListIndices(listIndex);
1000 		if(li.length>0){
1001 			const indices = &li.first_struct_index;
1002 
1003 			ret.length = li.length;
1004 			foreach(i, ref gffStruct ; ret){
1005 				gffStruct = buildStruct(indices[i]);
1006 			}
1007 		}
1008 		return ret;
1009 	}
1010 
1011 	GffValue buildValue(size_t fieldIndex){
1012 		GffValue ret;
1013 		auto f = getRawField(fieldIndex);
1014 
1015 		version(gff_verbose_parse){
1016 			stderr.writefln("%sParsing value: type=%s fieldIndex=%d field=%s",
1017 				gff_verbose_rtIndent, f.type.to!GffType, fieldIndex, *f,
1018 			);
1019 			gff_verbose_rtIndent ~= "│ ";
1020 		}
1021 
1022 		final switch(f.type) with(GffType){
1023 			case Invalid: assert(0, "Invalid value type");
1024 
1025 			case Byte:  ret.value = *cast(GffByte*) &f.data_or_data_offset; break;
1026 			case Char:  ret.value = *cast(GffChar*) &f.data_or_data_offset; break;
1027 			case Word:  ret.value = *cast(GffWord*) &f.data_or_data_offset; break;
1028 			case Short: ret.value = *cast(GffShort*)&f.data_or_data_offset; break;
1029 			case DWord: ret.value = *cast(GffDWord*)&f.data_or_data_offset; break;
1030 			case Int:   ret.value = *cast(GffInt*)  &f.data_or_data_offset; break;
1031 			case Float: ret.value = *cast(GffFloat*)&f.data_or_data_offset; break;
1032 
1033 			case DWord64:
1034 			case Int64:
1035 			case Double:
1036 				const d = getRawFieldData(f.data_or_data_offset);
1037 				switch(f.type){
1038 					case DWord64: ret.value = *cast(GffDWord64*)d; break;
1039 					case Int64:   ret.value = *cast(GffInt64*)  d; break;
1040 					case Double:  ret.value = *cast(GffDouble*) d; break;
1041 					default: assert(0);
1042 				}
1043 				break;
1044 
1045 			case String:
1046 				const data = getRawFieldData(f.data_or_data_offset);
1047 				const size = cast(const uint32_t*)data;
1048 				const chars = cast(const char*)(data + uint32_t.sizeof);
1049 				ret.value = chars[0..*size].idup;
1050 				break;
1051 
1052 			case ResRef:
1053 				const data = getRawFieldData(f.data_or_data_offset);
1054 				const size = cast(const uint8_t*)data;
1055 				const chars = cast(const char*)(data + uint8_t.sizeof);
1056 				ret.value = GffResRef(chars[0..*size].idup);
1057 				break;
1058 
1059 			case LocString:
1060 				const data = getRawFieldData(f.data_or_data_offset);
1061 				const str_ref = cast(const uint32_t*)(data+uint32_t.sizeof);
1062 				const str_count = cast(const uint32_t*)(data+2*uint32_t.sizeof);
1063 				auto sub_str = cast(void*)(data+3*uint32_t.sizeof);
1064 
1065 				auto val = GffLocString(*str_ref);
1066 				foreach(i ; 0 .. *str_count){
1067 					const id = cast(const int32_t*)sub_str;
1068 					const length = cast(const int32_t*)(sub_str+uint32_t.sizeof);
1069 					const str = cast(const char*)(sub_str+2*uint32_t.sizeof);
1070 
1071 					val.strings[*id] = str[0..*length].idup;
1072 					sub_str += 2*uint32_t.sizeof + char.sizeof*(*length);
1073 				}
1074 				ret.value = val;
1075 				break;
1076 
1077 			case Void:
1078 				const data = getRawFieldData(f.data_or_data_offset);
1079 				const size = cast(const uint32_t*)data;
1080 				const dataVoid = cast(const ubyte*)(data+uint32_t.sizeof);
1081 				ret.value = dataVoid[0..*size].dup;
1082 				break;
1083 
1084 			case Struct:
1085 				ret.value = buildStruct(f.data_or_data_offset);
1086 				break;
1087 
1088 			case List:
1089 				ret.value = buildList(f.data_or_data_offset);
1090 				break;
1091 
1092 		}
1093 		version(gff_verbose_parse) gff_verbose_rtIndent = gff_verbose_rtIndent[0..$-4];
1094 		return ret;
1095 	}
1096 	string dumpRawGff() const{
1097 		import std.string: center, rightJustify, toUpper;
1098 		import nwnlibd.parseutils;
1099 
1100 		string ret;
1101 
1102 		void printTitle(in string title){
1103 			ret ~= "======================================================================================\n";
1104 			ret ~= title.toUpper.center(86)~"\n";
1105 			ret ~= "======================================================================================\n";
1106 		}
1107 
1108 		printTitle("header");
1109 		with(headerPtr){
1110 			ret ~= "'"~file_type~"'    '"~file_version~"'\n";
1111 			ret ~= "struct:        offset="~struct_offset.to!string~"  count="~struct_count.to!string~"\n";
1112 			ret ~= "field:         offset="~field_offset.to!string~"  count="~field_count.to!string~"\n";
1113 			ret ~= "label:         offset="~label_offset.to!string~"  count="~label_count.to!string~"\n";
1114 			ret ~= "field_data:    offset="~field_data_offset.to!string~"  count="~field_data_count.to!string~"\n";
1115 			ret ~= "field_indices: offset="~field_indices_offset.to!string~"  count="~field_indices_count.to!string~"\n";
1116 			ret ~= "list_indices:  offset="~list_indices_offset.to!string~"  count="~list_indices_count.to!string~"\n";
1117 		}
1118 		printTitle("structs");
1119 		foreach(id, ref a ; structsPtr[0..headerPtr.struct_count])
1120 			ret ~= id.to!string.rightJustify(4)~" >"
1121 				~"  id="~a.id.to!string
1122 				~"  dodo="~a.data_or_data_offset.to!string
1123 				~"  fc="~a.field_count.to!string
1124 				~"\n";
1125 
1126 		printTitle("fields");
1127 		foreach(id, ref a ; fieldsPtr[0..headerPtr.field_count])
1128 			ret ~= id.to!string.rightJustify(4)~" >"
1129 				~"  type="~a.type.to!string
1130 				~"  lbl="~a.label_index.to!string
1131 				~"  dodo="~a.data_or_data_offset.to!string
1132 				~"\n";
1133 
1134 		printTitle("labels");
1135 		foreach(id, ref a ; labelsPtr[0..headerPtr.label_count])
1136 			ret ~= id.to!string.rightJustify(4)~" > "~a.to!string~"\n";
1137 
1138 		printTitle("field data");
1139 		ret ~= dumpByteArray(cast(ubyte[])fieldDatasPtr[0..headerPtr.field_data_count]);
1140 
1141 		printTitle("field indices");
1142 		ret ~= dumpByteArray(cast(ubyte[])fieldIndicesPtr[0..headerPtr.field_indices_count]);
1143 
1144 		printTitle("list indices");
1145 		ret ~= dumpByteArray(cast(ubyte[])listIndicesPtr[0..headerPtr.list_indices_count]);
1146 
1147 		return ret;
1148 	}
1149 
1150 	version(gff_verbose_parse) string gff_verbose_rtIndent;
1151 }
1152 
1153 private struct GffRawSerializer{
1154 	GffRawParser.RawHeader   header;
1155 	GffRawParser.RawStruct[] structs;
1156 	GffRawParser.RawField[]  fields;
1157 	GffRawParser.RawLabel[]  labels;
1158 	ubyte[]     fieldDatas;
1159 	ubyte[]     fieldIndices;
1160 	ubyte[]     listIndices;
1161 
1162 	uint32_t[string]  knownLabels;
1163 	version(gff_verbose_ser) string gff_verbose_rtIndent;
1164 
1165 	uint32_t registerStruct(in GffStruct gffStruct){
1166 		immutable createdStructIndex = cast(uint32_t)structs.length;
1167 		structs ~= GffRawParser.RawStruct();
1168 
1169 		immutable fieldCount = cast(uint32_t)gffStruct.length;
1170 		structs[createdStructIndex].id = gffStruct.id;
1171 		structs[createdStructIndex].field_count = fieldCount;
1172 
1173 
1174 		version(gff_verbose_ser){
1175 			stderr.writeln(gff_verbose_rtIndent,
1176 				"Registering struct id=",createdStructIndex,
1177 				"(type=",structs[createdStructIndex].type,", fields_count=",structs[createdStructIndex].field_count,")");
1178 			gff_verbose_rtIndent ~= "│ ";
1179 		}
1180 
1181 		if(fieldCount == 1){
1182 			//index in field array
1183 			auto child = &gffStruct.byKeyValue[0];
1184 			immutable fieldId = registerField(child.key, child.value);
1185 			structs[createdStructIndex].data_or_data_offset = fieldId;
1186 		}
1187 		else if(fieldCount>1){
1188 			//byte offset in field indices array
1189 			immutable fieldIndicesIndex = cast(uint32_t)fieldIndices.length;
1190 			structs[createdStructIndex].data_or_data_offset = fieldIndicesIndex;
1191 
1192 			fieldIndices.length += uint32_t.sizeof * fieldCount;
1193 			foreach(i, ref kv ; gffStruct.byKeyValue){
1194 
1195 				immutable fieldId = registerField(kv.key, kv.value);
1196 
1197 				immutable offset = fieldIndicesIndex + i * uint32_t.sizeof;
1198 				fieldIndices[offset..offset+uint32_t.sizeof] = cast(ubyte[])(cast(uint32_t*)&fieldId)[0..1];
1199 			}
1200 		}
1201 		else{
1202 			structs[createdStructIndex].data_or_data_offset = -1;
1203 		}
1204 
1205 		version(gff_verbose_ser) gff_verbose_rtIndent = gff_verbose_rtIndent[0..$-4];
1206 		return createdStructIndex;
1207 	}
1208 
1209 	uint32_t registerField(in string label, in GffValue value){
1210 		immutable createdFieldIndex = cast(uint32_t)fields.length;
1211 		fields ~= GffRawParser.RawField(value.type);
1212 
1213 		version(gff_verbose_ser){
1214 			stderr.writefln("%sRegistering field id=%d: %s %s = %s",
1215 				gff_verbose_rtIndent, createdFieldIndex, value.type, label, value
1216 			);
1217 			gff_verbose_rtIndent ~= "│ ";
1218 		}
1219 
1220 		assert(label.length <= 16, "Label too long");//TODO: Throw exception on GffNode.label set
1221 
1222 		if(auto i = (label in knownLabels)){
1223 			fields[createdFieldIndex].label_index = *i;
1224 		}
1225 		else{
1226 			fields[createdFieldIndex].label_index = cast(uint32_t)labels.length;
1227 			knownLabels[label] = cast(uint32_t)labels.length;
1228 			labels ~= GffRawParser.RawLabel(label.stringToCharArray!(char[16]));
1229 		}
1230 
1231 		final switch(value.type) with(GffType){
1232 			case Invalid: assert(0, "type has not been set");
1233 
1234 			//cast is ok because all those types are <= 32bit
1235 			case Byte:  fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffByte(); break;
1236 			case Char:  fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffChar(); break;
1237 			case Word:  fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffWord(); break;
1238 			case Short: fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffShort(); break;
1239 			case DWord: fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffDWord(); break;
1240 			case Int:   fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffInt(); break;
1241 			case Float: fields[createdFieldIndex].data_or_data_offset = *cast(uint32_t*)&value.get!GffFloat(); break;
1242 
1243 			case DWord64:
1244 			case Int64:
1245 			case Double:
1246 				fields[createdFieldIndex].data_or_data_offset = cast(uint32_t)fieldDatas.length;
1247 				switch(value.type){
1248 					case DWord64: fieldDatas ~= cast(ubyte[])(&value.get!GffDWord64())[0..1].dup; break;
1249 					case Int64:   fieldDatas ~= cast(ubyte[])(&value.get!GffInt64())[0..1].dup; break;
1250 					case Double:  fieldDatas ~= cast(ubyte[])(&value.get!GffDouble())[0..1].dup; break;
1251 					default: assert(0);
1252 				}
1253 				break;
1254 
1255 			case String:
1256 				immutable strLen = cast(uint32_t)value.get!GffString.length;
1257 
1258 				fields[createdFieldIndex].data_or_data_offset = cast(uint32_t)fieldDatas.length;
1259 				fieldDatas ~= cast(ubyte[])(&strLen)[0..1].dup;
1260 				fieldDatas ~= (cast(ubyte*)value.get!GffString.ptr)[0..strLen].dup;
1261 				break;
1262 			case ResRef:
1263 				assert(value.get!GffResRef.length <= 32, "Resref too long (max length: 32 characters)");
1264 				immutable strLen = cast(uint8_t)value.get!GffResRef.length;
1265 
1266 				fields[createdFieldIndex].data_or_data_offset = cast(uint32_t)fieldDatas.length;
1267 				fieldDatas ~= cast(ubyte[])(&strLen)[0..1].dup;
1268 				fieldDatas ~= (cast(ubyte*)value.get!GffResRef.ptr)[0..strLen].dup;
1269 				break;
1270 			case LocString:
1271 				immutable fieldDataIndex = fieldDatas.length;
1272 				fields[createdFieldIndex].data_or_data_offset = cast(uint32_t)fieldDataIndex;
1273 
1274 				//total size
1275 				fieldDatas ~= [0,0,0,0];//uint32_t
1276 
1277 				immutable strref = cast(uint32_t)value.get!GffLocString.strref;
1278 				fieldDatas ~= cast(ubyte[])(&strref)[0..1].dup;
1279 
1280 				immutable strcount = cast(uint32_t)value.get!GffLocString.strings.length;
1281 				fieldDatas ~= cast(ubyte[])(&strcount)[0..1].dup;
1282 
1283 				import std.algorithm: sort;
1284 				import std.array: array;
1285 				foreach(locstr ; value.get!GffLocString.strings.byKeyValue.array.sort!((a,b)=>a.key<b.key)){
1286 					immutable key = cast(int32_t)locstr.key;
1287 					fieldDatas ~= cast(ubyte[])(&key)[0..1].dup;//string id
1288 
1289 					immutable length = cast(int32_t)locstr.value.length;
1290 					fieldDatas ~= cast(ubyte[])(&length)[0..1].dup;
1291 
1292 					fieldDatas ~= cast(ubyte[])locstr.value.ptr[0..length].dup;
1293 				}
1294 
1295 				//total size
1296 				immutable totalSize = cast(uint32_t)(fieldDatas.length-fieldDataIndex) - 4;//totalSize does not count first 4 bytes
1297 				fieldDatas[fieldDataIndex..fieldDataIndex+4] = cast(ubyte[])(&totalSize)[0..1].dup;
1298 				break;
1299 			case Void:
1300 				auto dataLength = cast(uint32_t)value.get!GffVoid.length;
1301 				fields[createdFieldIndex].data_or_data_offset = cast(uint32_t)fieldDatas.length;
1302 				fieldDatas ~= cast(ubyte[])(&dataLength)[0..1];
1303 				fieldDatas ~= value.get!GffVoid;
1304 				break;
1305 			case Struct:
1306 				immutable structId = registerStruct(value.get!GffStruct);
1307 				fields[createdFieldIndex].data_or_data_offset = structId;
1308 				break;
1309 			case List:
1310 				immutable createdListOffset = cast(uint32_t)listIndices.length;
1311 				fields[createdFieldIndex].data_or_data_offset = createdListOffset;
1312 
1313 				uint32_t listLength = cast(uint32_t)value.get!GffList.length;
1314 				listIndices ~= cast(ubyte[])(&listLength)[0..1];
1315 				listIndices.length += listLength * uint32_t.sizeof;
1316 				if(value.get!GffList !is null){
1317 					foreach(i, ref field ; value.get!GffList){
1318 						immutable offset = createdListOffset+uint32_t.sizeof*(i+1);
1319 
1320 						uint32_t structIndex = registerStruct(field);
1321 						listIndices[offset..offset+uint32_t.sizeof] = cast(ubyte[])(&structIndex)[0..1];
1322 					}
1323 
1324 				}
1325 				break;
1326 		}
1327 		version(gff_verbose_ser) gff_verbose_rtIndent = gff_verbose_rtIndent[0..$-4];
1328 		return createdFieldIndex;
1329 	}
1330 
1331 	ubyte[] serialize(Gff gff){
1332 		registerStruct(gff.root);
1333 
1334 		assert(gff.fileType.length <= 4);
1335 		header.file_type = "    ";
1336 		header.file_type[0..gff.fileType.length] = gff.fileType.dup;
1337 
1338 		assert(gff.fileVersion.length <= 4);
1339 		header.file_version = "    ";
1340 		header.file_version[0..gff.fileVersion.length] = gff.fileVersion.dup;
1341 
1342 		uint32_t offset = cast(uint32_t)GffRawParser.RawHeader.sizeof;
1343 
1344 		header.struct_offset = offset;
1345 		header.struct_count = cast(uint32_t)structs.length;
1346 		offset += GffRawParser.RawStruct.sizeof * structs.length;
1347 
1348 		header.field_offset = offset;
1349 		header.field_count = cast(uint32_t)fields.length;
1350 		offset += GffRawParser.RawField.sizeof * fields.length;
1351 
1352 		header.label_offset = offset;
1353 		header.label_count = cast(uint32_t)labels.length;
1354 		offset += GffRawParser.RawLabel.sizeof * labels.length;
1355 
1356 		header.field_data_offset = offset;
1357 		header.field_data_count = cast(uint32_t)fieldDatas.length;
1358 		offset += fieldDatas.length;
1359 
1360 		header.field_indices_offset = offset;
1361 		header.field_indices_count = cast(uint32_t)fieldIndices.length;
1362 		offset += fieldIndices.length;
1363 
1364 		header.list_indices_offset = offset;
1365 		header.list_indices_count = cast(uint32_t)listIndices.length;
1366 		offset += listIndices.length;
1367 
1368 
1369 		version(unittest) auto offsetCheck = 0;
1370 		ubyte[] data;
1371 		data.reserve(offset);
1372 		data ~= cast(ubyte[])(&header)[0..1];
1373 		version(unittest) offsetCheck += GffRawParser.RawHeader.sizeof;
1374 		version(unittest) assert(data.length == offsetCheck);
1375 		data ~= cast(ubyte[])structs;
1376 		version(unittest) offsetCheck += structs.length * GffRawParser.RawStruct.sizeof;
1377 		version(unittest) assert(data.length == offsetCheck);
1378 		data ~= cast(ubyte[])fields;
1379 		version(unittest) offsetCheck += fields.length * GffRawParser.RawStruct.sizeof;
1380 		version(unittest) assert(data.length == offsetCheck);
1381 		data ~= cast(ubyte[])labels;
1382 		version(unittest) offsetCheck += labels.length * GffRawParser.RawLabel.sizeof;
1383 		version(unittest) assert(data.length == offsetCheck);
1384 		data ~= cast(ubyte[])fieldDatas;
1385 		version(unittest) offsetCheck += fieldDatas.length;
1386 		version(unittest) assert(data.length == offsetCheck);
1387 		data ~= fieldIndices;
1388 		version(unittest) offsetCheck += fieldIndices.length;
1389 		version(unittest) assert(data.length == offsetCheck);
1390 		data ~= listIndices;
1391 		version(unittest) offsetCheck += listIndices.length;
1392 		version(unittest) assert(data.length == offsetCheck);
1393 
1394 		assert(data.length == offset);
1395 		return data;
1396 	}
1397 }
1398 
1399 
1400 
1401 unittest{
1402 	import std.file : read;
1403 
1404 	immutable krogarDataOrig = cast(immutable ubyte[])import("krogar.bic");
1405 	auto gff = new Gff(krogarDataOrig);
1406 
1407 	//Parsing checks
1408 	assert(gff.fileType == "BIC");
1409 	assert(gff.fileVersion == "V3.2");
1410 	//assert(gff.fileVersion)
1411 	assert(gff["IsPC"].get!GffByte == true);
1412 	assert(gff["RefSaveThrow"].get!GffChar == 13);
1413 	assert(gff["SoundSetFile"].get!GffWord == 363);
1414 	assert(gff["HitPoints"].get!GffShort == 320);
1415 	assert(gff["Gold"].get!GffDWord == 6400);
1416 	assert(gff["Age"].get!GffInt == 50);
1417 	//assert(gff[""].get!GffDWord64 == );
1418 	//assert(gff[""].get!GffInt64 == );
1419 	assert(gff["XpMod"].get!GffFloat == 1);
1420 	//assert(gff[""].get!GffDouble == );
1421 	assert(gff["Deity"].get!GffString    == "Gorm Gulthyn");
1422 	assert(gff["ScriptHeartbeat"].get!GffResRef       == "gb_player_heart");
1423 	assert(gff["FirstName"].get!GffLocString.strref == -1);
1424 	assert(gff["FirstName"].get!GffLocString.strings[0] == "Krogar");
1425 	assert(gff["FirstName"].to!string == "Krogar");
1426 	//assert(gff[""].get!GffVoid == );
1427 	assert(gff["Tint_Head"]["Tintable"]["Tint"]["1"]["b"].get!GffByte == 109);
1428 	assert(gff["ClassList"][0]["Class"].get!GffInt == 4);
1429 
1430 	assertThrown!GffTypeException(gff["IsPC"].get!GffInt);
1431 	assertThrown!GffTypeException(gff["ClassList"].get!GffStruct);
1432 
1433 	// Tintable appears two times in the gff
1434 	// Both must be stored but only the last one should be accessed by its key
1435 	assert(gff.byKeyValue[53].key == "Tintable");
1436 	assert(gff.byKeyValue[53].value["Tint"]["1"]["r"].get!GffByte == 255);
1437 	assert(gff.byKeyValue[188].key == "Tintable");
1438 	assert(gff.byKeyValue[188].value["Tint"]["1"]["r"].get!GffByte == 253);
1439 	assert(gff["Tintable"]["Tint"]["1"]["r"].get!GffByte == 253);
1440 
1441 	//Perfect Serialization
1442 	auto krogarDataSerialized = gff.serialize();
1443 	auto gffSerialized = new Gff(krogarDataSerialized);
1444 
1445 	assert(gff.toPrettyString() == gffSerialized.toPrettyString(), "Serialization data mismatch");
1446 	assert(krogarDataOrig == krogarDataSerialized, "Serialization not byte perfect");
1447 
1448 	////Dup
1449 	//auto gffRoot2 = gff.root.dup;
1450 	//assert(gffRoot2 == gff.root);
1451 
1452 	assertThrown!GffValueSetException(gff.fileType = "FILETYPE");
1453 	gff.fileType = "A C";
1454 	assert(gff.fileType == "A C");
1455 	assertThrown!GffValueSetException(gff.fileVersion = "VERSION");
1456 	gff.fileVersion = "V42";
1457 	assert(gff.fileVersion == "V42");
1458 
1459 	auto data = cast(char[])gff.serialize();
1460 	assert(data[0..4]=="A C ");
1461 	assert(data[4..8]=="V42 ");
1462 
1463 
1464 	//Gff modifications
1465 	gff["Deity"] = "Crom";
1466 	assert(gff["Deity"].get!GffString == "Crom");
1467 
1468 	gff["NewValue"] = GffInt(42);
1469 	assert(gff["NewValue"].get!GffInt == 42);
1470 
1471 	assertThrown!Error(GffResRef("this is a bit longer than 32 characters"));
1472 	gff["ResRefExample"] = GffResRef("Hello");
1473 	assert(gff["ResRefExample"].get!GffResRef == "Hello");
1474 	gff["ResRefExample"].get!GffResRef = GffResRef("world");
1475 	assert(gff["ResRefExample"].to!string == "world");
1476 
1477 	gff["Equip_ItemList"][1] = GffStruct();
1478 	gff["Equip_ItemList"][1]["Hello"] = "world";
1479 	assert(gff["Equip_ItemList"][1]["Hello"].get!GffString == "world");
1480 	assertThrown!Error(gff["Equip_ItemList"][99] = GffStruct());
1481 
1482 
1483 
1484 	// JSON parsing /serialization
1485 	immutable dogeDataOrig = cast(immutable ubyte[])import("doge.utc");
1486 	auto dogeGff = new Gff(dogeDataOrig);
1487 
1488 	// duplication fixing serializations
1489 	auto dogeJson = dogeGff.toJson();
1490 	auto dogeFromJson = new Gff(dogeJson);
1491 	auto dogeFromJsonStr = new Gff(parseJSON(dogeJson.toString()));
1492 	assert(dogeFromJson.serialize() == dogeDataOrig);
1493 	assert(dogeFromJsonStr.serialize() == dogeDataOrig);
1494 }