#include "WorldIdRemap.h" #include "GameObjectFactory.h" #include "IdRegistry.h" #include "LogUtil.h" #include "ModStrings.h" #include "PdbParser.h" #include #include #include #include #include #include #include #include #include #include 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 kMissingBlockVanillaFallbackId = 7; // bedrock 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; 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); using CompressedTileStorageSet_fn = void (__fastcall *)(void* thisPtr, int x, int y, int z, int val); 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; 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; int s_chunkIoLogCount = 0; std::mutex s_chunkMetaMutex; struct ChunkMeta { void* storagePtr = nullptr; int x = 0; int z = 0; }; std::unordered_map s_chunkMetaByPtr; std::unordered_map> s_loadedNamespaceByChunk; std::unordered_map> s_chunkNamespaceCache; 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; }; 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 MakeChunkNamespacePathCoordsOnly(int chunkX, int chunkZ) { std::wstringstream ss; 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*))) 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); if (!IsReadableRange(fileEntry, sizeof(RuntimeFileEntryLayout))) { 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; 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 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 WriteChunkNamespaceMap( void* saveFile, const std::wstring& path, const std::unordered_map& map); static bool ReadChunkNamespaceMap( void* saveFile, const std::wstring& path, std::unordered_map* outMap) { if (!outMap) return false; outMap->clear(); 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; } 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, 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 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) 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 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); 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, const std::wstring& kind = L"") { 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 || kind == L"block") 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 { 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); s_levelChunkSetTile = reinterpret_cast(setTileFn); s_levelChunkGetPos = reinterpret_cast(getPosFn); s_levelChunkGetHighestNonEmptyY = reinterpret_cast(getHighestNonEmptyYFn); } void SetCompressedTileStorageSetSymbol(void* setFn) { s_compressedTileStorageSet = reinterpret_cast(setFn); } void EnsureMissingPlaceholders() { if (s_missingPlaceholdersReady) return; const int missingBlockId = IdRegistry::Instance().Register(IdRegistry::Type::Block, kMissingBlockId); const int missingItemId = IdRegistry::Instance().Register(IdRegistry::Type::Item, kMissingItemId); 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)"); if (missingBlockId >= 0) { ModStrings::Register(kMissingBlockDescriptionId, L"Missing Block"); GameObjectFactory::CreateTile( missingBlockId, 1, 1.0f, 1.0f, 1, L"weaveloader.api:block/missing_block", 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"weaveloader.api:item/missing_item", kMissingItemDescriptionId); IdRegistry::Instance().SetMissingFallback(IdRegistry::Type::Item, missingItemId); } 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)); } 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)) return 0; auto* storage = reinterpret_cast(chunkStoragePtr); if (!storage->saveFile) return 0; SetChunkMeta(levelChunkPtr, chunkStoragePtr, chunkX, chunkZ); std::unordered_map entries; 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 (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; 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 = sanitizeFallbackId; if (!IsValidRuntimeTileId(newId)) newId = sanitizeFallbackId; if (newId < 0 || newId > 255) { ++skippedInvalid; continue; } if (!SafeSetChunkTile(levelChunkPtr, x, y, z, newId)) { ++writeExceptions; continue; } ++changed; } if (changed > 0 && s_chunkRemapLogCount < 64) { ++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) 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 = 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 && !hadLoadedMap) 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); 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; if (namespacedId == kMissingBlockId) { const auto loadedIt = loadedEntries.find(blockIndex); if (loadedIt != loadedEntries.end() && !loadedIt->second.empty() && loadedIt->second != kMissingBlockId) { nextEntries[blockIndex] = loadedIt->second; continue; } continue; } nextEntries[blockIndex] = std::move(namespacedId); } } } 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) { ++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(); 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; 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) return; // Never let placeholder IDs overwrite a previously saved real namespace. if (namespacedId == kMissingItemId || namespacedId == kMissingBlockId) { std::wstring existingNamespace; if (TryGetCompoundString(itemTag, kNamespaceTagKey, &existingNamespace) && !existingNamespace.empty()) return; } 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) { 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; std::wstring kindWide; (void)TryGetCompoundString(itemTag, kNamespaceKindTagKey, &kindWide); const std::string namespacedId(namespacedWide.begin(), namespacedWide.end()); int numericId = ResolveNumericItemId(namespacedId, oldItemId, kindWide); if (numericId >= 0) *reinterpret_cast(static_cast(itemInstancePtr) + kItemIdOffset) = numericId; } }