diff --git a/LegacyForgeRuntime/src/CreativeInventory.cpp b/LegacyForgeRuntime/src/CreativeInventory.cpp index a6cbae8..bccae13 100644 --- a/LegacyForgeRuntime/src/CreativeInventory.cpp +++ b/LegacyForgeRuntime/src/CreativeInventory.cpp @@ -1,7 +1,9 @@ #include "CreativeInventory.h" #include "SymbolResolver.h" +#include "PdbParser.h" #include "LogUtil.h" #include +#include namespace CreativeInventory { @@ -10,6 +12,7 @@ void* pCategoryGroups = nullptr; void* pItemInstanceCtor = nullptr; void* pSharedPtrCtor = nullptr; void* pVectorPushBack = nullptr; +void* pSpecs = nullptr; std::vector 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>::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(vec); + char* last = *reinterpret_cast(vec + 8); + if (!first || last <= first) return 0; + return static_cast((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(pSpecs); + if (!specsArray) + { + LogUtil::Log("[LegacyForge] specs pointer is null, TabSpec array not yet allocated"); + return; + } + + char* groups = reinterpret_cast(pCategoryGroups); + + for (int tabIdx = 0; tabIdx < TAB_COUNT; ++tabIdx) + { + char* tab = reinterpret_cast(specsArray[tabIdx]); + if (!tab) continue; + + int staticGroupsCount = *reinterpret_cast(tab + TABSPEC_STATIC_GROUPS_COUNT_OFF); + int* staticGroupsA = *reinterpret_cast(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( + ReadVectorSize(groups + groupIdx * SIZEOF_MSVC_VECTOR)); + } + + unsigned int oldItems = *reinterpret_cast(tab + TABSPEC_STATIC_ITEMS_OFF); + unsigned int oldPages = *reinterpret_cast(tab + TABSPEC_PAGES_OFF); + + *reinterpret_cast(tab + TABSPEC_STATIC_ITEMS_OFF) = totalItems; + + int totalRows = (totalItems + COLUMNS - 1) / COLUMNS; + int newPages = totalRows - ROWS + 1; + if (newPages < 1) newPages = 1; + *reinterpret_cast(tab + TABSPEC_PAGES_OFF) = static_cast(newPages); + + if (totalItems != oldItems) + { + LogUtil::Log("[LegacyForge] Tab %d: staticItems %u -> %u, pages %u -> %u", + tabIdx, oldItems, totalItems, oldPages, static_cast(newPages)); + } + } +} + void InjectItems() { if (!pCategoryGroups || !pItemInstanceCtor || !pSharedPtrCtor || !pVectorPushBack) @@ -76,6 +172,8 @@ void InjectItems() auto spCtorFn = reinterpret_cast(pSharedPtrCtor); auto pushFn = reinterpret_cast(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(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(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(spBuf); + void* spCtrl = *reinterpret_cast(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()); diff --git a/LegacyForgeRuntime/src/CreativeInventory.h b/LegacyForgeRuntime/src/CreativeInventory.h index f0829d0..190a70b 100644 --- a/LegacyForgeRuntime/src/CreativeInventory.h +++ b/LegacyForgeRuntime/src/CreativeInventory.h @@ -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 s_pendingItems; } diff --git a/LegacyForgeRuntime/src/GameHooks.cpp b/LegacyForgeRuntime/src/GameHooks.cpp index 6e9d9ae..6e27be3 100644 --- a/LegacyForgeRuntime/src/GameHooks.cpp +++ b/LegacyForgeRuntime/src/GameHooks.cpp @@ -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)