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:
2026-07-28 22:18:03 +00:00
committed by archvillainette
parent 00f467b932
commit 2f4685e470
3 changed files with 98 additions and 24 deletions
+28 -18
View File
@@ -67,6 +67,19 @@ type resourceEntry struct {
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{
"res": 0x0000,
"bmp": 0x0001,
@@ -96,12 +109,13 @@ var extensionTypes = map[string]uint16{
"utt": 0x07F0,
"dds": 0x07F1,
"uts": 0x07F3,
"ltr": 0x07F4,
"gff": 0x07F5,
"fac": 0x07F6,
"gff": 0x07F7,
"ute": 0x07F8,
"utd": 0x07FA,
"utp": 0x07FC,
"dfa": 0x07FD,
"dft": 0x07FD,
"gic": 0x07FE,
"gui": 0x07FF,
"utm": 0x0803,
@@ -117,20 +131,15 @@ var extensionTypes = map[string]uint16{
"ndb": 0x0810,
"ptm": 0x0811,
"ptt": 0x0812,
"ltr": 0x0813,
"shd": 0x0815,
"mdb": 0x0816,
"mtr": 0x0818,
"jpg": 0x081C,
"lod": 0x081E,
"gif": 0x081F,
"png": 0x0820,
"jpg": 0x0821,
"lyt": 0x0BB8,
"vis": 0x0BB9,
"mdx": 0x0BC0,
"wlk": 0x0BCC,
"xml": 0x0BCD,
"gr2": 0x0FA3,
}
var typeExtensions = map[uint16]string{
@@ -162,12 +171,13 @@ var typeExtensions = map[uint16]string{
0x07F0: "utt",
0x07F1: "dds",
0x07F3: "uts",
0x07F4: "ltr",
0x07F5: "gff",
0x07F6: "fac",
0x07F7: "gff",
0x07F8: "ute",
0x07FA: "utd",
0x07FC: "utp",
0x07FD: "dfa",
0x07FD: "dft",
0x07FE: "gic",
0x07FF: "gui",
0x0803: "utm",
@@ -183,20 +193,15 @@ var typeExtensions = map[uint16]string{
0x0810: "ndb",
0x0811: "ptm",
0x0812: "ptt",
0x0813: "ltr",
0x0815: "shd",
0x0816: "mdb",
0x0818: "mtr",
0x081C: "jpg",
0x081E: "lod",
0x081F: "gif",
0x0820: "png",
0x0821: "jpg",
0x0BB8: "lyt",
0x0BB9: "vis",
0x0BC0: "mdx",
0x0BCC: "wlk",
0x0BCD: "xml",
0x0FA3: "gr2",
}
func init() {
@@ -205,8 +210,13 @@ func init() {
if !ok {
panic(fmt.Sprintf("missing canonical extension for resource type 0x%04X", resourceType))
}
if canonicalExt == ext {
continue
if canonicalExt != ext {
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))
}
}
}