Skyrim Mod talk:Mod File Format/DLBR
QNAM loader issues[edit]
Disassembly and code analysis indicates that the loader for the QNAM subrecord is broken. Bethesda accidentally had the game convert the QNAM form ID from local to global (i.e. from relative to the master list of the record's containing file, to relative to the game's full load order) twice. This means that it's not safe for mods to add dialogue branches to quests outside of Skyrim.esm, Update.esm, and their own content.
For an example of how things can go wrong, let's look at this hypothetical load order, where "TestFile.esp" is the file supplying a DLBR record:
Relationship | Global | Local | Filename |
---|---|---|---|
Master | 00 | 00 | Skyrim.esm |
Master | 01 | 01 | Update.esm |
Master | 02 | 02 | Dawnguard.esm |
Not Master | 03 | MyCoolFile.esp | |
Not Master | 04 | SomethingWeird.esp | |
Not Master | 05 | RandomMod.esp | |
Master | 06 | 03 | ExampleModMaster.esp |
Self | 07 | 04 | TestFile.esp |
The "global" values are the load order prefixes that these files have within the global load order. The "local" values are the load order prefixes that these files have within TestFile.esp itself.
Suppose that our hypothetical DLBR's owning quest is in Skyrim.esm. The game will resolve the load order prefix from the local value (00) to the global value (00). It will then attempt to convert the already-global value to a global value again, but since Skyrim.esm occupies the same prefix both within TestFile.esp's master list and within the game's full load order, this is harmless, and the DLBR/QNAM subrecord is loaded properly.
Now, suppose that the DLBR's owning quest is instead in ExampleModMaster.esp. The game will resolve the load order prefix from the local value (03) to the global value (06). The game will then attempt to resolve it again, but this time, we have a problem: 06 is out of bounds within TestFile's master list. So what happens? To answer that, let's take a look at the function that resolves form IDs:
void FixupFormIDForLoad(uint32_t& formID, TESFile* file) { if (formID && formID <= 0x7FF) return; if (!file) return; auto* source = file->GetFileByLoadPrefix(formID >> 0x18 + 1); // not sure what the +1 does here if (source) { uint8_t al = source->GetModIndex(); // get source's index in the game's load order formID = (al << 0x18) | (formID & 0x00FFFFFF); return; } // // If the form ID is out of bounds, treat it as if it came from (file). // uint8_t dl = file->GetModIndex(); formID = (dl << 0x18) | (formID & 0x00FFFFFF); }
This function takes as input the local form ID to resolve, and a pointer to the file that supplied this DLBR's containing record. We search that file's master list for the file that corresponds to the local form ID's load order prefix, and if one is found, then we use that file's current position within the game's load order. If no such file is found, which would generally only happen for an out-of-bounds form ID, then we force the form ID to fall within the file that supplied this DLBR's containing record.
So going back to our example, a DLBR/QNAM subrecord in TestFile.esp that refers to a quest in ExampleModMaster.esp will first be converted from 03 to 06, and then incorrectly converted a second time. Because 06 is out of bounds within TestFile.esp's master list, we place the form ID within TestFile.esp itself, and so the prefix is converted from 06 to 07.
For one last example scenario, suppose that the DLBR in TestFile.esp has an owning quest defined within TestFile.esp itself. The game will resolve the load order prefix from the local value (04) to the global value (07). It will then attempt to resolve it again, but because 07 is out of bounds within TestFile.esp's master list, we use TestFile.esp's load order prefix, 07.
What happens if the form ID is resolved incorrectly? Well, after the game resolves the form ID, it will then look for a quest with that form ID and set that as the DLBR's owning quest. This means that if the form ID is mangled and a quest happens to exist with the mangled form ID, then the DLBR will be incorrectly assigned to that quest; otherwise, the DLBR will not have any owning quest at all.
The conclusion to be drawn from this is that the most common scenarios all work, but uncommon ones go horribly wrong. Mods can override DLBR records within Skyrim.esm and Update.esm safely [1], and can add DLBR records to quests defined in Skyrim.esm and Update.esm safely; mods for Skyrim Special Edition can safely tamper with the DLCs for the same reason. Mods can add both DLBRs and QUSTs and have the former refer to the latter. Mods cannot, however, patch each other: if another mod defines new DLBRs and QUSTs and has the former refer to the latter, then overriding the former will break that relationship; and attempting to define a new DLBR whose owning quest belongs to another mod will fail as well.
[1] Provided Skyrim.esm and Update.esm are at the top of the file's master list and in the same order, which is always the case with official tools but which is not required by the file format.
DavidJCobb (talk) 01:39, 21 February 2021 (UTC)