Fix creative inventory: inject mod items before TabSpec page count calculation

- Inject items before Original_CreativeStaticCtor so TabSpec constructors
  include mod items when calculating m_staticItems and m_pages
- Add UpdateTabPageCounts() to recalculate TabSpec page counts after
  staticCtor as safety measure (handles different binary layouts)
- Resolve specs symbol for TabSpec array access
- Add diagnostic logging for ItemInstance vtable, shared_ptr, vector sizes
This commit is contained in:
Jacobwasbeast
2026-03-06 22:47:08 -06:00
parent 2280cb1192
commit 44fd4416e5
3 changed files with 125 additions and 4 deletions

View File

@@ -1,7 +1,9 @@
#include "CreativeInventory.h"
#include "SymbolResolver.h"
#include "PdbParser.h"
#include "LogUtil.h"
#include <cstring>
#include <algorithm>
namespace CreativeInventory
{
@@ -10,6 +12,7 @@ void* pCategoryGroups = nullptr;
void* pItemInstanceCtor = nullptr;
void* pSharedPtrCtor = nullptr;
void* pVectorPushBack = nullptr;
void* pSpecs = nullptr;
std::vector<PendingCreativeItem> s_pendingItems;
@@ -18,6 +21,27 @@ static const int SIZEOF_MSVC_VECTOR = 24;
static const int SIZEOF_MSVC_SHARED_PTR = 16;
static const int ITEMINSTANCE_ALLOC_SIZE = 256;
// TabSpec field offsets (MSVC x64 layout, derived from IUIScene_CreativeMenu.h)
// LPCWSTR m_icon @ 0 (8)
// int m_descriptionId @ 8 (4)
// int m_staticGroupsCount @ 12 (4)
// ECreative_Inventory_Groups* @ 16 (8)
// int m_dynamicGroupsCount @ 24 (4) + 4 pad
// ECreative_Inventory_Groups* @ 32 (8)
// int m_debugGroupsCount @ 40 (4) + 4 pad
// ECreative_Inventory_Groups* @ 48 (8)
// uint m_pages @ 56 (4)
// uint m_staticPerPage @ 60 (4)
// uint m_staticItems @ 64 (4)
// uint m_debugItems @ 68 (4)
static const int TABSPEC_STATIC_GROUPS_COUNT_OFF = 12;
static const int TABSPEC_STATIC_GROUPS_A_OFF = 16;
static const int TABSPEC_PAGES_OFF = 56;
static const int TABSPEC_STATIC_ITEMS_OFF = 64;
static const int TAB_COUNT = 8;
static const int COLUMNS = 10;
static const int ROWS = 5;
typedef void (__fastcall *ItemInstanceCtor_fn)(void* thisPtr, int id, int count, int auxValue);
typedef void (__fastcall *SharedPtrCtor_fn)(void* sharedPtrThis, void* rawItemPtr);
typedef void (__fastcall *VectorPushBackMove_fn)(void* vectorThis, void* sharedPtrRvalueRef);
@@ -43,6 +67,9 @@ bool ResolveSymbols(SymbolResolver& resolver)
"?push_back@?$vector@V?$shared_ptr@VItemInstance@@@std@@V?$allocator@V?$shared_ptr@VItemInstance@@@std@@@2@@std@@"
"QEAAX$$QEAV?$shared_ptr@VItemInstance@@@2@@Z");
pSpecs = resolver.Resolve(
"?specs@IUIScene_CreativeMenu@@1PEAPEAUTabSpec@1@EA");
if (pCategoryGroups) LogUtil::Log("[LegacyForge] categoryGroups @ %p", pCategoryGroups);
else LogUtil::Log("[LegacyForge] MISSING: categoryGroups");
@@ -55,9 +82,78 @@ bool ResolveSymbols(SymbolResolver& resolver)
if (pVectorPushBack) LogUtil::Log("[LegacyForge] vector::push_back @ %p", pVectorPushBack);
else LogUtil::Log("[LegacyForge] MISSING: vector<shared_ptr<II>>::push_back");
if (pSpecs) LogUtil::Log("[LegacyForge] specs @ %p", pSpecs);
else
{
LogUtil::Log("[LegacyForge] MISSING: specs (page counts won't be updated)");
PdbParser::DumpMatching("specs@IUIScene_CreativeMenu");
}
return pCategoryGroups && pItemInstanceCtor && pSharedPtrCtor && pVectorPushBack;
}
static size_t ReadVectorSize(char* vec)
{
char* first = *reinterpret_cast<char**>(vec);
char* last = *reinterpret_cast<char**>(vec + 8);
if (!first || last <= first) return 0;
return static_cast<size_t>((last - first) / SIZEOF_MSVC_SHARED_PTR);
}
void UpdateTabPageCounts()
{
if (!pSpecs || !pCategoryGroups)
{
LogUtil::Log("[LegacyForge] Cannot update tab page counts: specs=%p categoryGroups=%p",
pSpecs, pCategoryGroups);
return;
}
void** specsArray = *reinterpret_cast<void***>(pSpecs);
if (!specsArray)
{
LogUtil::Log("[LegacyForge] specs pointer is null, TabSpec array not yet allocated");
return;
}
char* groups = reinterpret_cast<char*>(pCategoryGroups);
for (int tabIdx = 0; tabIdx < TAB_COUNT; ++tabIdx)
{
char* tab = reinterpret_cast<char*>(specsArray[tabIdx]);
if (!tab) continue;
int staticGroupsCount = *reinterpret_cast<int*>(tab + TABSPEC_STATIC_GROUPS_COUNT_OFF);
int* staticGroupsA = *reinterpret_cast<int**>(tab + TABSPEC_STATIC_GROUPS_A_OFF);
if (!staticGroupsA || staticGroupsCount <= 0) continue;
unsigned int totalItems = 0;
for (int i = 0; i < staticGroupsCount; ++i)
{
int groupIdx = staticGroupsA[i];
if (groupIdx < 0 || groupIdx >= CREATIVE_GROUP_COUNT) continue;
totalItems += static_cast<unsigned int>(
ReadVectorSize(groups + groupIdx * SIZEOF_MSVC_VECTOR));
}
unsigned int oldItems = *reinterpret_cast<unsigned int*>(tab + TABSPEC_STATIC_ITEMS_OFF);
unsigned int oldPages = *reinterpret_cast<unsigned int*>(tab + TABSPEC_PAGES_OFF);
*reinterpret_cast<unsigned int*>(tab + TABSPEC_STATIC_ITEMS_OFF) = totalItems;
int totalRows = (totalItems + COLUMNS - 1) / COLUMNS;
int newPages = totalRows - ROWS + 1;
if (newPages < 1) newPages = 1;
*reinterpret_cast<unsigned int*>(tab + TABSPEC_PAGES_OFF) = static_cast<unsigned int>(newPages);
if (totalItems != oldItems)
{
LogUtil::Log("[LegacyForge] Tab %d: staticItems %u -> %u, pages %u -> %u",
tabIdx, oldItems, totalItems, oldPages, static_cast<unsigned int>(newPages));
}
}
}
void InjectItems()
{
if (!pCategoryGroups || !pItemInstanceCtor || !pSharedPtrCtor || !pVectorPushBack)
@@ -76,6 +172,8 @@ void InjectItems()
auto spCtorFn = reinterpret_cast<SharedPtrCtor_fn>(pSharedPtrCtor);
auto pushFn = reinterpret_cast<VectorPushBackMove_fn>(pVectorPushBack);
// categoryGroups is a static array of vectors (vector<...> categoryGroups[15]).
// pCategoryGroups is the address of the first vector; no dereference needed.
char* groups = reinterpret_cast<char*>(pCategoryGroups);
for (auto& item : s_pendingItems)
@@ -91,15 +189,29 @@ void InjectItems()
memset(rawItem, 0, ITEMINSTANCE_ALLOC_SIZE);
ctorFn(rawItem, item.itemId, item.count, item.auxValue);
// Verify ItemInstance vtable was set (first 8 bytes should be non-null)
void* vtable = *reinterpret_cast<void**>(rawItem);
LogUtil::Log("[LegacyForge] ItemInstance(%d,%d,%d) @ %p, vtable=%p",
item.itemId, item.count, item.auxValue, rawItem, vtable);
char spBuf[16];
memset(spBuf, 0, sizeof(spBuf));
spCtorFn(spBuf, rawItem);
// Log shared_ptr contents (ptr + control block)
void* spPtr = *reinterpret_cast<void**>(spBuf);
void* spCtrl = *reinterpret_cast<void**>(spBuf + 8);
LogUtil::Log("[LegacyForge] shared_ptr: ptr=%p ctrl=%p", spPtr, spCtrl);
char* vec = groups + item.groupIndex * SIZEOF_MSVC_VECTOR;
size_t sizeBefore = ReadVectorSize(vec);
pushFn(vec, spBuf);
LogUtil::Log("[LegacyForge] Injected item id=%d into creative group %d",
item.itemId, item.groupIndex);
size_t sizeAfter = ReadVectorSize(vec);
LogUtil::Log("[LegacyForge] Injected item id=%d into creative group %d "
"(vector @ %p, size: %zu -> %zu)",
item.itemId, item.groupIndex, vec, sizeBefore, sizeAfter);
}
LogUtil::Log("[LegacyForge] Injected %zu items into creative inventory", s_pendingItems.size());

View File

@@ -16,11 +16,13 @@ namespace CreativeInventory
void AddPending(int itemId, int count, int auxValue, int groupIndex);
bool ResolveSymbols(SymbolResolver& resolver);
void InjectItems();
void UpdateTabPageCounts();
extern void* pCategoryGroups;
extern void* pItemInstanceCtor;
extern void* pSharedPtrCtor;
extern void* pVectorPushBack;
extern void* pSpecs;
extern std::vector<PendingCreativeItem> s_pendingItems;
}

View File

@@ -164,11 +164,18 @@ namespace GameHooks
void Hooked_CreativeStaticCtor()
{
// Inject mod items BEFORE vanilla staticCtor so they are included in the
// TabSpec page-count calculation that happens at the end of staticCtor.
LogUtil::Log("[LegacyForge] Hook: CreativeStaticCtor -- injecting modded items first");
CreativeInventory::InjectItems();
LogUtil::Log("[LegacyForge] Hook: CreativeStaticCtor -- building vanilla creative lists");
Original_CreativeStaticCtor();
LogUtil::Log("[LegacyForge] Hook: CreativeStaticCtor -- injecting modded items");
CreativeInventory::InjectItems();
// Safety: recalculate TabSpec page counts in case the injection-before
// approach didn't fully account for all items (e.g. different binary).
LogUtil::Log("[LegacyForge] Hook: CreativeStaticCtor -- updating tab page counts");
CreativeInventory::UpdateTabPageCounts();
}
void __fastcall Hooked_MainMenuCustomDraw(void* thisPtr, void* region)