Skip to content

Correct and extend BGSInventoryItem and inventory UI types - #65

Merged
qudix merged 4 commits into
libxse:mainfrom
hxef:hxef/findings
Sep 15, 2026
Merged

qudix merged 4 commits into
libxse:mainfrom
hxef:hxef/findings

Conversation

@hxef

@hxef hxef commented Sep 14, 2026

Copy link
Copy Markdown
  • ObjectRefHandle arguments taken by reference
  • Stack functors corrected, and the six missing ones added
  • InventoryUserUIInterfaceEntry stack indices widened to 16 bits
  • kLocationSpecRefs set to bit 28

FindAndWriteStackData and the InventoryItemDisplayData constructor expect a pointer to a handle, but they were passed the handle itself. The engine then used the handle's number as a memory address and read from the wrong place. Taking the handle as a const reference passes a pointer to it, as the engine expects.
An item's copies are grouped into stacks, and copies that differ, such as in mods or a custom name, go in separate stacks. To change a stack, the engine uses two small helper objects called functors. One finds the stack and the other changes it. FindAndWriteStackData runs both.

CheckExtraDataFunctor matched every stack, so it always found the first one. It now matches only the stack that has the extra data list it was given.

The WriteDataImpl comments said vtable slot 01. It is the only virtual function, so it is slot 00.

Adds the six missing functors: FindEquippedStackFunctor, HasExtraDataFunctor, IsUIEquivalentStackFunctor, ClearEquipFlagsFunctor, ModCountFunctor and SetFlagFunctor. Their members and sizes match the places the 1.11.240 engine creates them. IsUIEquivalentStackFunctor and the three write functors run the engine's own code through its vtable.
Each entry in an inventory menu lists the stacks it shows, by each stack's position in the item's stack list. One entry can cover several stacks. The engine stores each position in 2 bytes, but they were declared as 1 byte. So stackIndex[1] was read just 1 byte into the list, which is still inside the first position, and usually came out as 0. A position above 255 needs both of its bytes, so it came out wrong too.

The game's own code reads these numbers 2 bytes at a time, both in InventoryItemDisplayData's constructor and in the function that adds up an entry's item count. In the disassembly this shows up as word ptr reads, and a word is 2 bytes.
It was 1u < 28, which is 1, instead of 1u << 28.
@qudix
qudix merged commit 2cf5687 into libxse:main Sep 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants