diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index 848fe19..9b2537a 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -21,11 +21,13 @@ bool HookManager::Install(const SymbolResolver& symbols) } WorldIdRemap::SetTagNewTagSymbol(symbols.pTagNewTag); + WorldIdRemap::SetTileArraySymbol(symbols.pTileTiles); WorldIdRemap::SetLevelChunkTileSymbols( symbols.pLevelChunkGetTile, symbols.pLevelChunkSetTile, symbols.pLevelChunkGetPos, symbols.pLevelChunkGetHighestNonEmptyY); + WorldIdRemap::SetCompressedTileStorageSetSymbol(symbols.pCompressedTileStorageSet); if (symbols.pRunStaticCtors) { diff --git a/WeaveLoaderRuntime/src/PdbParser.cpp b/WeaveLoaderRuntime/src/PdbParser.cpp index 4f92440..d6b0aaa 100644 --- a/WeaveLoaderRuntime/src/PdbParser.cpp +++ b/WeaveLoaderRuntime/src/PdbParser.cpp @@ -16,6 +16,8 @@ #include "PDB_CoalescedMSFStream.h" #include "PDB_ModuleInfoStream.h" #include "PDB_ModuleSymbolStream.h" +#include "PDB_Util.h" +#include "Foundation/PDB_BitUtil.h" struct SymEntry { @@ -188,7 +190,15 @@ static uint32_t ResolveProcRefRVA(uint16_t moduleIndex, uint32_t symbolOffset) const PDB::ModuleSymbolStream modSymStream = mod.CreateSymbolStream(*s_rawFile); for (uint32_t candidateOffset : candidateOffsets) { - const PDB::CodeView::DBI::Record* target = modSymStream.GetRecordAtOffset(candidateOffset); + const PDB::CodeView::DBI::Record* target = nullptr; + size_t currentOffset = sizeof(uint32_t); + modSymStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record) { + if (target == nullptr && currentOffset == static_cast(candidateOffset)) + target = record; + const uint32_t recordSize = PDB::GetCodeViewRecordSize(record); + currentOffset = PDB::BitUtil::RoundUpToMultiple( + currentOffset + sizeof(PDB::CodeView::DBI::RecordHeader) + recordSize, 4u); + }); const uint32_t rva = ResolveProcRecordRVA(target, s_sectionStream); if (rva != 0) return rva; diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 4d6883c..021aa68 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -263,6 +263,7 @@ bool SymbolResolver::ResolveGameFunctions() pLevelChunkSetTile = ResolveExactProcName(m_moduleBase, "LevelChunk::setTile"); pLevelChunkGetPos = ResolveExactProcName(m_moduleBase, "LevelChunk::getPos"); pLevelChunkGetHighestNonEmptyY = ResolveExactProcName(m_moduleBase, "LevelChunk::getHighestNonEmptyY"); + pCompressedTileStorageSet = ResolveExactProcName(m_moduleBase, "CompressedTileStorage::set"); // Some public symbols in this build resolve to stub bodies. Prefer exact // module procedure names from the PDB where those exist. @@ -380,6 +381,7 @@ bool SymbolResolver::ResolveGameFunctions() logSym("LevelChunk::setTile", pLevelChunkSetTile); logSym("LevelChunk::getPos", pLevelChunkGetPos); logSym("LevelChunk::getHighestNonEmptyY", pLevelChunkGetHighestNonEmptyY); + logSym("CompressedTileStorage::set", pCompressedTileStorageSet); bool ok = pRunStaticCtors && pMinecraftTick && pMinecraftInit; if (ok) diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index ad35c73..3c33036 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -100,6 +100,7 @@ public: void* pLevelChunkSetTile = nullptr; // LevelChunk::setTile(int,int,int,int) void* pLevelChunkGetPos = nullptr; // LevelChunk::getPos() void* pLevelChunkGetHighestNonEmptyY = nullptr; // LevelChunk::getHighestNonEmptyY() + void* pCompressedTileStorageSet = nullptr; // CompressedTileStorage::set(int,int,int,int) private: uintptr_t m_moduleBase = 0; diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.cpp b/WeaveLoaderRuntime/src/WorldIdRemap.cpp index 6bcbdb5..63fd5af 100644 --- a/WeaveLoaderRuntime/src/WorldIdRemap.cpp +++ b/WeaveLoaderRuntime/src/WorldIdRemap.cpp @@ -20,10 +20,10 @@ namespace { constexpr wchar_t kNamespaceTagKey[] = L"weaveloader:id"; + constexpr wchar_t kNamespaceKindTagKey[] = L"weaveloader:kind"; constexpr char kMissingBlockId[] = "weaveloader:missing_block"; constexpr char kMissingItemId[] = "weaveloader:missing_item"; - constexpr int kMissingBlockNumericId = 255; - constexpr int kMissingItemNumericId = 31999; + constexpr int kMissingBlockVanillaFallbackId = 7; // bedrock constexpr int kMissingBlockDescriptionId = 900000; constexpr int kMissingItemDescriptionId = 900001; constexpr ptrdiff_t kItemIdOffset = 0x20; @@ -38,6 +38,7 @@ namespace 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); + using CompressedTileStorageSet_fn = void (__fastcall *)(void* thisPtr, int x, int y, int z, int val); struct CompoundTagLayout { @@ -55,10 +56,15 @@ namespace std::once_flag s_symbolInitOnce; TagNewTag_fn s_tagNewTag = nullptr; + void* s_tileArray = nullptr; LevelChunkGetTile_fn s_levelChunkGetTile = nullptr; LevelChunkSetTile_fn s_levelChunkSetTile = nullptr; LevelChunkGetPos_fn s_levelChunkGetPos = nullptr; LevelChunkGetHighestNonEmptyY_fn s_levelChunkGetHighestNonEmptyY = nullptr; + CompressedTileStorageSet_fn s_compressedTileStorageSet = nullptr; + + // LevelChunk layout: vtable(8) + byteArray(16) = 24, then lowerBlocks at 24, upperBlocks at 32 + static constexpr size_t kLevelChunkLowerBlocksOffset = 24; bool s_missingPlaceholdersReady = false; int s_chunkRemapLogCount = 0; int s_chunkSaveLogCount = 0; @@ -72,6 +78,8 @@ namespace int z = 0; }; std::unordered_map s_chunkMetaByPtr; + std::unordered_map> s_loadedNamespaceByChunk; + std::unordered_map> s_chunkNamespaceCache; struct ConsoleSavePathLayout { @@ -104,7 +112,6 @@ namespace virtual int closeHandle(void* fileEntry) = 0; virtual void finalizeWrite() = 0; virtual void tick() = 0; - virtual bool doesFileExist(ConsoleSavePathLayout fileName) = 0; }; struct RuntimeFileEntryLayout @@ -122,17 +129,20 @@ namespace return storagePtr && IsReadableRange(storagePtr, sizeof(void*) + sizeof(std::wstring) + sizeof(void*)); } - static std::wstring MakeChunkNamespacePath(const McRegionChunkStorageLayout* storage, int chunkX, int chunkZ) + static std::wstring MakeChunkNamespacePathCoordsOnly(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"; + ss << L"modloader\\blockns_v3\\c." << chunkX << L"." << chunkZ << L".txt"; return ss.str(); } + static std::string MakeChunkCacheKey(const McRegionChunkStorageLayout* storage, int chunkX, int chunkZ) + { + const std::wstring path = MakeChunkNamespacePathCoordsOnly(chunkX, chunkZ); + std::string key(path.begin(), path.end()); + return key; + } + static bool SaveReadAllText(void* saveFile, const std::wstring& path, std::string* outText) { if (!saveFile || !outText || !IsReadableRange(saveFile, sizeof(void*))) @@ -145,11 +155,18 @@ namespace return false; save->setFilePointer(fileEntry, 0, nullptr, FILE_BEGIN); - unsigned int fileSize = 0; - if (IsReadableRange(fileEntry, sizeof(RuntimeFileEntryLayout))) + if (!IsReadableRange(fileEntry, sizeof(RuntimeFileEntryLayout))) { - const auto* entry = reinterpret_cast(fileEntry); - fileSize = entry->length; + save->closeHandle(fileEntry); + return false; + } + const auto* entry = reinterpret_cast(fileEntry); + const unsigned int fileSize = entry->length; + // Hard cap for corrupted metadata; chunk namespace files should stay tiny. + if (fileSize > (1024u * 1024u)) + { + save->closeHandle(fileEntry); + return false; } std::string text; @@ -171,15 +188,6 @@ namespace 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*))) @@ -198,6 +206,11 @@ namespace return ok && bytesWritten == text.size(); } + static bool WriteChunkNamespaceMap( + void* saveFile, + const std::wstring& path, + const std::unordered_map& map); + static bool ReadChunkNamespaceMap( void* saveFile, const std::wstring& path, @@ -207,9 +220,6 @@ namespace return false; outMap->clear(); - if (!SaveFileExists(saveFile, path)) - return false; - std::string text; if (!SaveReadAllText(saveFile, path, &text)) return false; @@ -244,9 +254,46 @@ namespace if (expectedEntries >= 0 && parsedEntries >= expectedEntries) break; } + if (expectedEntries < 0) + { + // Require explicit count header for safety; legacy files can contain stale tail bytes. + outMap->clear(); + return false; + } return true; } + static bool IsLikelyNamespacedId(const std::string& s) + { + if (s.size() < 3 || s.size() > 200) + return false; + if (s.find(':') == std::string::npos) + return false; + for (char c : s) + { + if (c <= 0x20 || c == '\x7f') + return false; + } + return true; + } + + static bool ReadChunkNamespaceMapWithFallback( + const McRegionChunkStorageLayout* storage, + int chunkX, + int chunkZ, + std::unordered_map* outMap) + { + if (!storage || !storage->saveFile || !outMap) + return false; + outMap->clear(); + + // Preferred stable path that does not depend on object layout/prefix decoding. + const std::wstring v3Path = MakeChunkNamespacePathCoordsOnly(chunkX, chunkZ); + if (ReadChunkNamespaceMap(storage->saveFile, v3Path, outMap) && !outMap->empty()) + return true; + return false; + } + static bool WriteChunkNamespaceMap( void* saveFile, const std::wstring& path, @@ -294,6 +341,26 @@ namespace meta.z = chunkZ; } + static void SetLoadedChunkNamespaces(void* chunkPtr, const std::unordered_map& entries) + { + if (!chunkPtr) + return; + std::lock_guard lock(s_chunkMetaMutex); + s_loadedNamespaceByChunk[chunkPtr] = entries; + } + + static bool TryGetLoadedChunkNamespaces(void* chunkPtr, std::unordered_map* outEntries) + { + if (!chunkPtr || !outEntries) + return false; + std::lock_guard lock(s_chunkMetaMutex); + const auto it = s_loadedNamespaceByChunk.find(chunkPtr); + if (it == s_loadedNamespaceByChunk.end()) + return false; + *outEntries = it->second; + return true; + } + static bool TryResolveChunkCoords(void* levelChunkPtr, int* outX, int* outZ) { if (!levelChunkPtr || !s_levelChunkGetPos) @@ -307,11 +374,103 @@ namespace return true; } + static bool IsValidRuntimeTileId(int tileId) + { + if (tileId == 0) + return true; + if (tileId < 0 || tileId > 255) + return false; + if (!s_tileArray || !IsReadableRange(s_tileArray, sizeof(void*))) + return true; // can't validate, don't aggressively rewrite + + // Tile::tiles is a global Tile** pointer variable in this build. + // Symbol points at the variable, so dereference once to get the actual table. + auto** tiles = *reinterpret_cast(s_tileArray); + if (!tiles || !IsReadableRange(tiles, sizeof(void*) * 256)) + return true; // can't validate safely + + return tiles[tileId] != nullptr; + } + static bool IsVanillaId(const std::string& namespacedId) { return namespacedId.rfind("minecraft:", 0) == 0; } + static int ResolveSafeMissingBlockFallbackId() + { + int fallbackId = IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); + if (IsValidRuntimeTileId(fallbackId)) + return fallbackId; + const int missingBlockId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, kMissingBlockId); + if (IsValidRuntimeTileId(missingBlockId)) + return missingBlockId; + if (IsValidRuntimeTileId(kMissingBlockVanillaFallbackId)) + return kMissingBlockVanillaFallbackId; + if (IsValidRuntimeTileId(1)) + return 1; + return 0; + } + + static bool IsTilePointerValid(int tileId) + { + if (tileId == 0) + return true; + if (tileId < 0 || tileId > 255) + return false; + if (!s_tileArray || !IsReadableRange(s_tileArray, sizeof(void*))) + return true; + auto** tiles = *reinterpret_cast(s_tileArray); + if (!tiles || !IsReadableRange(tiles, sizeof(void*) * 256)) + return true; + return tiles[tileId] != nullptr; + } + + static bool SafeSetChunkTile(void* levelChunkPtr, int x, int y, int z, int tileId) + { + if (!s_levelChunkSetTile) + return false; + + // If the current block has no valid Tile pointer, setTileAndData will crash when it + // calls Tile::tiles[old]->onRemoving. Clear it to air first via direct block storage write. + if (s_levelChunkGetTile && s_compressedTileStorageSet && levelChunkPtr && IsReadableRange(levelChunkPtr, kLevelChunkLowerBlocksOffset + 16)) + { +#if defined(_MSC_VER) + __try + { + int current = s_levelChunkGetTile(levelChunkPtr, x, y, z); + if (current != 0 && !IsTilePointerValid(current)) + { + void* blocksPtr = *reinterpret_cast(static_cast(levelChunkPtr) + (y >= 128 ? kLevelChunkLowerBlocksOffset + 8 : kLevelChunkLowerBlocksOffset)); + if (blocksPtr && IsReadableRange(blocksPtr, sizeof(void*))) + { + s_compressedTileStorageSet(blocksPtr, x, y % 128, z, 0); + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + // Ignore - we'll try setTile anyway + } +#endif + } + +#if defined(_MSC_VER) + __try + { + s_levelChunkSetTile(levelChunkPtr, x, y, z, tileId); + return true; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } +#else + s_levelChunkSetTile(levelChunkPtr, x, y, z, tileId); + return true; +#endif + } + static std::string ResolveNamespacedItemId(int numericId) { std::string namespacedId = IdRegistry::Instance().GetStringId(IdRegistry::Type::Item, numericId); @@ -322,7 +481,7 @@ namespace return IdRegistry::Instance().GetStringId(IdRegistry::Type::Block, numericId); } - static int ResolveNumericItemId(const std::string& namespacedId, int oldNumericId) + static int ResolveNumericItemId(const std::string& namespacedId, int oldNumericId, const std::wstring& kind = L"") { if (namespacedId.empty()) return -1; @@ -345,7 +504,7 @@ namespace if (blockId >= 0) return blockId; - if (wasKnownAsBlock) + if (wasKnownAsBlock || kind == L"block") return IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); return IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Item); @@ -456,11 +615,18 @@ namespace namespace WorldIdRemap { + static thread_local int s_remapStage = 0; + void SetTagNewTagSymbol(void* fnPtr) { s_tagNewTag = reinterpret_cast(fnPtr); } + void SetTileArraySymbol(void* tileArrayPtr) + { + s_tileArray = tileArrayPtr; + } + void SetLevelChunkTileSymbols(void* getTileFn, void* setTileFn, void* getPosFn, void* getHighestNonEmptyYFn) { s_levelChunkGetTile = reinterpret_cast(getTileFn); @@ -469,35 +635,51 @@ namespace WorldIdRemap s_levelChunkGetHighestNonEmptyY = reinterpret_cast(getHighestNonEmptyYFn); } + void SetCompressedTileStorageSetSymbol(void* setFn) + { + s_compressedTileStorageSet = reinterpret_cast(setFn); + } + void EnsureMissingPlaceholders() { if (s_missingPlaceholdersReady) return; - IdRegistry::Instance().RegisterVanilla(IdRegistry::Type::Block, kMissingBlockNumericId, kMissingBlockId); - IdRegistry::Instance().RegisterVanilla(IdRegistry::Type::Item, kMissingItemNumericId, kMissingItemId); + const int missingBlockId = IdRegistry::Instance().Register(IdRegistry::Type::Block, kMissingBlockId); + const int missingItemId = IdRegistry::Instance().Register(IdRegistry::Type::Item, 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); + if (missingBlockId < 0) + LogUtil::Log("[WeaveLoader] Failed to register missing block placeholder (no free block IDs)"); + if (missingItemId < 0) + LogUtil::Log("[WeaveLoader] Failed to register missing item placeholder (no free item IDs)"); - ModStrings::Register(kMissingItemDescriptionId, L"Missing Item"); - GameObjectFactory::CreateItem( - kMissingItemNumericId, - 64, - 0, - L"apple", - kMissingItemDescriptionId); - IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Item, kMissingItemNumericId); + if (missingBlockId >= 0) + { + ModStrings::Register(kMissingBlockDescriptionId, L"Missing Block"); + GameObjectFactory::CreateTile( + missingBlockId, + 1, + 1.0f, + 1.0f, + 1, + L"bedrock", + 0.0f, + 15, + kMissingBlockDescriptionId); + IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Block, missingBlockId); + } + + if (missingItemId >= 0) + { + ModStrings::Register(kMissingItemDescriptionId, L"Missing Item"); + GameObjectFactory::CreateItem( + missingItemId, + 64, + 0, + L"apple", + kMissingItemDescriptionId); + IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Item, missingItemId); + } s_missingPlaceholdersReady = true; LogUtil::Log("[WeaveLoader] Missing content placeholders ready: block=%d item=%d", @@ -505,8 +687,9 @@ namespace WorldIdRemap IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Item)); } - int RemapChunkBlockIds(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ) + static int RemapChunkBlockIdsImpl(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ) { + s_remapStage = 1; if (!chunkStoragePtr || !levelChunkPtr || !s_levelChunkGetTile || !s_levelChunkSetTile) return 0; if (!IsValidChunkStorage(chunkStoragePtr)) @@ -519,37 +702,87 @@ namespace WorldIdRemap SetChunkMeta(levelChunkPtr, chunkStoragePtr, chunkX, chunkZ); std::unordered_map entries; - const std::wstring path = MakeChunkNamespacePath(storage, chunkX, chunkZ); - if (!SaveFileExists(storage->saveFile, path)) - return 0; + const std::string cacheKey = MakeChunkCacheKey(storage, chunkX, chunkZ); + { + std::lock_guard lock(s_chunkMetaMutex); + const auto cacheIt = s_chunkNamespaceCache.find(cacheKey); + if (cacheIt != s_chunkNamespaceCache.end()) + entries = cacheIt->second; + } 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; + if (entries.empty()) + { + s_remapStage = 2; + if (ReadChunkNamespaceMapWithFallback(storage, chunkX, chunkZ, &entries) && !entries.empty()) + { + std::lock_guard lock(s_chunkMetaMutex); + s_chunkNamespaceCache[cacheKey] = entries; + } + } + if (!entries.empty()) + SetLoadedChunkNamespaces(levelChunkPtr, entries); + + const int sanitizeFallbackId = ResolveSafeMissingBlockFallbackId(); + int applyMaxY = 127; + if (s_levelChunkGetHighestNonEmptyY) + { + const int highestY = s_levelChunkGetHighestNonEmptyY(levelChunkPtr); + if (highestY >= 0 && highestY < kChunkMaxY) + { + int bound = highestY + 16; + if (bound >= kChunkMaxY) + bound = kChunkMaxY - 1; + applyMaxY = bound; + } + else if (highestY >= 0) + { + applyMaxY = kChunkMaxY - 1; + } + } + + if (applyMaxY > 127) + applyMaxY = 127; - const int missingBlockFallback = IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); int changed = 0; + int skippedInvalid = 0; + int writeExceptions = 0; + s_remapStage = 3; for (const auto& entry : entries) { int x = 0; int y = 0; int z = 0; DecodeBlockIndex(entry.first, &x, &y, &z); + if (y < 0 || y > applyMaxY) + { + ++skippedInvalid; + continue; + } + if (!IsLikelyNamespacedId(entry.second)) + { + ++skippedInvalid; + continue; + } int newId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, entry.second); if (newId < 0) - newId = missingBlockFallback; + newId = sanitizeFallbackId; + if (!IsValidRuntimeTileId(newId)) + newId = sanitizeFallbackId; 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; + ++skippedInvalid; + continue; } + if (!SafeSetChunkTile(levelChunkPtr, x, y, z, newId)) + { + ++writeExceptions; + continue; + } + ++changed; } if (changed > 0 && s_chunkRemapLogCount < 64) @@ -557,9 +790,37 @@ namespace WorldIdRemap ++s_chunkRemapLogCount; LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: remapped %d block ids in chunk", changed); } + if (skippedInvalid > 0 && s_chunkRemapLogCount < 64) + { + ++s_chunkRemapLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: skipped %d invalid namespace entries in chunk", skippedInvalid); + } + if (writeExceptions > 0 && s_chunkRemapLogCount < 64) + { + ++s_chunkRemapLogCount; + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: skipped %d writes due to setTile exceptions", writeExceptions); + } + s_remapStage = 0; return changed; } + int RemapChunkBlockIds(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ) + { +#if defined(_MSC_VER) + __try + { + return RemapChunkBlockIdsImpl(chunkStoragePtr, levelChunkPtr, chunkX, chunkZ); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + LogUtil::Log("[WeaveLoader] WorldIdRemap blocks: exception while remapping chunk x=%d z=%d stage=%d (skipped)", chunkX, chunkZ, s_remapStage); + return 0; + } +#else + return RemapChunkBlockIdsImpl(chunkStoragePtr, levelChunkPtr, chunkX, chunkZ); +#endif + } + void SaveChunkBlockNamespaces(void* chunkStoragePtr, void* levelChunkPtr) { if (!chunkStoragePtr || !levelChunkPtr || !s_levelChunkGetTile) @@ -580,18 +841,18 @@ namespace WorldIdRemap 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); + const std::wstring path = MakeChunkNamespacePathCoordsOnly(meta.x, meta.z); + const std::string cacheKey = MakeChunkCacheKey(storage, meta.x, meta.z); + std::unordered_map loadedEntries; + const bool hadLoadedMap = TryGetLoadedChunkNamespaces(levelChunkPtr, &loadedEntries) && !loadedEntries.empty(); std::unordered_map nextEntries; + const int missingBlockFallback = IdRegistry::Instance().GetMissingFallback(IdRegistry::Type::Block); int maxY = kChunkMaxY - 1; if (s_levelChunkGetHighestNonEmptyY) { const int highestY = s_levelChunkGetHighestNonEmptyY(levelChunkPtr); - if (highestY < 0 && !hadExistingMap) + if (highestY < 0 && !hadLoadedMap) return; if (highestY >= 0 && highestY < kChunkMaxY) maxY = highestY; @@ -604,18 +865,32 @@ namespace WorldIdRemap for (int x = 0; x < kChunkWidth; ++x) { const int blockId = s_levelChunkGetTile(levelChunkPtr, x, y, z); + const int blockIndex = MakeBlockIndex(x, y, z); + + if (blockId == missingBlockFallback) + { + const auto loadedIt = loadedEntries.find(blockIndex); + if (loadedIt != loadedEntries.end() && !loadedIt->second.empty() && loadedIt->second != kMissingBlockId) + { + nextEntries[blockIndex] = loadedIt->second; + continue; + } + continue; + } + 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; + const auto loadedIt = loadedEntries.find(blockIndex); + if (loadedIt != loadedEntries.end() && !loadedIt->second.empty() && loadedIt->second != kMissingBlockId) + { + nextEntries[blockIndex] = loadedIt->second; + continue; + } continue; } @@ -624,10 +899,14 @@ namespace WorldIdRemap } } - if (nextEntries.empty() && !hadExistingMap) + if (nextEntries.empty() && !hadLoadedMap) return; if (!WriteChunkNamespaceMap(storage->saveFile, path, nextEntries)) return; + { + std::lock_guard lock(s_chunkMetaMutex); + s_chunkNamespaceCache[cacheKey] = nextEntries; + } if (s_chunkSaveLogCount < 64) { @@ -650,6 +929,8 @@ namespace WorldIdRemap const std::string namespacedId = ResolveNamespacedItemId(itemId); if (namespacedId.empty() || IsVanillaId(namespacedId)) return; + const bool isBlockBacked = !IdRegistry::Instance().GetStringId(IdRegistry::Type::Block, itemId).empty() && + IdRegistry::Instance().GetStringId(IdRegistry::Type::Item, itemId).empty(); CompoundTagLayout* itemTag = EnsureItemTag(itemInstancePtr); if (!itemTag) @@ -666,6 +947,7 @@ namespace WorldIdRemap const std::wstring namespacedWide(namespacedId.begin(), namespacedId.end()); if (!PutCompoundString(itemTag, kNamespaceTagKey, namespacedWide)) return; + (void)PutCompoundString(itemTag, kNamespaceKindTagKey, isBlockBacked ? L"block" : L"item"); } void RemapItemInstanceFromTag(void* itemInstancePtr, void* compoundTagPtr) @@ -685,9 +967,11 @@ namespace WorldIdRemap std::wstring namespacedWide; if (!TryGetCompoundString(itemTag, kNamespaceTagKey, &namespacedWide) || namespacedWide.empty()) return; + std::wstring kindWide; + (void)TryGetCompoundString(itemTag, kNamespaceKindTagKey, &kindWide); const std::string namespacedId(namespacedWide.begin(), namespacedWide.end()); - int numericId = ResolveNumericItemId(namespacedId, oldItemId); + int numericId = ResolveNumericItemId(namespacedId, oldItemId, kindWide); if (numericId >= 0) *reinterpret_cast(static_cast(itemInstancePtr) + kItemIdOffset) = numericId; } diff --git a/WeaveLoaderRuntime/src/WorldIdRemap.h b/WeaveLoaderRuntime/src/WorldIdRemap.h index 0bc8617..c87732a 100644 --- a/WeaveLoaderRuntime/src/WorldIdRemap.h +++ b/WeaveLoaderRuntime/src/WorldIdRemap.h @@ -5,7 +5,9 @@ namespace WorldIdRemap { void SetTagNewTagSymbol(void* fnPtr); + void SetTileArraySymbol(void* tileArrayPtr); void SetLevelChunkTileSymbols(void* getTileFn, void* setTileFn, void* getPosFn, void* getHighestNonEmptyYFn); + void SetCompressedTileStorageSetSymbol(void* setFn); void EnsureMissingPlaceholders(); int RemapChunkBlockIds(void* chunkStoragePtr, void* levelChunkPtr, int chunkX, int chunkZ); void SaveChunkBlockNamespaces(void* chunkStoragePtr, void* levelChunkPtr);