fix(erf): align restype numbers with upstream neverwinter.nim (#69)
Closes #64. Five entries in `internal/erf/erf.go`'s restype table disagreed with upstream neverwinter.nim (`neverwinter/restype.nim`). `HAKResourceTypeForExtension` packs arbitrary source files, so a `.jpg` packed into a HAK today is written as restype 2076, which the game reads as `tml`. | ext | was | now | |-----|-----|-----| | gff | 2039 (upstream `bte`) | 2037 | | ltr | 2067 (upstream `bak`) | 2036 | | jpg | 2076 (upstream `tml`) | 2081 | | dfa | 2045, name typo | `dft`, 2045 (number was already right) | | mdb | 2070 (upstream `xbc`) | removed | Upstream registers no restype for `mdb`, so there is no correct number to give it, and leaving it squatting on `xbc` silently mislabels the resource. It is dropped from `AssetExtensions` too, which turns a `.mdb` source into a loud "unsupported extension" build error instead of a corrupt HAK entry. **This is the one judgment call the issue left open** — if `mdb` should instead stay as a deliberate local addition like `lyt`/`vis`/`gr2`, say so and I will move it to a free number in the 0x0BB8+ local range. No asset in the current corpus uses any of these paths, so nothing in shipped content changes. The six local additions upstream does not have (`lyt` 3000, `vis` 3001, `mdx` 3008, `wlk` 3020, `xml` 3021, `gr2` 4003) are left alone and now carry a comment saying they are deliberate. ## Root cause The `init()` consistency guard only panicked when a number was missing from the reverse map. Its `if canonicalExt == ext { continue }` branch did nothing when the two maps disagreed, so a mismatch was never caught. The guard now panics on disagreement and also checks the reverse direction. ## Tests - `TestRestypeTableMatchesUpstream` pins every number shared with upstream, and asserts the four numbers upstream reserves for other extensions (2039 `bte`, 2067 `bak`, 2070 `xbc`, 2076 `tml`) stay unclaimed. - `TestRestypeTablesAreMutualInverses` is the check `init()` is meant to enforce. `make check` passes.Reviewed-on: #69 Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #69.
This commit is contained in:
+28
-18
@@ -67,6 +67,19 @@ type resourceEntry struct {
|
|||||||
Size uint32
|
Size uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extensionTypes and typeExtensions must stay exact inverses of each other;
|
||||||
|
// init() panics if they drift apart.
|
||||||
|
//
|
||||||
|
// Numbers below 0x0BB8 follow upstream neverwinter.nim
|
||||||
|
// (neverwinter/restype.nim). The 0x0BB8 and up entries are lyt/vis/mdx: real
|
||||||
|
// Aurora archive types that NWN1 ships in its own data/*.bif but upstream
|
||||||
|
// happens not to register. xoreos corroborates those three numbers
|
||||||
|
// (src/aurora/types.h).
|
||||||
|
//
|
||||||
|
// This table is NWN:EE only. Do not add NWN2 formats (mdb, gr2, wlk, xml):
|
||||||
|
// NWN:EE either gives that number to something else (2070 is xbc here and mdb
|
||||||
|
// in NWN2) or has no number for it at all, so packing one into a HAK writes a
|
||||||
|
// resource the game misreads. See the reserved list in erf_test.go.
|
||||||
var extensionTypes = map[string]uint16{
|
var extensionTypes = map[string]uint16{
|
||||||
"res": 0x0000,
|
"res": 0x0000,
|
||||||
"bmp": 0x0001,
|
"bmp": 0x0001,
|
||||||
@@ -96,12 +109,13 @@ var extensionTypes = map[string]uint16{
|
|||||||
"utt": 0x07F0,
|
"utt": 0x07F0,
|
||||||
"dds": 0x07F1,
|
"dds": 0x07F1,
|
||||||
"uts": 0x07F3,
|
"uts": 0x07F3,
|
||||||
|
"ltr": 0x07F4,
|
||||||
|
"gff": 0x07F5,
|
||||||
"fac": 0x07F6,
|
"fac": 0x07F6,
|
||||||
"gff": 0x07F7,
|
|
||||||
"ute": 0x07F8,
|
"ute": 0x07F8,
|
||||||
"utd": 0x07FA,
|
"utd": 0x07FA,
|
||||||
"utp": 0x07FC,
|
"utp": 0x07FC,
|
||||||
"dfa": 0x07FD,
|
"dft": 0x07FD,
|
||||||
"gic": 0x07FE,
|
"gic": 0x07FE,
|
||||||
"gui": 0x07FF,
|
"gui": 0x07FF,
|
||||||
"utm": 0x0803,
|
"utm": 0x0803,
|
||||||
@@ -117,20 +131,15 @@ var extensionTypes = map[string]uint16{
|
|||||||
"ndb": 0x0810,
|
"ndb": 0x0810,
|
||||||
"ptm": 0x0811,
|
"ptm": 0x0811,
|
||||||
"ptt": 0x0812,
|
"ptt": 0x0812,
|
||||||
"ltr": 0x0813,
|
|
||||||
"shd": 0x0815,
|
"shd": 0x0815,
|
||||||
"mdb": 0x0816,
|
|
||||||
"mtr": 0x0818,
|
"mtr": 0x0818,
|
||||||
"jpg": 0x081C,
|
|
||||||
"lod": 0x081E,
|
"lod": 0x081E,
|
||||||
"gif": 0x081F,
|
"gif": 0x081F,
|
||||||
"png": 0x0820,
|
"png": 0x0820,
|
||||||
|
"jpg": 0x0821,
|
||||||
"lyt": 0x0BB8,
|
"lyt": 0x0BB8,
|
||||||
"vis": 0x0BB9,
|
"vis": 0x0BB9,
|
||||||
"mdx": 0x0BC0,
|
"mdx": 0x0BC0,
|
||||||
"wlk": 0x0BCC,
|
|
||||||
"xml": 0x0BCD,
|
|
||||||
"gr2": 0x0FA3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeExtensions = map[uint16]string{
|
var typeExtensions = map[uint16]string{
|
||||||
@@ -162,12 +171,13 @@ var typeExtensions = map[uint16]string{
|
|||||||
0x07F0: "utt",
|
0x07F0: "utt",
|
||||||
0x07F1: "dds",
|
0x07F1: "dds",
|
||||||
0x07F3: "uts",
|
0x07F3: "uts",
|
||||||
|
0x07F4: "ltr",
|
||||||
|
0x07F5: "gff",
|
||||||
0x07F6: "fac",
|
0x07F6: "fac",
|
||||||
0x07F7: "gff",
|
|
||||||
0x07F8: "ute",
|
0x07F8: "ute",
|
||||||
0x07FA: "utd",
|
0x07FA: "utd",
|
||||||
0x07FC: "utp",
|
0x07FC: "utp",
|
||||||
0x07FD: "dfa",
|
0x07FD: "dft",
|
||||||
0x07FE: "gic",
|
0x07FE: "gic",
|
||||||
0x07FF: "gui",
|
0x07FF: "gui",
|
||||||
0x0803: "utm",
|
0x0803: "utm",
|
||||||
@@ -183,20 +193,15 @@ var typeExtensions = map[uint16]string{
|
|||||||
0x0810: "ndb",
|
0x0810: "ndb",
|
||||||
0x0811: "ptm",
|
0x0811: "ptm",
|
||||||
0x0812: "ptt",
|
0x0812: "ptt",
|
||||||
0x0813: "ltr",
|
|
||||||
0x0815: "shd",
|
0x0815: "shd",
|
||||||
0x0816: "mdb",
|
|
||||||
0x0818: "mtr",
|
0x0818: "mtr",
|
||||||
0x081C: "jpg",
|
|
||||||
0x081E: "lod",
|
0x081E: "lod",
|
||||||
0x081F: "gif",
|
0x081F: "gif",
|
||||||
0x0820: "png",
|
0x0820: "png",
|
||||||
|
0x0821: "jpg",
|
||||||
0x0BB8: "lyt",
|
0x0BB8: "lyt",
|
||||||
0x0BB9: "vis",
|
0x0BB9: "vis",
|
||||||
0x0BC0: "mdx",
|
0x0BC0: "mdx",
|
||||||
0x0BCC: "wlk",
|
|
||||||
0x0BCD: "xml",
|
|
||||||
0x0FA3: "gr2",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -205,8 +210,13 @@ func init() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("missing canonical extension for resource type 0x%04X", resourceType))
|
panic(fmt.Sprintf("missing canonical extension for resource type 0x%04X", resourceType))
|
||||||
}
|
}
|
||||||
if canonicalExt == ext {
|
if canonicalExt != ext {
|
||||||
continue
|
panic(fmt.Sprintf("resource type 0x%04X is %q in extensionTypes but %q in typeExtensions", resourceType, ext, canonicalExt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for resourceType, ext := range typeExtensions {
|
||||||
|
if registered, ok := extensionTypes[ext]; !ok || registered != resourceType {
|
||||||
|
panic(fmt.Sprintf("extension %q is 0x%04X in typeExtensions but 0x%04X in extensionTypes (registered=%v)", ext, resourceType, registered, ok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,14 +134,10 @@ func TestExtensionMappingsSupportModernAssetTypes(t *testing.T) {
|
|||||||
"mtr": 0x0818,
|
"mtr": 0x0818,
|
||||||
"shd": 0x0815,
|
"shd": 0x0815,
|
||||||
"txi": 0x07E6,
|
"txi": 0x07E6,
|
||||||
"jpg": 0x081C,
|
"jpg": 0x0821,
|
||||||
"mdb": 0x0816,
|
|
||||||
"lyt": 0x0BB8,
|
"lyt": 0x0BB8,
|
||||||
"vis": 0x0BB9,
|
"vis": 0x0BB9,
|
||||||
"mdx": 0x0BC0,
|
"mdx": 0x0BC0,
|
||||||
"xml": 0x0BCD,
|
|
||||||
"wlk": 0x0BCC,
|
|
||||||
"gr2": 0x0FA3,
|
|
||||||
}
|
}
|
||||||
for ext, wantType := range cases {
|
for ext, wantType := range cases {
|
||||||
gotType, ok := ResourceTypeForExtension(ext)
|
gotType, ok := ResourceTypeForExtension(ext)
|
||||||
@@ -191,3 +187,71 @@ func TestExtensionMappingsSupportAllUTBlueprintTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRestypeTableMatchesUpstream pins the restype numbers Crucible shares with
|
||||||
|
// neverwinter.nim's restype.nim. The values below are upstream's; a mismatch
|
||||||
|
// means a HAK we write is mislabelled for the game.
|
||||||
|
func TestRestypeTableMatchesUpstream(t *testing.T) {
|
||||||
|
upstream := map[string]uint16{
|
||||||
|
"res": 0, "bmp": 1, "mve": 2, "tga": 3, "wav": 4, "plt": 6, "ini": 7,
|
||||||
|
"bmu": 8, "txt": 10, "mdl": 2002, "nss": 2009, "ncs": 2010, "are": 2012,
|
||||||
|
"set": 2013, "ifo": 2014, "bic": 2015, "wok": 2016, "2da": 2017,
|
||||||
|
"tlk": 2018, "txi": 2022, "git": 2023, "uti": 2025, "utc": 2027,
|
||||||
|
"dlg": 2029, "itp": 2030, "utt": 2032, "dds": 2033, "uts": 2035,
|
||||||
|
"ltr": 2036, "gff": 2037, "fac": 2038, "ute": 2040, "utd": 2042,
|
||||||
|
"utp": 2044, "dft": 2045, "gic": 2046, "gui": 2047, "utm": 2051,
|
||||||
|
"dwk": 2052, "pwk": 2053, "utg": 2055, "jrl": 2056, "utw": 2058,
|
||||||
|
"ssf": 2060, "hak": 2061, "nwm": 2062, "bik": 2063, "ndb": 2064,
|
||||||
|
"ptm": 2065, "ptt": 2066, "shd": 2069, "mtr": 2072, "lod": 2078,
|
||||||
|
"gif": 2079, "png": 2080, "jpg": 2081,
|
||||||
|
}
|
||||||
|
for ext, want := range upstream {
|
||||||
|
got, ok := ResourceTypeForExtension(ext)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: not registered, upstream has %d", ext, want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("%s: registered as %d, upstream has %d", ext, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extensions upstream registers that Crucible must not reuse for anything else.
|
||||||
|
reserved := map[uint16]string{2039: "bte", 2067: "bak", 2070: "xbc", 2076: "tml"}
|
||||||
|
for number, upstreamExt := range reserved {
|
||||||
|
if ext, ok := ExtensionForResourceType(number); ok {
|
||||||
|
t.Errorf("%d: registered as %s, upstream reserves it for %s", number, ext, upstreamExt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNWN2FormatsAreNotRegistered keeps NWN2 file formats out of an NWN:EE
|
||||||
|
// table. mdb and gr2 have Aurora numbers that mean something else (or nothing)
|
||||||
|
// in NWN:EE; wlk and xml have no archive number in any Aurora game, so the
|
||||||
|
// values Crucible used for them were invented. Packing any of these into a HAK
|
||||||
|
// writes a resource the game misreads.
|
||||||
|
func TestNWN2FormatsAreNotRegistered(t *testing.T) {
|
||||||
|
for _, ext := range []string{"mdb", "gr2", "wlk", "xml"} {
|
||||||
|
if number, ok := ResourceTypeForExtension(ext); ok {
|
||||||
|
t.Errorf("%s is an NWN2 format but is registered as 0x%04X", ext, number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRestypeTablesAreMutualInverses is the check init() is meant to enforce.
|
||||||
|
func TestRestypeTablesAreMutualInverses(t *testing.T) {
|
||||||
|
for ext, number := range extensionTypes {
|
||||||
|
canonical, ok := typeExtensions[number]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: number 0x%04X missing from typeExtensions", ext, number)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if canonical != ext {
|
||||||
|
t.Errorf("%s: maps to 0x%04X, which maps back to %s", ext, number, canonical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for number, ext := range typeExtensions {
|
||||||
|
if got, ok := extensionTypes[ext]; !ok || got != number {
|
||||||
|
t.Errorf("0x%04X: maps to %s, which maps back to 0x%04X (ok=%v)", number, ext, got, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ var SourceExtensions = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AssetExtensions = []string{
|
var AssetExtensions = []string{
|
||||||
".2da", ".bik", ".bmp", ".bmu", ".dds", ".dwk", ".gr2", ".itp", ".jpg", ".lod", ".lyt", ".mdb", ".mdl", ".mdx", ".mtr", ".plt", ".png", ".pwk", ".set", ".shd", ".tga", ".txi", ".uti", ".vis", ".wav", ".wlk", ".wok", ".xml",
|
".2da", ".bik", ".bmp", ".bmu", ".dds", ".dwk", ".itp", ".jpg", ".lod", ".lyt", ".mdl", ".mdx", ".mtr", ".plt", ".png", ".pwk", ".set", ".shd", ".tga", ".txi", ".uti", ".vis", ".wav", ".wok",
|
||||||
}
|
}
|
||||||
|
|
||||||
var BuiltinScriptPrefixes = []string{
|
var BuiltinScriptPrefixes = []string{
|
||||||
|
|||||||
Reference in New Issue
Block a user