From 656693ad9511b2886192f172a754a1813e047c74 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 8 Mar 2026 20:53:45 -0500 Subject: [PATCH] fix(runtime): remap modded item ids using namespace tags --- WeaveLoaderRuntime/CMakeLists.txt | 1 + WeaveLoaderRuntime/src/GameHooks.cpp | 23 ++ WeaveLoaderRuntime/src/GameHooks.h | 6 + WeaveLoaderRuntime/src/HookManager.cpp | 31 +++ WeaveLoaderRuntime/src/IdRegistry.cpp | 23 ++ WeaveLoaderRuntime/src/IdRegistry.h | 9 + WeaveLoaderRuntime/src/PdbParser.cpp | 143 ++++++++++- WeaveLoaderRuntime/src/SymbolResolver.cpp | 11 + WeaveLoaderRuntime/src/SymbolResolver.h | 3 + WeaveLoaderRuntime/src/WorldIdRemap.cpp | 286 ++++++++++++++++++++++ WeaveLoaderRuntime/src/WorldIdRemap.h | 11 + 11 files changed, 545 insertions(+), 2 deletions(-) create mode 100644 WeaveLoaderRuntime/src/WorldIdRemap.cpp create mode 100644 WeaveLoaderRuntime/src/WorldIdRemap.h diff --git a/WeaveLoaderRuntime/CMakeLists.txt b/WeaveLoaderRuntime/CMakeLists.txt index d55a874..d6e87f7 100644 --- a/WeaveLoaderRuntime/CMakeLists.txt +++ b/WeaveLoaderRuntime/CMakeLists.txt @@ -86,6 +86,7 @@ add_library(WeaveLoaderRuntime SHARED src/GameHooks.cpp src/DotNetHost.cpp src/IdRegistry.cpp + src/WorldIdRemap.cpp src/NativeExports.cpp src/CustomPickaxeRegistry.cpp src/CustomToolMaterialRegistry.cpp diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 73f251d..925ec81 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -10,6 +10,7 @@ #include "ManagedBlockRegistry.h" #include "CustomSlabRegistry.h" #include "LogUtil.h" +#include "WorldIdRemap.h" #include #include #include @@ -43,6 +44,8 @@ namespace GameHooks TextureGetSourceDim_fn Original_ClockTextureGetSourceWidth = nullptr; TextureGetSourceDim_fn Original_ClockTextureGetSourceHeight = nullptr; ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock = nullptr; + ItemInstanceSave_fn Original_ItemInstanceSave = nullptr; + ItemInstanceLoad_fn Original_ItemInstanceLoad = nullptr; ItemMineBlock_fn Original_ItemMineBlock = nullptr; ItemMineBlock_fn Original_DiggerItemMineBlock = nullptr; PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed = nullptr; @@ -1656,6 +1659,24 @@ namespace GameHooks Original_ItemInstanceMineBlock(thisPtr, level, tile, x, y, z, ownerSharedPtr); } + void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr) + { + // Namespace marker now lives on ItemInstance::tag, so it must be present + // before vanilla serialization copies that nested tag into the output. + WorldIdRemap::TagModdedItemInstance(thisPtr, compoundTagPtr); + void* outTag = Original_ItemInstanceSave + ? Original_ItemInstanceSave(thisPtr, compoundTagPtr) + : compoundTagPtr; + return outTag; + } + + void __fastcall Hooked_ItemInstanceLoad(void* thisPtr, void* compoundTagPtr) + { + if (Original_ItemInstanceLoad) + Original_ItemInstanceLoad(thisPtr, compoundTagPtr); + WorldIdRemap::RemapItemInstanceFromTag(thisPtr, compoundTagPtr); + } + bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr) { s_itemMineBlockHookCalls++; @@ -2050,6 +2071,8 @@ namespace GameHooks Original_RunStaticCtors(); + WorldIdRemap::EnsureMissingPlaceholders(); + LogUtil::Log("[WeaveLoader] Hook: RunStaticCtors complete -- calling Init"); DotNetHost::CallInit(); s_initCalled = true; diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index b023a67..976fa29 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -24,6 +24,8 @@ typedef void (__fastcall *ItemRendererRenderItemBillboard_fn)(void* thisPtr, voi typedef void (__fastcall *AnimatedTextureCycleFrames_fn)(void* thisPtr); typedef int (__fastcall *TextureGetSourceDim_fn)(void* thisPtr); typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); +typedef void* (__fastcall *ItemInstanceSave_fn)(void* thisPtr, void* compoundTagPtr); +typedef void (__fastcall *ItemInstanceLoad_fn)(void* thisPtr, void* compoundTagPtr); typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); typedef float (__fastcall *PickaxeGetDestroySpeed_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); typedef bool (__fastcall *PickaxeCanDestroySpecial_fn)(void* thisPtr, void* tilePtr); @@ -85,6 +87,8 @@ namespace GameHooks extern TextureGetSourceDim_fn Original_ClockTextureGetSourceWidth; extern TextureGetSourceDim_fn Original_ClockTextureGetSourceHeight; extern ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock; + extern ItemInstanceSave_fn Original_ItemInstanceSave; + extern ItemInstanceLoad_fn Original_ItemInstanceLoad; extern ItemMineBlock_fn Original_ItemMineBlock; extern ItemMineBlock_fn Original_DiggerItemMineBlock; extern PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed; @@ -147,6 +151,8 @@ namespace GameHooks int __fastcall Hooked_ClockTextureGetSourceWidth(void* thisPtr); int __fastcall Hooked_ClockTextureGetSourceHeight(void* thisPtr); void __fastcall Hooked_ItemInstanceMineBlock(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); + void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr); + void __fastcall Hooked_ItemInstanceLoad(void* thisPtr, void* compoundTagPtr); bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); bool __fastcall Hooked_DiggerItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); float __fastcall Hooked_PickaxeItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index 1e3e6c4..bc5299d 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -8,6 +8,7 @@ #include "GameObjectFactory.h" #include "FurnaceRecipeRegistry.h" #include "NativeExports.h" +#include "WorldIdRemap.h" #include "LogUtil.h" #include @@ -19,6 +20,8 @@ bool HookManager::Install(const SymbolResolver& symbols) return false; } + WorldIdRemap::SetTagNewTagSymbol(symbols.pTagNewTag); + if (symbols.pRunStaticCtors) { if (MH_CreateHook(symbols.pRunStaticCtors, @@ -83,6 +86,34 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.pItemInstanceSave) + { + if (MH_CreateHook(symbols.pItemInstanceSave, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceSave), + reinterpret_cast(&GameHooks::Original_ItemInstanceSave)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::save"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::save (namespace remap)"); + } + } + + if (symbols.pItemInstanceLoad) + { + if (MH_CreateHook(symbols.pItemInstanceLoad, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceLoad), + reinterpret_cast(&GameHooks::Original_ItemInstanceLoad)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::load"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::load (namespace remap)"); + } + } + if (symbols.pItemInstanceGetIcon) { if (MH_CreateHook(symbols.pItemInstanceGetIcon, diff --git a/WeaveLoaderRuntime/src/IdRegistry.cpp b/WeaveLoaderRuntime/src/IdRegistry.cpp index 9c40b0f..30e5d1c 100644 --- a/WeaveLoaderRuntime/src/IdRegistry.cpp +++ b/WeaveLoaderRuntime/src/IdRegistry.cpp @@ -75,3 +75,26 @@ void IdRegistry::RegisterVanilla(Type type, int numericId, const std::string& na reg.stringToNum[namespacedId] = numericId; reg.numToString[numericId] = namespacedId; } + +std::vector> IdRegistry::GetEntries(Type type) const +{ + std::lock_guard lock(m_mutex); + std::vector> entries; + const auto& reg = m_registries[static_cast(type)]; + entries.reserve(reg.numToString.size()); + for (const auto& it : reg.numToString) + entries.push_back(it); + return entries; +} + +void IdRegistry::SetMissingFallback(Type type, int numericId) +{ + std::lock_guard lock(m_mutex); + m_registries[static_cast(type)].missingFallbackId = numericId; +} + +int IdRegistry::GetMissingFallback(Type type) const +{ + std::lock_guard lock(m_mutex); + return m_registries[static_cast(type)].missingFallbackId; +} diff --git a/WeaveLoaderRuntime/src/IdRegistry.h b/WeaveLoaderRuntime/src/IdRegistry.h index 5fa7aff..d8fb87c 100644 --- a/WeaveLoaderRuntime/src/IdRegistry.h +++ b/WeaveLoaderRuntime/src/IdRegistry.h @@ -2,6 +2,7 @@ #include #include #include +#include /// Maps namespaced string IDs ("namespace:path") to auto-allocated numeric IDs. /// Separate pools for blocks, items, and entities. @@ -26,6 +27,13 @@ public: /// Pre-register a vanilla entry with a known numeric ID. void RegisterVanilla(Type type, int numericId, const std::string& namespacedId); + /// Returns a copy of the current numeric->namespace mappings for a type. + std::vector> GetEntries(Type type) const; + + /// Configure and query the placeholder numeric ID used when a namespaced ID no longer exists. + void SetMissingFallback(Type type, int numericId); + int GetMissingFallback(Type type) const; + private: IdRegistry(); @@ -34,6 +42,7 @@ private: std::unordered_map stringToNum; std::unordered_map numToString; int nextFreeId; + int missingFallbackId = -1; }; // Tile IDs 174-255 are unused by vanilla (161-169 also free but small). diff --git a/WeaveLoaderRuntime/src/PdbParser.cpp b/WeaveLoaderRuntime/src/PdbParser.cpp index 311f597..4f92440 100644 --- a/WeaveLoaderRuntime/src/PdbParser.cpp +++ b/WeaveLoaderRuntime/src/PdbParser.cpp @@ -97,6 +97,107 @@ static const char* GetGlobalSymName(const PDB::CodeView::DBI::Record* record, } } +#pragma pack(push, 1) +struct ProcRefRecordData +{ + uint32_t sumName; + uint32_t ibSym; + uint16_t imod; + PDB_FLEXIBLE_ARRAY_MEMBER(char, name); +}; +#pragma pack(pop) + +static const char* GetProcRefName(const PDB::CodeView::DBI::Record* record, + uint16_t& outModuleIndex, uint32_t& outSymbolOffset) +{ + switch (record->header.kind) + { + case PDB::CodeView::DBI::SymbolRecordKind::S_PROCREF: + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROCREF: + { + const ProcRefRecordData* ref = reinterpret_cast(&record->data); + outModuleIndex = ref->imod; + outSymbolOffset = ref->ibSym; + return ref->name; + } + default: + return nullptr; + } +} + +static uint32_t ResolveProcRecordRVA(const PDB::CodeView::DBI::Record* record, const PDB::ImageSectionStream* sectionStream) +{ + if (!record || !sectionStream) + return 0; + + uint16_t section = 0; + uint32_t offset = 0; + + switch (record->header.kind) + { + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32: + section = record->data.S_LPROC32.section; + offset = record->data.S_LPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32: + section = record->data.S_GPROC32.section; + offset = record->data.S_GPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID: + section = record->data.S_LPROC32_ID.section; + offset = record->data.S_LPROC32_ID.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID: + section = record->data.S_GPROC32_ID.section; + offset = record->data.S_GPROC32_ID.offset; + break; + default: + return 0; + } + + return sectionStream->ConvertSectionOffsetToRVA(section, offset); +} + +static uint32_t ResolveProcRefRVA(uint16_t moduleIndex, uint32_t symbolOffset) +{ + if (!s_moduleStream || !s_rawFile) + return 0; + + const PDB::ArrayView modules = s_moduleStream->GetModules(); + if (modules.GetLength() == 0) + return 0; + + const size_t candidateIndices[2] = { + (moduleIndex > 0) ? static_cast(moduleIndex - 1u) : static_cast(-1), + static_cast(moduleIndex) + }; + const uint32_t candidateOffsets[2] = { + symbolOffset, + symbolOffset + 4u + }; + + for (size_t moduleArrayIndex : candidateIndices) + { + if (moduleArrayIndex >= modules.GetLength()) + continue; + + const PDB::ModuleInfoStream::Module& mod = modules[moduleArrayIndex]; + if (!mod.HasSymbolStream()) + continue; + + const PDB::ModuleSymbolStream modSymStream = mod.CreateSymbolStream(*s_rawFile); + for (uint32_t candidateOffset : candidateOffsets) + { + const PDB::CodeView::DBI::Record* target = modSymStream.GetRecordAtOffset(candidateOffset); + const uint32_t rva = ResolveProcRecordRVA(target, s_sectionStream); + if (rva != 0) + return rva; + } + } + + return 0; +} + namespace PdbParser { @@ -213,7 +314,26 @@ uint32_t FindSymbolRVA(const char* decoratedName) } } - // 3) Search per-module symbol streams (S_LPROC32, S_GPROC32, S_LPROC32_ID, S_GPROC32_ID, S_LDATA32, S_GDATA32) + // 3) Search global PROCREF/LPROCREF symbols and chase them into the referenced module record. + { + const PDB::ArrayView records = s_globalStream->GetRecords(); + for (const PDB::HashRecord& hashRecord : records) + { + const PDB::CodeView::DBI::Record* record = s_globalStream->GetRecord(*s_symbolRecords, hashRecord); + uint16_t moduleIndex = 0; + uint32_t symbolOffset = 0; + const char* name = GetProcRefName(record, moduleIndex, symbolOffset); + + if (!name || strcmp(name, decoratedName) != 0) + continue; + + const uint32_t rva = ResolveProcRefRVA(moduleIndex, symbolOffset); + if (rva != 0) + return rva; + } + } + + // 4) Search per-module symbol streams (S_LPROC32, S_GPROC32, S_LPROC32_ID, S_GPROC32_ID, S_LDATA32, S_GDATA32) { const PDB::ArrayView modules = s_moduleStream->GetModules(); for (const PDB::ModuleInfoStream::Module& mod : modules) @@ -304,7 +424,26 @@ uint32_t FindSymbolRVAByName(const char* exactName) } } - // 2) Search per-module symbol streams for exact name matches. + // 2) Search global PROCREF/LPROCREF symbols and chase them into the referenced module record. + { + const PDB::ArrayView records = s_globalStream->GetRecords(); + for (const PDB::HashRecord& hashRecord : records) + { + const PDB::CodeView::DBI::Record* record = s_globalStream->GetRecord(*s_symbolRecords, hashRecord); + uint16_t moduleIndex = 0; + uint32_t symbolOffset = 0; + const char* name = GetProcRefName(record, moduleIndex, symbolOffset); + + if (!name || strcmp(name, exactName) != 0) + continue; + + const uint32_t rva = ResolveProcRefRVA(moduleIndex, symbolOffset); + if (rva != 0) + return rva; + } + } + + // 3) Search per-module symbol streams for exact name matches. { const PDB::ArrayView modules = s_moduleStream->GetModules(); for (const PDB::ModuleInfoStream::Module& mod : modules) diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 380eca2..4fb0026 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -28,6 +28,9 @@ static const char* SYM_COMPASS_GETSOURCEHEIGHT = "?getSourceHeight@CompassTextur static const char* SYM_CLOCK_GETSOURCEWIDTH = "?getSourceWidth@ClockTexture@@UEBAHXZ"; static const char* SYM_CLOCK_GETSOURCEHEIGHT = "?getSourceHeight@ClockTexture@@UEBAHXZ"; static const char* SYM_ITEMINSTANCE_MINEBLOCK = "?mineBlock@ItemInstance@@QEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@Z"; +static const char* SYM_ITEMINSTANCE_SAVE = "?save@ItemInstance@@QEAAPEAVCompoundTag@@PEAV2@@Z"; +static const char* SYM_ITEMINSTANCE_LOAD = "?load@ItemInstance@@QEAAXPEAVCompoundTag@@@Z"; +static const char* SYM_TAG_NEWTAG = "?newTag@Tag@@SAPEAV1@EAEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"; static const char* SYM_ITEM_MINEBLOCK = "?mineBlock@Item@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z"; static const char* SYM_DIGGERITEM_MINEBLOCK = "?mineBlock@DiggerItem@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z"; static const char* SYM_PICKAXEITEM_GETDESTROYSPEED = "?getDestroySpeed@PickaxeItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z"; @@ -181,6 +184,11 @@ bool SymbolResolver::ResolveGameFunctions() pClockTextureGetSourceWidth = Resolve(SYM_CLOCK_GETSOURCEWIDTH); pClockTextureGetSourceHeight = Resolve(SYM_CLOCK_GETSOURCEHEIGHT); pItemInstanceMineBlock = Resolve(SYM_ITEMINSTANCE_MINEBLOCK); + pItemInstanceSave = Resolve(SYM_ITEMINSTANCE_SAVE); + pItemInstanceLoad = Resolve(SYM_ITEMINSTANCE_LOAD); + pTagNewTag = Resolve(SYM_TAG_NEWTAG); + if (!pTagNewTag) + pTagNewTag = ResolveExactProcName(m_moduleBase, "Tag::newTag"); pItemMineBlock = Resolve(SYM_ITEM_MINEBLOCK); pDiggerItemMineBlock = Resolve(SYM_DIGGERITEM_MINEBLOCK); pPickaxeItemGetDestroySpeed = Resolve(SYM_PICKAXEITEM_GETDESTROYSPEED); @@ -293,6 +301,9 @@ bool SymbolResolver::ResolveGameFunctions() logSym("ClockTexture::getSourceWidth", pClockTextureGetSourceWidth); logSym("ClockTexture::getSourceHeight", pClockTextureGetSourceHeight); logSym("ItemInstance::mineBlock", pItemInstanceMineBlock); + logSym("ItemInstance::save", pItemInstanceSave); + logSym("ItemInstance::load", pItemInstanceLoad); + logSym("Tag::newTag", pTagNewTag); logSym("Item::mineBlock", pItemMineBlock); logSym("DiggerItem::mineBlock", pDiggerItemMineBlock); logSym("PickaxeItem::getDestroySpeed", pPickaxeItemGetDestroySpeed); diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index e988eaa..4c2fc43 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -33,6 +33,9 @@ public: void* pClockTextureGetSourceWidth = nullptr; // ClockTexture::getSourceWidth() const void* pClockTextureGetSourceHeight = nullptr; // ClockTexture::getSourceHeight() const void* pItemInstanceMineBlock = nullptr; // ItemInstance::mineBlock(Level*,int,int,int,int,shared_ptr) + void* pItemInstanceSave = nullptr; // ItemInstance::save(CompoundTag*) + void* pItemInstanceLoad = nullptr; // ItemInstance::load(CompoundTag*) + void* pTagNewTag = nullptr; // Tag::newTag(byte,const wstring&) void* pItemMineBlock = nullptr; // Item::mineBlock(shared_ptr,Level*,int,int,int,int,shared_ptr) void* pDiggerItemMineBlock = nullptr; // DiggerItem::mineBlock(shared_ptr,Level*,int,int,int,int,shared_ptr) void* pPickaxeItemGetDestroySpeed = nullptr; // PickaxeItem::getDestroySpeed(shared_ptr,Tile*) diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.cpp b/WeaveLoaderRuntime/src/WorldIdRemap.cpp new file mode 100644 index 0000000..9429ece --- /dev/null +++ b/WeaveLoaderRuntime/src/WorldIdRemap.cpp @@ -0,0 +1,286 @@ +#include "WorldIdRemap.h" + +#include "GameObjectFactory.h" +#include "IdRegistry.h" +#include "LogUtil.h" +#include "ModStrings.h" +#include "PdbParser.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + constexpr wchar_t kNamespaceTagKey[] = L"weaveloader:id"; + constexpr char kMissingBlockId[] = "weaveloader:missing_block"; + constexpr char kMissingItemId[] = "weaveloader:missing_item"; + constexpr int kMissingBlockNumericId = 255; + constexpr int kMissingItemNumericId = 31999; + constexpr int kMissingBlockDescriptionId = 900000; + constexpr int kMissingItemDescriptionId = 900001; + constexpr ptrdiff_t kItemIdOffset = 0x20; + constexpr ptrdiff_t kItemTagOffset = 0x28; + constexpr unsigned char kTagStringId = 8; + constexpr unsigned char kTagCompoundId = 10; + + using TagNewTag_fn = void* (__fastcall *)(unsigned char type, const std::wstring& name); + + struct CompoundTagLayout + { + void* vtable; + std::wstring name; + std::unordered_map tags; + }; + + struct StringTagLayout + { + void* vtable; + std::wstring name; + std::wstring data; + }; + + std::once_flag s_symbolInitOnce; + TagNewTag_fn s_tagNewTag = nullptr; + bool s_missingPlaceholdersReady = false; + + static bool IsVanillaId(const std::string& namespacedId) + { + return namespacedId.rfind("minecraft:", 0) == 0; + } + + static std::string ResolveNamespacedItemId(int numericId) + { + std::string namespacedId = IdRegistry::Instance().GetStringId(IdRegistry::Type::Item, numericId); + if (!namespacedId.empty()) + return namespacedId; + + // Block-backed inventory entries are TileItems whose stored item ID matches the block ID. + return IdRegistry::Instance().GetStringId(IdRegistry::Type::Block, numericId); + } + + static int ResolveNumericItemId(const std::string& namespacedId, int oldNumericId) + { + if (namespacedId.empty()) + return -1; + + const bool wasKnownAsBlock = !IdRegistry::Instance().GetStringId(IdRegistry::Type::Block, oldNumericId).empty(); + const bool wasKnownAsItem = !IdRegistry::Instance().GetStringId(IdRegistry::Type::Item, oldNumericId).empty(); + + if (wasKnownAsBlock && !wasKnownAsItem) + { + int blockId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, namespacedId); + if (blockId >= 0) + return blockId; + } + + int itemId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Item, namespacedId); + if (itemId >= 0) + return itemId; + + int blockId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, namespacedId); + if (blockId >= 0) + return blockId; + + if (wasKnownAsBlock) + return IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); + + return IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Item); + } + + static bool IsReadableRange(const void* ptr, size_t bytes); + + static CompoundTagLayout* GetItemTag(void* itemInstancePtr) + { + if (!itemInstancePtr || !IsReadableRange(static_cast(itemInstancePtr) + kItemTagOffset, sizeof(void*))) + return nullptr; + return *reinterpret_cast(static_cast(itemInstancePtr) + kItemTagOffset); + } + + static CompoundTagLayout* EnsureItemTag(void* itemInstancePtr) + { + CompoundTagLayout* tag = GetItemTag(itemInstancePtr); + if (tag || !s_tagNewTag) + return tag; + + void* createdTag = s_tagNewTag(kTagCompoundId, L""); + if (!createdTag) + return nullptr; + + *reinterpret_cast(static_cast(itemInstancePtr) + kItemTagOffset) = createdTag; + return reinterpret_cast(createdTag); + } + + static bool TryGetCompoundString(CompoundTagLayout* compoundTag, const std::wstring& key, std::wstring* outValue) + { + if (!compoundTag || !outValue) + return false; + + const auto it = compoundTag->tags.find(key); + if (it == compoundTag->tags.end() || !it->second) + return false; + + const auto* stringTag = reinterpret_cast(it->second); + *outValue = stringTag->data; + return true; + } + + static bool PutCompoundString(CompoundTagLayout* compoundTag, const std::wstring& key, const std::wstring& value) + { + if (!compoundTag) + return false; + + const auto it = compoundTag->tags.find(key); + if (it != compoundTag->tags.end() && it->second) + { + auto* stringTag = reinterpret_cast(it->second); + stringTag->data = value; + return true; + } + + if (!s_tagNewTag) + return false; + + void* createdTag = s_tagNewTag(kTagStringId, key); + if (!createdTag) + return false; + + auto* stringTag = reinterpret_cast(createdTag); + stringTag->data = value; + compoundTag->tags[key] = createdTag; + return true; + } + + static bool IsReadableRange(const void* ptr, size_t bytes) + { + if (!ptr || bytes == 0) + return false; + + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(ptr, &mbi, sizeof(mbi)) != sizeof(mbi)) + return false; + if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS || (mbi.Protect & PAGE_GUARD)) + return false; + + const DWORD readMask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | + PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; + if ((mbi.Protect & readMask) == 0) + return false; + + const uintptr_t p = reinterpret_cast(ptr); + const uintptr_t end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return p + bytes >= p && p + bytes <= end; + } + + static void ResolveSymbolsOnce() + { + std::call_once(s_symbolInitOnce, []() { + if (s_tagNewTag) + return; + + const uintptr_t base = reinterpret_cast(GetModuleHandleW(nullptr)); + uint32_t newTagRvaByName = PdbParser::FindSymbolRVAByName("Tag::newTag"); + uint32_t newTagRvaByDecorated = PdbParser::FindSymbolRVA( + "?newTag@Tag@@SAPEAV1@EAEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"); + uint32_t newTagRva = newTagRvaByName; + if (newTagRva == 0) + newTagRva = newTagRvaByDecorated; + if (newTagRva) + s_tagNewTag = reinterpret_cast(base + newTagRva); + }); + } +} + +namespace WorldIdRemap +{ + void SetTagNewTagSymbol(void* fnPtr) + { + s_tagNewTag = reinterpret_cast(fnPtr); + } + + void EnsureMissingPlaceholders() + { + if (s_missingPlaceholdersReady) + return; + + IdRegistry::Instance().RegisterVanilla(IdRegistry::Type::Block, kMissingBlockNumericId, kMissingBlockId); + IdRegistry::Instance().RegisterVanilla(IdRegistry::Type::Item, kMissingItemNumericId, kMissingItemId); + + ModStrings::Register(kMissingBlockDescriptionId, L"Missing Block"); + GameObjectFactory::CreateTile( + kMissingBlockNumericId, + 1, + 1.0f, + 1.0f, + 1, + L"bedrock", + 0.0f, + 15, + kMissingBlockDescriptionId); + IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Block, kMissingBlockNumericId); + + ModStrings::Register(kMissingItemDescriptionId, L"Missing Item"); + GameObjectFactory::CreateItem( + kMissingItemNumericId, + 64, + 0, + L"apple", + kMissingItemDescriptionId); + IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Item, kMissingItemNumericId); + + s_missingPlaceholdersReady = true; + LogUtil::Log("[WeaveLoader] Missing content placeholders ready: block=%d item=%d", + IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block), + IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Item)); + } + + void TagModdedItemInstance(void* itemInstancePtr, void* compoundTagPtr) + { + ResolveSymbolsOnce(); + if (!itemInstancePtr) + return; + if (!IsReadableRange(static_cast(itemInstancePtr) + kItemIdOffset, sizeof(int))) + return; + (void)compoundTagPtr; + + const int itemId = *reinterpret_cast(static_cast(itemInstancePtr) + kItemIdOffset); + const std::string namespacedId = ResolveNamespacedItemId(itemId); + if (namespacedId.empty() || IsVanillaId(namespacedId)) + return; + + CompoundTagLayout* itemTag = EnsureItemTag(itemInstancePtr); + if (!itemTag) + return; + + const std::wstring namespacedWide(namespacedId.begin(), namespacedId.end()); + if (!PutCompoundString(itemTag, kNamespaceTagKey, namespacedWide)) + return; + } + + void RemapItemInstanceFromTag(void* itemInstancePtr, void* compoundTagPtr) + { + ResolveSymbolsOnce(); + if (!itemInstancePtr) + return; + (void)compoundTagPtr; + if (!IsReadableRange(static_cast(itemInstancePtr) + kItemIdOffset, sizeof(int))) + return; + + const int oldItemId = *reinterpret_cast(static_cast(itemInstancePtr) + kItemIdOffset); + CompoundTagLayout* itemTag = GetItemTag(itemInstancePtr); + if (!itemTag) + return; + + std::wstring namespacedWide; + if (!TryGetCompoundString(itemTag, kNamespaceTagKey, &namespacedWide) || namespacedWide.empty()) + return; + + const std::string namespacedId(namespacedWide.begin(), namespacedWide.end()); + int numericId = ResolveNumericItemId(namespacedId, oldItemId); + if (numericId >= 0) + *reinterpret_cast(static_cast(itemInstancePtr) + kItemIdOffset) = numericId; + } +} diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.h b/WeaveLoaderRuntime/src/WorldIdRemap.h new file mode 100644 index 0000000..ec7941d --- /dev/null +++ b/WeaveLoaderRuntime/src/WorldIdRemap.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace WorldIdRemap +{ + void SetTagNewTagSymbol(void* fnPtr); + void EnsureMissingPlaceholders(); + void TagModdedItemInstance(void* itemInstancePtr, void* compoundTagPtr); + void RemapItemInstanceFromTag(void* itemInstancePtr, void* compoundTagPtr); +}