diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 925ec81..4fe759c 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -60,6 +60,8 @@ namespace GameHooks LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr; ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks = nullptr; TileGetResource_fn Original_TileGetResource = nullptr; + McRegionChunkStorageLoad_fn Original_McRegionChunkStorageLoad = nullptr; + McRegionChunkStorageSave_fn Original_McRegionChunkStorageSave = nullptr; TileCloneTileId_fn Original_TileCloneTileId = nullptr; TileGetTextureFaceData_fn Original_StoneSlabGetTexture = nullptr; TileGetTextureFaceData_fn Original_WoodSlabGetTexture = nullptr; @@ -1167,6 +1169,31 @@ namespace GameHooks return originalResult || anyPending; } + void* __fastcall Hooked_McRegionChunkStorageLoad(void* thisPtr, void* level, int x, int z) + { + static int s_chunkLoadLogCount = 0; + void* levelChunk = Original_McRegionChunkStorageLoad + ? Original_McRegionChunkStorageLoad(thisPtr, level, x, z) + : nullptr; + if (levelChunk) + { + const int remapped = WorldIdRemap::RemapChunkBlockIds(thisPtr, levelChunk, x, z); + if (s_chunkLoadLogCount < 64) + { + ++s_chunkLoadLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap chunk load: x=%d z=%d remapped=%d", x, z, remapped); + } + } + return levelChunk; + } + + void __fastcall Hooked_McRegionChunkStorageSave(void* thisPtr, void* level, void* levelChunk) + { + WorldIdRemap::SaveChunkBlockNamespaces(thisPtr, levelChunk); + if (Original_McRegionChunkStorageSave) + Original_McRegionChunkStorageSave(thisPtr, level, levelChunk); + } + int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) { const ManagedBlockRegistry::Definition* def = ManagedBlockRegistry::Find(TryReadTileId(thisPtr)); diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index 976fa29..996c384 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -37,6 +37,8 @@ typedef bool (__fastcall *LevelSetDataDispatch_fn)(void* thisPtr, int x, int y, typedef void (__fastcall *LevelUpdateNeighborsAtDispatch_fn)(void* thisPtr, int x, int y, int z, int type); typedef bool (__fastcall *ServerLevelTickPendingTicks_fn)(void* thisPtr, bool force); typedef int (__fastcall *LevelGetTile_fn)(void* thisPtr, int x, int y, int z); +typedef void* (__fastcall *McRegionChunkStorageLoad_fn)(void* thisPtr, void* level, int x, int z); +typedef void (__fastcall *McRegionChunkStorageSave_fn)(void* thisPtr, void* level, void* levelChunk); typedef int (__fastcall *TileGetResource_fn)(void* thisPtr, int data, void* random, int playerBonusLevel); typedef int (__fastcall *TileCloneTileId_fn)(void* thisPtr, void* level, int x, int y, int z); typedef void* (__fastcall *TileGetTextureFaceData_fn)(void* thisPtr, int face, int data); @@ -103,6 +105,8 @@ namespace GameHooks extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt; extern ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks; extern TileGetResource_fn Original_TileGetResource; + extern McRegionChunkStorageLoad_fn Original_McRegionChunkStorageLoad; + extern McRegionChunkStorageSave_fn Original_McRegionChunkStorageSave; extern TileCloneTileId_fn Original_TileCloneTileId; extern TileGetTextureFaceData_fn Original_StoneSlabGetTexture; extern TileGetTextureFaceData_fn Original_WoodSlabGetTexture; @@ -166,6 +170,8 @@ namespace GameHooks bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate); void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type); bool __fastcall Hooked_ServerLevelTickPendingTicks(void* thisPtr, bool force); + void* __fastcall Hooked_McRegionChunkStorageLoad(void* thisPtr, void* level, int x, int z); + void __fastcall Hooked_McRegionChunkStorageSave(void* thisPtr, void* level, void* levelChunk); int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); int __fastcall Hooked_TileCloneTileId(void* thisPtr, void* level, int x, int y, int z); void* __fastcall Hooked_StoneSlabGetTexture(void* thisPtr, int face, int data); diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index bc5299d..848fe19 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -21,6 +21,11 @@ bool HookManager::Install(const SymbolResolver& symbols) } WorldIdRemap::SetTagNewTagSymbol(symbols.pTagNewTag); + WorldIdRemap::SetLevelChunkTileSymbols( + symbols.pLevelChunkGetTile, + symbols.pLevelChunkSetTile, + symbols.pLevelChunkGetPos, + symbols.pLevelChunkGetHighestNonEmptyY); if (symbols.pRunStaticCtors) { @@ -380,6 +385,34 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.pMcRegionChunkStorageLoad) + { + if (MH_CreateHook(symbols.pMcRegionChunkStorageLoad, + reinterpret_cast(&GameHooks::Hooked_McRegionChunkStorageLoad), + reinterpret_cast(&GameHooks::Original_McRegionChunkStorageLoad)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook McRegionChunkStorage::load"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked McRegionChunkStorage::load (block id remap)"); + } + } + + if (symbols.pMcRegionChunkStorageSave) + { + if (MH_CreateHook(symbols.pMcRegionChunkStorageSave, + reinterpret_cast(&GameHooks::Hooked_McRegionChunkStorageSave), + reinterpret_cast(&GameHooks::Original_McRegionChunkStorageSave)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook McRegionChunkStorage::save"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked McRegionChunkStorage::save (block namespace persistence)"); + } + } + if (symbols.pTileGetResource) { if (MH_CreateHook(symbols.pTileGetResource, diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 4fb0026..4d6883c 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -44,6 +44,8 @@ static const char* SYM_LEVEL_UPDATE_NEIGHBORS_AT = "?updateNeighborsAt@Level@@QE static const char* SYM_SERVERLEVEL_TICKPENDINGTICKS = "?tickPendingTicks@ServerLevel@@UEAA_N_N@Z"; static const char* SYM_LEVEL_GETTILE = "?getTile@Level@@UEAAHHHH@Z"; static const char* SYM_LEVEL_SETDATA = "?setData@Level@@UEAA_NHHHHH_N@Z"; +static const char* SYM_MCREGIONCHUNKSTORAGE_LOAD = "?load@McRegionChunkStorage@@UEAAPEAVLevelChunk@@PEAVLevel@@HH@Z"; +static const char* SYM_MCREGIONCHUNKSTORAGE_SAVE = "?save@McRegionChunkStorage@@UEAAXPEAVLevel@@PEAVLevelChunk@@@Z"; static const char* SYM_TILE_GETRESOURCE = "?getResource@Tile@@UEAAHHPEAVRandom@@H@Z"; static const char* SYM_TILE_CLONETILEID = "?cloneTileId@Tile@@UEAAHPEAVLevel@@HHH@Z"; static const char* SYM_TILE_GETTEXTURE_FACEDATA = "?getTexture@Tile@@UEAAPEAVIcon@@HH@Z"; @@ -202,6 +204,12 @@ bool SymbolResolver::ResolveGameFunctions() pServerLevelTickPendingTicks = Resolve(SYM_SERVERLEVEL_TICKPENDINGTICKS); pLevelGetTile = Resolve(SYM_LEVEL_GETTILE); pLevelSetData = Resolve(SYM_LEVEL_SETDATA); + pMcRegionChunkStorageLoad = Resolve(SYM_MCREGIONCHUNKSTORAGE_LOAD); + if (!pMcRegionChunkStorageLoad) + pMcRegionChunkStorageLoad = ResolveExactProcName(m_moduleBase, "McRegionChunkStorage::load"); + pMcRegionChunkStorageSave = Resolve(SYM_MCREGIONCHUNKSTORAGE_SAVE); + if (!pMcRegionChunkStorageSave) + pMcRegionChunkStorageSave = ResolveExactProcName(m_moduleBase, "McRegionChunkStorage::save"); pTileGetResource = Resolve(SYM_TILE_GETRESOURCE); pTileCloneTileId = Resolve(SYM_TILE_CLONETILEID); pTileGetTextureFaceData = Resolve(SYM_TILE_GETTEXTURE_FACEDATA); @@ -251,6 +259,10 @@ bool SymbolResolver::ResolveGameFunctions() pLevelSetTileAndData = Resolve(SYM_LEVEL_SETTILEANDDATA); pLevelAddToTickNextTick = Resolve(SYM_LEVEL_ADDTOTICKNEXTTICK); pServerLevelAddToTickNextTick = Resolve(SYM_SERVERLEVEL_ADDTOTICKNEXTTICK); + pLevelChunkGetTile = ResolveExactProcName(m_moduleBase, "LevelChunk::getTile"); + pLevelChunkSetTile = ResolveExactProcName(m_moduleBase, "LevelChunk::setTile"); + pLevelChunkGetPos = ResolveExactProcName(m_moduleBase, "LevelChunk::getPos"); + pLevelChunkGetHighestNonEmptyY = ResolveExactProcName(m_moduleBase, "LevelChunk::getHighestNonEmptyY"); // Some public symbols in this build resolve to stub bodies. Prefer exact // module procedure names from the PDB where those exist. @@ -317,6 +329,8 @@ bool SymbolResolver::ResolveGameFunctions() logSym("ServerLevel::tickPendingTicks", pServerLevelTickPendingTicks); logSym("Level::getTile", pLevelGetTile); logSym("Level::setData", pLevelSetData); + logSym("McRegionChunkStorage::load", pMcRegionChunkStorageLoad); + logSym("McRegionChunkStorage::save", pMcRegionChunkStorageSave); logSym("Tile::getResource", pTileGetResource); logSym("Tile::cloneTileId", pTileCloneTileId); logSym("Tile::getTexture(face,data)", pTileGetTextureFaceData); @@ -362,6 +376,10 @@ bool SymbolResolver::ResolveGameFunctions() logSym("Level::setTileAndData", pLevelSetTileAndData); logSym("Level::addToTickNextTick", pLevelAddToTickNextTick); logSym("ServerLevel::addToTickNextTick", pServerLevelAddToTickNextTick); + logSym("LevelChunk::getTile", pLevelChunkGetTile); + logSym("LevelChunk::setTile", pLevelChunkSetTile); + logSym("LevelChunk::getPos", pLevelChunkGetPos); + logSym("LevelChunk::getHighestNonEmptyY", pLevelChunkGetHighestNonEmptyY); bool ok = pRunStaticCtors && pMinecraftTick && pMinecraftInit; if (ok) diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index 4c2fc43..ad35c73 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -49,6 +49,8 @@ public: void* pServerLevelTickPendingTicks = nullptr; // ServerLevel::tickPendingTicks(bool) void* pLevelGetTile = nullptr; // Level::getTile(int,int,int) void* pLevelSetData = nullptr; // Level::setData(int,int,int,int,int,bool) + void* pMcRegionChunkStorageLoad = nullptr; // McRegionChunkStorage::load(Level*,int,int) + void* pMcRegionChunkStorageSave = nullptr; // McRegionChunkStorage::save(Level*,LevelChunk*) void* pTileGetResource = nullptr; // Tile::getResource(int,Random*,int) void* pTileCloneTileId = nullptr; // Tile::cloneTileId(Level*,int,int,int) void* pTileGetTextureFaceData = nullptr; // Tile::getTexture(int,int) @@ -94,6 +96,10 @@ public: void* pLevelSetTileAndData = nullptr; // Level::setTileAndData(int,int,int,int,int,int) void* pLevelAddToTickNextTick = nullptr; // Level::addToTickNextTick(int,int,int,int,int) void* pServerLevelAddToTickNextTick = nullptr; // ServerLevel::addToTickNextTick(int,int,int,int,int) + void* pLevelChunkGetTile = nullptr; // LevelChunk::getTile(int,int,int) + void* pLevelChunkSetTile = nullptr; // LevelChunk::setTile(int,int,int,int) + void* pLevelChunkGetPos = nullptr; // LevelChunk::getPos() + void* pLevelChunkGetHighestNonEmptyY = nullptr; // LevelChunk::getHighestNonEmptyY() private: uintptr_t m_moduleBase = 0; diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.cpp b/WeaveLoaderRuntime/src/WorldIdRemap.cpp index a358247..6bcbdb5 100644 --- a/WeaveLoaderRuntime/src/WorldIdRemap.cpp +++ b/WeaveLoaderRuntime/src/WorldIdRemap.cpp @@ -8,10 +8,14 @@ #include #include +#include +#include #include #include +#include #include #include +#include namespace { @@ -26,8 +30,14 @@ namespace constexpr ptrdiff_t kItemTagOffset = 0x28; constexpr unsigned char kTagStringId = 8; constexpr unsigned char kTagCompoundId = 10; + constexpr int kChunkWidth = 16; + constexpr int kChunkMaxY = 256; using TagNewTag_fn = void* (__fastcall *)(unsigned char type, const std::wstring& name); + using LevelChunkGetTile_fn = int (__fastcall *)(void* thisPtr, int x, int y, int z); + using LevelChunkSetTile_fn = bool (__fastcall *)(void* thisPtr, int x, int y, int z, int tile); + using LevelChunkGetPos_fn = void* (__fastcall *)(void* thisPtr); + using LevelChunkGetHighestNonEmptyY_fn = int (__fastcall *)(void* thisPtr); struct CompoundTagLayout { @@ -45,7 +55,257 @@ namespace std::once_flag s_symbolInitOnce; TagNewTag_fn s_tagNewTag = nullptr; + LevelChunkGetTile_fn s_levelChunkGetTile = nullptr; + LevelChunkSetTile_fn s_levelChunkSetTile = nullptr; + LevelChunkGetPos_fn s_levelChunkGetPos = nullptr; + LevelChunkGetHighestNonEmptyY_fn s_levelChunkGetHighestNonEmptyY = nullptr; bool s_missingPlaceholdersReady = false; + int s_chunkRemapLogCount = 0; + int s_chunkSaveLogCount = 0; + int s_chunkIoLogCount = 0; + std::mutex s_chunkMetaMutex; + + struct ChunkMeta + { + void* storagePtr = nullptr; + int x = 0; + int z = 0; + }; + std::unordered_map s_chunkMetaByPtr; + + struct ConsoleSavePathLayout + { + std::wstring path; + explicit ConsoleSavePathLayout(const std::wstring& p) : path(p) {} + }; + + struct McRegionChunkStorageLayout + { + void* vtable; + std::wstring prefix; + void* saveFile; + }; + + struct ChunkPosLayout + { + int x; + int z; + }; + + struct RuntimeConsoleSaveFile + { + virtual ~RuntimeConsoleSaveFile() = default; + virtual void* createFile(const ConsoleSavePathLayout& fileName) = 0; + virtual void deleteFile(void* fileEntry) = 0; + virtual void setFilePointer(void* fileEntry, long distance, long* distanceHigh, unsigned long moveMethod) = 0; + virtual int writeFile(void* fileEntry, const void* buffer, unsigned long bytesToWrite, unsigned long* bytesWritten) = 0; + virtual int zeroFile(void* fileEntry, unsigned long bytesToWrite, unsigned long* bytesWritten) = 0; + virtual int readFile(void* fileEntry, void* buffer, unsigned long bytesToRead, unsigned long* bytesRead) = 0; + virtual int closeHandle(void* fileEntry) = 0; + virtual void finalizeWrite() = 0; + virtual void tick() = 0; + virtual bool doesFileExist(ConsoleSavePathLayout fileName) = 0; + }; + + struct RuntimeFileEntryLayout + { + wchar_t filename[64]; + unsigned int length; + unsigned int startOffsetOrRegion; + long long lastModifiedTime; + unsigned int currentFilePointer; + }; + static bool IsReadableRange(const void* ptr, size_t bytes); + + static bool IsValidChunkStorage(const void* storagePtr) + { + return storagePtr && IsReadableRange(storagePtr, sizeof(void*) + sizeof(std::wstring) + sizeof(void*)); + } + + static std::wstring MakeChunkNamespacePath(const McRegionChunkStorageLayout* storage, int chunkX, int chunkZ) + { + std::wstringstream ss; + std::hash hasher; + const size_t prefixHash = hasher(storage ? storage->prefix : std::wstring()); + ss << L"modloader\\blockns\\" + << std::hex << prefixHash << std::dec + << L"\\c." << chunkX << L"." << chunkZ << L".txt"; + return ss.str(); + } + + static bool SaveReadAllText(void* saveFile, const std::wstring& path, std::string* outText) + { + if (!saveFile || !outText || !IsReadableRange(saveFile, sizeof(void*))) + return false; + auto* save = reinterpret_cast(saveFile); + + ConsoleSavePathLayout savePath(path); + void* fileEntry = save->createFile(savePath); + if (!fileEntry) + return false; + save->setFilePointer(fileEntry, 0, nullptr, FILE_BEGIN); + + unsigned int fileSize = 0; + if (IsReadableRange(fileEntry, sizeof(RuntimeFileEntryLayout))) + { + const auto* entry = reinterpret_cast(fileEntry); + fileSize = entry->length; + } + + std::string text; + if (fileSize > 0) + { + text.resize(fileSize); + unsigned long bytesRead = 0; + const int ok = save->readFile(fileEntry, text.data(), fileSize, &bytesRead); + if (!ok) + { + save->closeHandle(fileEntry); + return false; + } + text.resize(bytesRead); + } + + save->closeHandle(fileEntry); + *outText = std::move(text); + return true; + } + + static bool SaveFileExists(void* saveFile, const std::wstring& path) + { + if (!saveFile || !IsReadableRange(saveFile, sizeof(void*))) + return false; + auto* save = reinterpret_cast(saveFile); + ConsoleSavePathLayout savePath(path); + return save->doesFileExist(savePath); + } + + static bool SaveWriteAllText(void* saveFile, const std::wstring& path, const std::string& text) + { + if (!saveFile || !IsReadableRange(saveFile, sizeof(void*))) + return false; + auto* save = reinterpret_cast(saveFile); + + ConsoleSavePathLayout savePath(path); + void* fileEntry = save->createFile(savePath); + if (!fileEntry) + return false; + save->setFilePointer(fileEntry, 0, nullptr, FILE_BEGIN); + + unsigned long bytesWritten = 0; + const int ok = save->writeFile(fileEntry, text.data(), static_cast(text.size()), &bytesWritten); + save->closeHandle(fileEntry); + return ok && bytesWritten == text.size(); + } + + static bool ReadChunkNamespaceMap( + void* saveFile, + const std::wstring& path, + std::unordered_map* outMap) + { + if (!outMap) + return false; + outMap->clear(); + + if (!SaveFileExists(saveFile, path)) + return false; + + std::string text; + if (!SaveReadAllText(saveFile, path, &text)) + return false; + + std::istringstream input(text); + std::string line; + int expectedEntries = -1; + int parsedEntries = 0; + while (std::getline(input, line)) + { + if (line.empty()) + continue; + + if (expectedEntries < 0 && line.rfind("#count ", 0) == 0) + { + std::istringstream hs(line.substr(7)); + int count = -1; + if (hs >> count && count >= 0) + expectedEntries = count; + continue; + } + + std::istringstream ss(line); + int blockIndex = -1; + std::string namespacedId; + if (!(ss >> blockIndex >> namespacedId)) + continue; + if (blockIndex < 0 || namespacedId.empty()) + continue; + (*outMap)[blockIndex] = namespacedId; + ++parsedEntries; + if (expectedEntries >= 0 && parsedEntries >= expectedEntries) + break; + } + return true; + } + + static bool WriteChunkNamespaceMap( + void* saveFile, + const std::wstring& path, + const std::unordered_map& map) + { + std::ostringstream out; + out << "#count " << map.size() << '\n'; + for (const auto& entry : map) + out << entry.first << ' ' << entry.second << '\n'; + return SaveWriteAllText(saveFile, path, out.str()); + } + + static int MakeBlockIndex(int x, int y, int z) + { + return ((y & 0xFF) << 8) | ((z & 0x0F) << 4) | (x & 0x0F); + } + + static void DecodeBlockIndex(int index, int* x, int* y, int* z) + { + if (x) *x = index & 0x0F; + if (z) *z = (index >> 4) & 0x0F; + if (y) *y = (index >> 8) & 0xFF; + } + + static bool TryGetChunkMeta(void* chunkPtr, ChunkMeta* outMeta) + { + if (!chunkPtr || !outMeta) + return false; + std::lock_guard lock(s_chunkMetaMutex); + const auto it = s_chunkMetaByPtr.find(chunkPtr); + if (it == s_chunkMetaByPtr.end()) + return false; + *outMeta = it->second; + return true; + } + + static void SetChunkMeta(void* chunkPtr, void* storagePtr, int chunkX, int chunkZ) + { + if (!chunkPtr || !storagePtr) + return; + std::lock_guard lock(s_chunkMetaMutex); + ChunkMeta& meta = s_chunkMetaByPtr[chunkPtr]; + meta.storagePtr = storagePtr; + meta.x = chunkX; + meta.z = chunkZ; + } + + static bool TryResolveChunkCoords(void* levelChunkPtr, int* outX, int* outZ) + { + if (!levelChunkPtr || !s_levelChunkGetPos) + return false; + void* posPtr = s_levelChunkGetPos(levelChunkPtr); + if (!posPtr || !IsReadableRange(posPtr, sizeof(ChunkPosLayout))) + return false; + const auto* pos = reinterpret_cast(posPtr); + if (outX) *outX = pos->x; + if (outZ) *outZ = pos->z; + return true; + } static bool IsVanillaId(const std::string& namespacedId) { @@ -201,6 +461,14 @@ namespace WorldIdRemap s_tagNewTag = reinterpret_cast(fnPtr); } + void SetLevelChunkTileSymbols(void* getTileFn, void* setTileFn, void* getPosFn, void* getHighestNonEmptyYFn) + { + s_levelChunkGetTile = reinterpret_cast(getTileFn); + s_levelChunkSetTile = reinterpret_cast(setTileFn); + s_levelChunkGetPos = reinterpret_cast(getPosFn); + s_levelChunkGetHighestNonEmptyY = reinterpret_cast(getHighestNonEmptyYFn); + } + void EnsureMissingPlaceholders() { if (s_missingPlaceholdersReady) @@ -237,6 +505,138 @@ namespace WorldIdRemap IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Item)); } + int RemapChunkBlockIds(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ) + { + if (!chunkStoragePtr || !levelChunkPtr || !s_levelChunkGetTile || !s_levelChunkSetTile) + return 0; + if (!IsValidChunkStorage(chunkStoragePtr)) + return 0; + + auto* storage = reinterpret_cast(chunkStoragePtr); + if (!storage->saveFile) + return 0; + + SetChunkMeta(levelChunkPtr, chunkStoragePtr, chunkX, chunkZ); + + std::unordered_map entries; + const std::wstring path = MakeChunkNamespacePath(storage, chunkX, chunkZ); + if (!SaveFileExists(storage->saveFile, path)) + return 0; + if (s_chunkIoLogCount < 32) + { + ++s_chunkIoLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: reading chunk namespace map x=%d z=%d", chunkX, chunkZ); + } + if (!ReadChunkNamespaceMap(storage->saveFile, path, &entries) || entries.empty()) + return 0; + + const int missingBlockFallback = IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); + int changed = 0; + for (const auto& entry : entries) + { + int x = 0; + int y = 0; + int z = 0; + DecodeBlockIndex(entry.first, &x, &y, &z); + int newId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, entry.second); + if (newId < 0) + newId = missingBlockFallback; + if (newId < 0 || newId > 255) + continue; + + const int oldId = s_levelChunkGetTile(levelChunkPtr, x, y, z); + if (newId != oldId) + { + s_levelChunkSetTile(levelChunkPtr, x, y, z, newId); + ++changed; + } + } + + if (changed > 0 && s_chunkRemapLogCount < 64) + { + ++s_chunkRemapLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: remapped %d block ids in chunk", changed); + } + return changed; + } + + void SaveChunkBlockNamespaces(void* chunkStoragePtr, void* levelChunkPtr) + { + if (!chunkStoragePtr || !levelChunkPtr || !s_levelChunkGetTile) + return; + if (!IsValidChunkStorage(chunkStoragePtr)) + return; + + auto* storage = reinterpret_cast(chunkStoragePtr); + if (!storage->saveFile) + return; + + ChunkMeta meta{}; + if (!TryGetChunkMeta(levelChunkPtr, &meta)) + { + meta.storagePtr = chunkStoragePtr; + if (!TryResolveChunkCoords(levelChunkPtr, &meta.x, &meta.z)) + return; + SetChunkMeta(levelChunkPtr, chunkStoragePtr, meta.x, meta.z); + } + + const std::wstring path = MakeChunkNamespacePath(storage, meta.x, meta.z); + const bool hadExistingMap = SaveFileExists(storage->saveFile, path); + std::unordered_map previousEntries; + if (hadExistingMap) + ReadChunkNamespaceMap(storage->saveFile, path, &previousEntries); + + std::unordered_map nextEntries; + int maxY = kChunkMaxY - 1; + if (s_levelChunkGetHighestNonEmptyY) + { + const int highestY = s_levelChunkGetHighestNonEmptyY(levelChunkPtr); + if (highestY < 0 && !hadExistingMap) + return; + if (highestY >= 0 && highestY < kChunkMaxY) + maxY = highestY; + } + + for (int y = 0; y <= maxY; ++y) + { + for (int z = 0; z < kChunkWidth; ++z) + { + for (int x = 0; x < kChunkWidth; ++x) + { + const int blockId = s_levelChunkGetTile(levelChunkPtr, x, y, z); + std::string namespacedId = IdRegistry::Instance().GetStringId(IdRegistry::Type::Block, blockId); + if (namespacedId.empty()) + continue; + if (IsVanillaId(namespacedId)) + continue; + + const int blockIndex = MakeBlockIndex(x, y, z); + if (namespacedId == kMissingBlockId) + { + const auto it = previousEntries.find(blockIndex); + if (it != previousEntries.end() && !it->second.empty() && it->second != kMissingBlockId) + nextEntries[blockIndex] = it->second; + continue; + } + + nextEntries[blockIndex] = std::move(namespacedId); + } + } + } + + if (nextEntries.empty() && !hadExistingMap) + return; + if (!WriteChunkNamespaceMap(storage->saveFile, path, nextEntries)) + return; + + if (s_chunkSaveLogCount < 64) + { + ++s_chunkSaveLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: saved %zu namespace entries for chunk x=%d z=%d", + nextEntries.size(), meta.x, meta.z); + } + } + void TagModdedItemInstance(void* itemInstancePtr, void* compoundTagPtr) { ResolveSymbolsOnce(); diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.h b/WeaveLoaderRuntime/src/WorldIdRemap.h index ec7941d..0bc8617 100644 --- a/WeaveLoaderRuntime/src/WorldIdRemap.h +++ b/WeaveLoaderRuntime/src/WorldIdRemap.h @@ -5,7 +5,10 @@ namespace WorldIdRemap { void SetTagNewTagSymbol(void* fnPtr); + void SetLevelChunkTileSymbols(void* getTileFn, void* setTileFn, void* getPosFn, void* getHighestNonEmptyYFn); void EnsureMissingPlaceholders(); + int RemapChunkBlockIds(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ); + void SaveChunkBlockNamespaces(void* chunkStoragePtr, void* levelChunkPtr); void TagModdedItemInstance(void* itemInstancePtr, void* compoundTagPtr); void RemapItemInstanceFromTag(void* itemInstancePtr, void* compoundTagPtr); }