mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-24 22:54:32 +00:00
fix(runtime): remap modded block ids using save hooks
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<void*>(&GameHooks::Hooked_McRegionChunkStorageLoad),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_McRegionChunkStorageSave),
|
||||
reinterpret_cast<void**>(&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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
#include <Windows.h>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
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<void*, ChunkMeta> 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<std::wstring> 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<RuntimeConsoleSaveFile*>(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<const RuntimeFileEntryLayout*>(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<RuntimeConsoleSaveFile*>(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<RuntimeConsoleSaveFile*>(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<unsigned long>(text.size()), &bytesWritten);
|
||||
save->closeHandle(fileEntry);
|
||||
return ok && bytesWritten == text.size();
|
||||
}
|
||||
|
||||
static bool ReadChunkNamespaceMap(
|
||||
void* saveFile,
|
||||
const std::wstring& path,
|
||||
std::unordered_map<int, std::string>* 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<int, std::string>& 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<std::mutex> 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<std::mutex> 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<const ChunkPosLayout*>(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<TagNewTag_fn>(fnPtr);
|
||||
}
|
||||
|
||||
void SetLevelChunkTileSymbols(void* getTileFn, void* setTileFn, void* getPosFn, void* getHighestNonEmptyYFn)
|
||||
{
|
||||
s_levelChunkGetTile = reinterpret_cast<LevelChunkGetTile_fn>(getTileFn);
|
||||
s_levelChunkSetTile = reinterpret_cast<LevelChunkSetTile_fn>(setTileFn);
|
||||
s_levelChunkGetPos = reinterpret_cast<LevelChunkGetPos_fn>(getPosFn);
|
||||
s_levelChunkGetHighestNonEmptyY = reinterpret_cast<LevelChunkGetHighestNonEmptyY_fn>(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<McRegionChunkStorageLayout*>(chunkStoragePtr);
|
||||
if (!storage->saveFile)
|
||||
return 0;
|
||||
|
||||
SetChunkMeta(levelChunkPtr, chunkStoragePtr, chunkX, chunkZ);
|
||||
|
||||
std::unordered_map<int, std::string> 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<McRegionChunkStorageLayout*>(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<int, std::string> previousEntries;
|
||||
if (hadExistingMap)
|
||||
ReadChunkNamespaceMap(storage->saveFile, path, &previousEntries);
|
||||
|
||||
std::unordered_map<int, std::string> 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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user