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 }