diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 4fe759c..1919663 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -15,8 +15,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -33,6 +35,7 @@ namespace GameHooks GetString_fn Original_GetString = nullptr; GetResourceAsStream_fn Original_GetResourceAsStream = nullptr; LoadUVs_fn Original_LoadUVs = nullptr; + PreStitchedTextureMapStitch_fn Original_PreStitchedTextureMapStitch = nullptr; RegisterIcon_fn Original_RegisterIcon = nullptr; ItemInstanceGetIcon_fn Original_ItemInstanceGetIcon = nullptr; EntityRendererBindTextureResource_fn Original_EntityRendererBindTextureResource = nullptr; @@ -83,10 +86,17 @@ namespace GameHooks TexturesBindTextureResource_fn Original_TexturesBindTextureResource = nullptr; TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName = nullptr; TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex = nullptr; + TexturesReadImage_fn Original_TexturesReadImage = nullptr; StitchedTextureUV_fn Original_StitchedGetU0 = nullptr; StitchedTextureUV_fn Original_StitchedGetU1 = nullptr; StitchedTextureUV_fn Original_StitchedGetV0 = nullptr; StitchedTextureUV_fn Original_StitchedGetV1 = nullptr; + BufferedImageCtorFile_fn Original_BufferedImageCtorFile = nullptr; + BufferedImageCtorDLCPack_fn Original_BufferedImageCtorDLCPack = nullptr; + TextureManagerCreateTexture_fn Original_TextureManagerCreateTexture = nullptr; + TextureTransferFromImage_fn Original_TextureTransferFromImage = nullptr; + TexturePackGetImageResource_fn Original_AbstractTexturePackGetImageResource = nullptr; + TexturePackGetImageResource_fn Original_DLCTexturePackGetImageResource = nullptr; static int s_itemMineBlockHookCalls = 0; static void* s_currentLevel = nullptr; static thread_local void* s_activeUseLevel = nullptr; @@ -123,6 +133,7 @@ namespace GameHooks static thread_local bool s_hasForcedBillboardRoute = false; static thread_local int s_forcedBillboardAtlas = -1; static thread_local int s_forcedBillboardPage = 0; + static thread_local int s_activeStitchAtlasType = -1; static int s_animatedTextureGuardLogCount = 0; struct TextureNameArrayNative @@ -315,6 +326,14 @@ namespace GameHooks return lower; } + static bool EndsWithPath(const std::wstring& path, const wchar_t* suffix) + { + size_t pathLen = path.size(); + size_t suffLen = wcslen(suffix); + if (suffLen > pathLen) return false; + return path.compare(pathLen - suffLen, suffLen, suffix) == 0; + } + static int DetectAtlasTypeFromResource(void* resourcePtr) { if (!resourcePtr) @@ -1341,9 +1360,23 @@ namespace GameHooks return Original_HalfSlabCloneTileId ? Original_HalfSlabCloneTileId(thisPtr, level, x, y, z) : TryReadTileId(thisPtr); } + void __fastcall Hooked_PreStitchedTextureMapStitch(void* thisPtr) + { + const int prevAtlasType = s_activeStitchAtlasType; + int iconType = -1; + if (thisPtr && IsReadableRange(static_cast(thisPtr) + 8, sizeof(int))) + iconType = *reinterpret_cast(static_cast(thisPtr) + 8); + s_activeStitchAtlasType = iconType; + if (Original_PreStitchedTextureMapStitch) + Original_PreStitchedTextureMapStitch(thisPtr); + s_activeStitchAtlasType = prevAtlasType; + } + void __fastcall Hooked_LoadUVs(void* thisPtr) { LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: ENTER (textureMap=%p)", thisPtr); + if (thisPtr && IsReadableRange(static_cast(thisPtr) + 8, sizeof(int))) + s_activeStitchAtlasType = *reinterpret_cast(static_cast(thisPtr) + 8); if (Original_LoadUVs) Original_LoadUVs(thisPtr); LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: original returned, creating mod icons"); @@ -1352,40 +1385,73 @@ namespace GameHooks LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: DONE"); } + static float ApplyAtlasScale(void* iconPtr, float value, bool isU, bool isModIcon) + { + if (isModIcon) + return value; + + int atlasType = -1; + if (!ModAtlas::GetIconAtlasType(iconPtr, atlasType)) + { + if (s_activeStitchAtlasType >= 0) + atlasType = s_activeStitchAtlasType; + else + return value; + } + + float uScale = 1.0f; + float vScale = 1.0f; + if (!ModAtlas::GetAtlasScale(atlasType, uScale, vScale)) + return value; + + const float scale = isU ? uScale : vScale; + if (scale > 0.9995f && scale < 1.0005f) + return value; + return value * scale; + } + float __fastcall Hooked_StitchedGetU0(void* thisPtr, bool adjust) { int atlasType = -1; int page = 0; - if (ModAtlas::TryGetIconRoute(thisPtr, atlasType, page) && atlasType == 0) + const bool isModIcon = ModAtlas::TryGetIconRoute(thisPtr, atlasType, page); + if (isModIcon && atlasType == 0 && page > 0) ModAtlas::NotifyIconSampled(thisPtr); - return Original_StitchedGetU0 ? Original_StitchedGetU0(thisPtr, adjust) : 0.0f; + float value = Original_StitchedGetU0 ? Original_StitchedGetU0(thisPtr, adjust) : 0.0f; + return ApplyAtlasScale(thisPtr, value, true, isModIcon); } float __fastcall Hooked_StitchedGetU1(void* thisPtr, bool adjust) { int atlasType = -1; int page = 0; - if (ModAtlas::TryGetIconRoute(thisPtr, atlasType, page) && atlasType == 0) + const bool isModIcon = ModAtlas::TryGetIconRoute(thisPtr, atlasType, page); + if (isModIcon && atlasType == 0 && page > 0) ModAtlas::NotifyIconSampled(thisPtr); - return Original_StitchedGetU1 ? Original_StitchedGetU1(thisPtr, adjust) : 0.0f; + float value = Original_StitchedGetU1 ? Original_StitchedGetU1(thisPtr, adjust) : 0.0f; + return ApplyAtlasScale(thisPtr, value, true, isModIcon); } float __fastcall Hooked_StitchedGetV0(void* thisPtr, bool adjust) { int atlasType = -1; int page = 0; - if (ModAtlas::TryGetIconRoute(thisPtr, atlasType, page) && atlasType == 0) + const bool isModIcon = ModAtlas::TryGetIconRoute(thisPtr, atlasType, page); + if (isModIcon && atlasType == 0 && page > 0) ModAtlas::NotifyIconSampled(thisPtr); - return Original_StitchedGetV0 ? Original_StitchedGetV0(thisPtr, adjust) : 0.0f; + float value = Original_StitchedGetV0 ? Original_StitchedGetV0(thisPtr, adjust) : 0.0f; + return ApplyAtlasScale(thisPtr, value, false, isModIcon); } float __fastcall Hooked_StitchedGetV1(void* thisPtr, bool adjust) { int atlasType = -1; int page = 0; - if (ModAtlas::TryGetIconRoute(thisPtr, atlasType, page) && atlasType == 0) + const bool isModIcon = ModAtlas::TryGetIconRoute(thisPtr, atlasType, page); + if (isModIcon && atlasType == 0 && page > 0) ModAtlas::NotifyIconSampled(thisPtr); - return Original_StitchedGetV1 ? Original_StitchedGetV1(thisPtr, adjust) : 0.0f; + float value = Original_StitchedGetV1 ? Original_StitchedGetV1(thisPtr, adjust) : 0.0f; + return ApplyAtlasScale(thisPtr, value, false, isModIcon); } void __fastcall Hooked_TexturesBindTextureResource(void* thisPtr, void* resourcePtr) @@ -1498,19 +1564,152 @@ namespace GameHooks return Original_TexturesLoadTextureByIndex(thisPtr, idx); } + static bool IsModLoaderGeneratedPath(const std::wstring& lower) + { + return (lower.find(L"/mods/modloader/generated/") != std::wstring::npos) || + (lower.find(L"/modloader/") != std::wstring::npos); + } + + static std::wstring ToLowerSimple(const std::wstring& s) + { + std::wstring lower; + lower.reserve(s.size()); + for (wchar_t ch : s) + lower.push_back((wchar_t)towlower(ch)); + return lower; + } + + static std::unordered_map s_textureNames; + + void* __fastcall Hooked_TexturesReadImage(void* thisPtr, int texId, const std::wstring& name) + { + if (!Original_TexturesReadImage) + return nullptr; + void* img = Original_TexturesReadImage(thisPtr, texId, name); + if (!img) + return img; + + std::wstring lower = NormalizeLowerPath(name); + if (IsModLoaderGeneratedPath(lower)) + return img; + + if (EndsWithPath(lower, L"terrain.png")) + { + ModAtlas::OverrideAtlasFromBufferedImage(0, img); + } + else if (EndsWithPath(lower, L"items.png")) + { + ModAtlas::OverrideAtlasFromBufferedImage(1, img); + } + return img; + } + + void __fastcall Hooked_BufferedImageCtorFile(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive) + { + if (Original_BufferedImageCtorFile) + Original_BufferedImageCtorFile(thisPtr, file, filenameHasExtension, bTitleUpdateTexture, drive); + + // Intentionally left empty: handled in Textures::readImage hook to avoid + // constructor-level crashes during boot. + } + + void __fastcall Hooked_BufferedImageCtorDLCPack(void* thisPtr, void* dlcPack, const std::wstring& file, bool filenameHasExtension) + { + if (Original_BufferedImageCtorDLCPack) + Original_BufferedImageCtorDLCPack(thisPtr, dlcPack, file, filenameHasExtension); + + // Intentionally left empty: handled in Textures::readImage hook to avoid + // constructor-level crashes during boot. + } + + void* __fastcall Hooked_TextureManagerCreateTexture(void* thisPtr, const std::wstring& name, int mode, int width, int height, int wrap, int format, int minFilter, int magFilter, bool mipmap, void* image) + { + if (!Original_TextureManagerCreateTexture) + return nullptr; + + void* tex = Original_TextureManagerCreateTexture(thisPtr, name, mode, width, height, wrap, format, minFilter, magFilter, mipmap, image); + if (tex) + { + std::wstring lower = ToLowerSimple(name); + if (lower == L"terrain" || lower == L"items") + s_textureNames[tex] = lower; + } + return tex; + } + + void __fastcall Hooked_TextureTransferFromImage(void* thisPtr, void* image) + { + if (!Original_TextureTransferFromImage) + return; + + auto it = s_textureNames.find(thisPtr); + if (it != s_textureNames.end() && image) + { + if (it->second == L"terrain") + ModAtlas::OverrideAtlasFromBufferedImage(0, image); + else if (it->second == L"items") + ModAtlas::OverrideAtlasFromBufferedImage(1, image); + } + + Original_TextureTransferFromImage(thisPtr, image); + } + + static void TryOverrideAtlasFromPackImage(const std::wstring& file, void* image) + { + if (!image) return; + std::wstring lower = NormalizeLowerPath(file); + if (IsModLoaderGeneratedPath(lower)) + return; + + if (EndsWithPath(lower, L"terrain.png")) + { + ModAtlas::OverrideAtlasFromBufferedImage(0, image); + } + else if (EndsWithPath(lower, L"items.png")) + { + ModAtlas::OverrideAtlasFromBufferedImage(1, image); + } + } + + void* __fastcall Hooked_AbstractTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive) + { + if (!Original_AbstractTexturePackGetImageResource) + return nullptr; + void* img = Original_AbstractTexturePackGetImageResource(thisPtr, file, filenameHasExtension, bTitleUpdateTexture, drive); + TryOverrideAtlasFromPackImage(file, img); + return img; + } + + void* __fastcall Hooked_DLCTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive) + { + if (!Original_DLCTexturePackGetImageResource) + return nullptr; + void* img = Original_DLCTexturePackGetImageResource(thisPtr, file, filenameHasExtension, bTitleUpdateTexture, drive); + TryOverrideAtlasFromPackImage(file, img); + return img; + } + static int s_registerIconCallCount = 0; void* __fastcall Hooked_RegisterIcon(void* thisPtr, const std::wstring& name) { s_registerIconCallCount++; + int iconType = -1; + if (thisPtr && IsReadableRange(static_cast(thisPtr) + 8, sizeof(int))) + iconType = *reinterpret_cast(static_cast(thisPtr) + 8); + void* modIcon = ModAtlas::LookupModIcon(name); if (modIcon) { + if (iconType >= 0) + ModAtlas::NoteIconAtlasType(modIcon, iconType); LogUtil::Log("[WeaveLoader] registerIcon #%d: '%ls' -> MOD ICON %p", s_registerIconCallCount, name.c_str(), modIcon); return modIcon; } void* result = Original_RegisterIcon ? Original_RegisterIcon(thisPtr, name) : nullptr; + if (result && iconType >= 0) + ModAtlas::NoteIconAtlasType(result, iconType); if (s_registerIconCallCount <= 30 || !result) { LogUtil::Log("[WeaveLoader] registerIcon #%d: '%ls' -> vanilla %p", @@ -2034,37 +2233,58 @@ namespace GameHooks void* Hooked_GetResourceAsStream(const void* fileName) { const std::wstring* path = static_cast(fileName); - if (ModAtlas::HasModTextures() && Original_GetResourceAsStream && path) + if (!Original_GetResourceAsStream || !path) + return Original_GetResourceAsStream ? Original_GetResourceAsStream(fileName) : nullptr; + + int atlasType = -1; + int page = 0; + if (ParseVirtualAtlasRequest(*path, atlasType, page)) { - std::string terrainPath = ModAtlas::GetMergedTerrainPath(); - std::string itemsPath = ModAtlas::GetMergedItemsPath(); - if (!terrainPath.empty() && path->find(L"terrain.png") != std::wstring::npos) + std::string atlasPath = ModAtlas::GetVirtualPagePath(atlasType, page); + if (atlasPath.empty()) + atlasPath = ModAtlas::GetMergedPagePath(atlasType, page); + if (!atlasPath.empty()) { - std::wstring ourPath(terrainPath.begin(), terrainPath.end()); - LogUtil::Log("[WeaveLoader] getResourceAsStream: redirecting terrain.png to merged atlas"); + std::wstring ourPath(atlasPath.begin(), atlasPath.end()); return Original_GetResourceAsStream(&ourPath); } - if (!itemsPath.empty() && path->find(L"items.png") != std::wstring::npos) + } + + std::wstring lower = NormalizeLowerPath(*path); + const bool isGenerated = + (lower.find(L"/modloader/") != std::wstring::npos) || + (lower.find(L"/mods/modloader/generated/") != std::wstring::npos); + + if (!isGenerated) + { + if (EndsWithPath(lower, L"terrain.png")) { - std::wstring ourPath(itemsPath.begin(), itemsPath.end()); - LogUtil::Log("[WeaveLoader] getResourceAsStream: redirecting items.png to merged atlas"); - return Original_GetResourceAsStream(&ourPath); - } - int atlasType = -1; - int page = 0; - if (ParseVirtualAtlasRequest(*path, atlasType, page)) - { - std::string atlasPath = ModAtlas::GetVirtualPagePath(atlasType, page); - if (atlasPath.empty()) - atlasPath = ModAtlas::GetMergedPagePath(atlasType, page); - if (!atlasPath.empty()) + ModAtlas::SetOverrideAtlasPath(0, std::string(path->begin(), path->end())); + ModAtlas::EnsureAtlasesBuilt(); + std::string terrainPath = ModAtlas::GetMergedTerrainPath(); + if (!terrainPath.empty()) { - std::wstring ourPath(atlasPath.begin(), atlasPath.end()); + std::wstring ourPath(terrainPath.begin(), terrainPath.end()); + LogUtil::Log("[WeaveLoader] getResourceAsStream: redirecting terrain.png to merged atlas"); + return Original_GetResourceAsStream(&ourPath); + } + } + + if (EndsWithPath(lower, L"items.png")) + { + ModAtlas::SetOverrideAtlasPath(1, std::string(path->begin(), path->end())); + ModAtlas::EnsureAtlasesBuilt(); + std::string itemsPath = ModAtlas::GetMergedItemsPath(); + if (!itemsPath.empty()) + { + std::wstring ourPath(itemsPath.begin(), itemsPath.end()); + LogUtil::Log("[WeaveLoader] getResourceAsStream: redirecting items.png to merged atlas"); return Original_GetResourceAsStream(&ourPath); } } } - return Original_GetResourceAsStream ? Original_GetResourceAsStream(fileName) : nullptr; + + return Original_GetResourceAsStream(fileName); } static bool s_loggedGetString = false; @@ -2134,6 +2354,7 @@ namespace GameHooks ModAtlas::SetVirtualAtlasDirectory(virtualAtlasDir); HMODULE hMod = nullptr; + std::string modsPath; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)&Hooked_MinecraftInit, &hMod) && hMod) { char dllPath[MAX_PATH] = { 0 }; @@ -2144,14 +2365,15 @@ namespace GameHooks if (dllPos != std::string::npos) { dllDir.resize(dllPos + 1); - std::string modsPath = dllDir + "mods"; - ModAtlas::BuildAtlases(modsPath, gameResPath); + modsPath = dllDir + "mods"; goto atlas_done; } } } - ModAtlas::BuildAtlases(base + "mods", gameResPath); + modsPath = base + "mods"; atlas_done: + ModAtlas::SetBasePaths(modsPath, gameResPath); + ModAtlas::EnsureAtlasesBuilt(); // Redirect terrain.png/items.png file opens to our merged atlases // so the game loads mod textures without modifying vanilla files. diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index 996c384..92daa69 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -17,6 +17,7 @@ typedef void (WINAPI *OutputDebugStringA_fn)(const char* lpOutputString); typedef const wchar_t* (*GetString_fn)(int); typedef void* (*GetResourceAsStream_fn)(const void* fileName); typedef void (__fastcall *LoadUVs_fn)(void* thisPtr); +typedef void (__fastcall *PreStitchedTextureMapStitch_fn)(void* thisPtr); typedef void* (__fastcall *RegisterIcon_fn)(void* thisPtr, const std::wstring& name); typedef void* (__fastcall *ItemInstanceGetIcon_fn)(void* thisPtr); typedef void (__fastcall *EntityRendererBindTextureResource_fn)(void* thisPtr, void* resourcePtr); @@ -63,7 +64,13 @@ typedef void (__fastcall *AbstractContainerMenuBroadcastChanges_fn)(void* thisPt typedef void (__fastcall *TexturesBindTextureResource_fn)(void* thisPtr, void* resourcePtr); typedef int (__fastcall *TexturesLoadTextureByName_fn)(void* thisPtr, int texId, const std::wstring& resourceName); typedef int (__fastcall *TexturesLoadTextureByIndex_fn)(void* thisPtr, int idx); +typedef void* (__fastcall *TexturesReadImage_fn)(void* thisPtr, int texId, const std::wstring& name); typedef float (__fastcall *StitchedTextureUV_fn)(void* thisPtr, bool adjust); +typedef void (__fastcall *BufferedImageCtorFile_fn)(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); +typedef void (__fastcall *BufferedImageCtorDLCPack_fn)(void* thisPtr, void* dlcPack, const std::wstring& file, bool filenameHasExtension); +typedef void* (__fastcall *TextureManagerCreateTexture_fn)(void* thisPtr, const std::wstring& name, int mode, int width, int height, int wrap, int format, int minFilter, int magFilter, bool mipmap, void* image); +typedef void (__fastcall *TextureTransferFromImage_fn)(void* thisPtr, void* image); +typedef void* (__fastcall *TexturePackGetImageResource_fn)(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); namespace GameHooks { @@ -78,6 +85,7 @@ namespace GameHooks extern GetString_fn Original_GetString; extern GetResourceAsStream_fn Original_GetResourceAsStream; extern LoadUVs_fn Original_LoadUVs; + extern PreStitchedTextureMapStitch_fn Original_PreStitchedTextureMapStitch; extern RegisterIcon_fn Original_RegisterIcon; extern ItemInstanceGetIcon_fn Original_ItemInstanceGetIcon; extern EntityRendererBindTextureResource_fn Original_EntityRendererBindTextureResource; @@ -128,10 +136,17 @@ namespace GameHooks extern TexturesBindTextureResource_fn Original_TexturesBindTextureResource; extern TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName; extern TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex; + extern TexturesReadImage_fn Original_TexturesReadImage; extern StitchedTextureUV_fn Original_StitchedGetU0; extern StitchedTextureUV_fn Original_StitchedGetU1; extern StitchedTextureUV_fn Original_StitchedGetV0; extern StitchedTextureUV_fn Original_StitchedGetV1; + extern BufferedImageCtorFile_fn Original_BufferedImageCtorFile; + extern BufferedImageCtorDLCPack_fn Original_BufferedImageCtorDLCPack; + extern TextureManagerCreateTexture_fn Original_TextureManagerCreateTexture; + extern TextureTransferFromImage_fn Original_TextureTransferFromImage; + extern TexturePackGetImageResource_fn Original_AbstractTexturePackGetImageResource; + extern TexturePackGetImageResource_fn Original_DLCTexturePackGetImageResource; void Hooked_RunStaticCtors(); void __fastcall Hooked_MinecraftTick(void* thisPtr, bool bFirst, bool bUpdateTextures); @@ -144,6 +159,7 @@ namespace GameHooks const wchar_t* Hooked_GetString(int id); void* Hooked_GetResourceAsStream(const void* fileName); void __fastcall Hooked_LoadUVs(void* thisPtr); + void __fastcall Hooked_PreStitchedTextureMapStitch(void* thisPtr); void* __fastcall Hooked_RegisterIcon(void* thisPtr, const std::wstring& name); void* __fastcall Hooked_ItemInstanceGetIcon(void* thisPtr); void __fastcall Hooked_EntityRendererBindTextureResource(void* thisPtr, void* resourcePtr); @@ -194,10 +210,17 @@ namespace GameHooks void __fastcall Hooked_TexturesBindTextureResource(void* thisPtr, void* resourcePtr); int __fastcall Hooked_TexturesLoadTextureByName(void* thisPtr, int texId, const std::wstring& resourceName); int __fastcall Hooked_TexturesLoadTextureByIndex(void* thisPtr, int idx); + void* __fastcall Hooked_TexturesReadImage(void* thisPtr, int texId, const std::wstring& name); float __fastcall Hooked_StitchedGetU0(void* thisPtr, bool adjust); float __fastcall Hooked_StitchedGetU1(void* thisPtr, bool adjust); float __fastcall Hooked_StitchedGetV0(void* thisPtr, bool adjust); float __fastcall Hooked_StitchedGetV1(void* thisPtr, bool adjust); + void __fastcall Hooked_BufferedImageCtorFile(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); + void __fastcall Hooked_BufferedImageCtorDLCPack(void* thisPtr, void* dlcPack, const std::wstring& file, bool filenameHasExtension); + void* __fastcall Hooked_TextureManagerCreateTexture(void* thisPtr, const std::wstring& name, int mode, int width, int height, int wrap, int format, int minFilter, int magFilter, bool mipmap, void* image); + void __fastcall Hooked_TextureTransferFromImage(void* thisPtr, void* image); + void* __fastcall Hooked_AbstractTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); + void* __fastcall Hooked_DLCTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); void SetAtlasLocationPointers(void* blocksLocation, void* itemsLocation); void SetTileTilesArray(void* tilesArray); void SetSummonSymbols(void* levelAddEntity, diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index 9b2537a..6f1a9fb 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -722,6 +722,79 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.pTexturesReadImage) + { + if (MH_CreateHook(symbols.pTexturesReadImage, + reinterpret_cast(&GameHooks::Hooked_TexturesReadImage), + reinterpret_cast(&GameHooks::Original_TexturesReadImage)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Textures::readImage"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Textures::readImage"); + } + } + + if (symbols.pTextureManagerCreateTexture) + { + if (MH_CreateHook(symbols.pTextureManagerCreateTexture, + reinterpret_cast(&GameHooks::Hooked_TextureManagerCreateTexture), + reinterpret_cast(&GameHooks::Original_TextureManagerCreateTexture)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook TextureManager::createTexture"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked TextureManager::createTexture"); + } + } + + if (symbols.pTextureTransferFromImage) + { + if (MH_CreateHook(symbols.pTextureTransferFromImage, + reinterpret_cast(&GameHooks::Hooked_TextureTransferFromImage), + reinterpret_cast(&GameHooks::Original_TextureTransferFromImage)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Texture::transferFromImage"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Texture::transferFromImage"); + } + } + + if (symbols.pAbstractTexturePackGetImageResource) + { + if (MH_CreateHook(symbols.pAbstractTexturePackGetImageResource, + reinterpret_cast(&GameHooks::Hooked_AbstractTexturePackGetImageResource), + reinterpret_cast(&GameHooks::Original_AbstractTexturePackGetImageResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook AbstractTexturePack::getImageResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked AbstractTexturePack::getImageResource"); + } + } + + if (symbols.pDLCTexturePackGetImageResource) + { + if (MH_CreateHook(symbols.pDLCTexturePackGetImageResource, + reinterpret_cast(&GameHooks::Hooked_DLCTexturePackGetImageResource), + reinterpret_cast(&GameHooks::Original_DLCTexturePackGetImageResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook DLCTexturePack::getImageResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked DLCTexturePack::getImageResource"); + } + } + + // BufferedImage constructor hooks disabled: the work is now handled in + // Textures::readImage for stability during boot. + if (symbols.pStitchedGetU0) { if (MH_CreateHook(symbols.pStitchedGetU0, @@ -790,6 +863,20 @@ bool HookManager::Install(const SymbolResolver& symbols) symbols.pEntityLerpMotion, symbols.pEntitySetPos); + if (symbols.pPreStitchedTextureMapStitch) + { + if (MH_CreateHook(symbols.pPreStitchedTextureMapStitch, + reinterpret_cast(&GameHooks::Hooked_PreStitchedTextureMapStitch), + reinterpret_cast(&GameHooks::Original_PreStitchedTextureMapStitch)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook PreStitchedTextureMap::stitch"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked PreStitchedTextureMap::stitch (atlas type tracking)"); + } + } + if (symbols.pLoadUVs && symbols.pSimpleIconCtor && symbols.pOperatorNew) { ModAtlas::SetInjectSymbols(symbols.pSimpleIconCtor, symbols.pOperatorNew); diff --git a/WeaveLoaderRuntime/src/ModAtlas.cpp b/WeaveLoaderRuntime/src/ModAtlas.cpp index ad34887..f47103e 100644 --- a/WeaveLoaderRuntime/src/ModAtlas.cpp +++ b/WeaveLoaderRuntime/src/ModAtlas.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,18 @@ namespace ModAtlas { static std::string s_mergedDir; static std::string s_virtualAtlasDir; + static std::string s_modsPath; + static std::string s_gameResPath; + static std::string s_overrideTerrainPath; + static std::string s_overrideItemsPath; + static std::string s_lastTerrainBasePath; + static std::string s_lastItemsBasePath; + static int s_lastTerrainBaseWidth = 0; + static int s_lastTerrainBaseHeight = 0; + static int s_lastItemsBaseWidth = 0; + static int s_lastItemsBaseHeight = 0; + static void* s_lastTerrainImagePtr = nullptr; + static void* s_lastItemsImagePtr = nullptr; static std::string s_mergedTerrainPage1Path; static std::string s_mergedItemsPage1Path; static std::vector s_blockEntries; @@ -29,15 +42,32 @@ namespace ModAtlas static bool s_hasItemsPage0Mods = false; static int s_terrainPages = 0; static int s_itemPages = 0; + static int s_terrainBaseRows = 32; + static int s_terrainRows = 32; + static float s_terrainVScale = 1.0f; + static int s_itemRows = 16; + static float s_itemVScale = 1.0f; static void* s_simpleIconCtor = nullptr; static void* (*s_operatorNew)(size_t) = nullptr; static std::unordered_map s_modIcons; + static std::unordered_map s_iconAtlasType; struct IconRouteInfo { int atlasType; int page; }; static std::unordered_map s_iconRoutes; static RegisterIcon_fn s_originalRegisterIcon = nullptr; static thread_local bool s_hasPendingPage = false; static thread_local int s_pendingAtlasType = -1; static thread_local int s_pendingPage = 0; + static thread_local bool s_buildInProgress = false; + static bool s_applyMergedToBufferedImage = false; + + // CreateFileW hook: redirect game file opens to merged atlases + static std::wstring s_mergedTerrainW; + static std::wstring s_mergedItemsW; + static std::wstring s_vanillaTerrainW; + static std::wstring s_vanillaItemsW; + + typedef HANDLE (WINAPI *CreateFileW_fn)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + static CreateFileW_fn s_originalCreateFileW = nullptr; // iconType is at offset 8 in PreStitchedTextureMap (verified via getIconType disassembly) @@ -109,6 +139,23 @@ namespace ModAtlas return *data != nullptr; } + static bool SavePngToBytes(const unsigned char* img, int w, int h, + std::vector& outBytes) + { + outBytes.clear(); + if (!img || w <= 0 || h <= 0) return false; + + auto writeFunc = [](void* context, void* data, int size) + { + auto* bytes = reinterpret_cast*>(context); + const unsigned char* p = reinterpret_cast(data); + bytes->insert(bytes->end(), p, p + size); + }; + + int ok = stbi_write_png_to_func(writeFunc, &outBytes, w, h, 4, img, w * 4); + return ok != 0 && !outBytes.empty(); + } + static bool FileExists(const std::string& path) { DWORD attr = GetFileAttributesA(path.c_str()); @@ -128,15 +175,16 @@ namespace ModAtlas return baseDir + "\\" + stem + "_p" + std::to_string(page) + ".png"; } - static void Blit16x16(unsigned char* dst, int dstW, int dstH, int dstX, int dstY, - const unsigned char* src, int srcW, int srcH) + static void BlitIcon(unsigned char* dst, int dstW, int dstH, int dstX, int dstY, int iconSize, + const unsigned char* src, int srcW, int srcH) { - for (int y = 0; y < 16; y++) + if (iconSize <= 0) return; + for (int y = 0; y < iconSize; y++) { - for (int x = 0; x < 16; x++) + for (int x = 0; x < iconSize; x++) { - int sx = (srcW > 0) ? (x * srcW / 16) : 0; - int sy = (srcH > 0) ? (y * srcH / 16) : 0; + int sx = (srcW > 0) ? (x * srcW / iconSize) : 0; + int sy = (srcH > 0) ? (y * srcH / iconSize) : 0; int di = ((dstY + y) * dstW + (dstX + x)) * 4; int si = (sy * srcW + sx) * 4; if (si < srcW * srcH * 4 && di < dstW * dstH * 4) @@ -168,69 +216,120 @@ namespace ModAtlas return true; } - static size_t BuildAtlasPage(const std::string& vanillaPath, const std::string& outPath, + static int ComputeIconSize(int imgW, int imgH, int gridCols, int baseRows) + { + const int sizeW = (gridCols > 0) ? (imgW / gridCols) : 0; + const int sizeH = (baseRows > 0) ? (imgH / baseRows) : 0; + if (sizeW <= 0 && sizeH <= 0) return 0; + if (sizeW <= 0) return sizeH; + if (sizeH <= 0) return sizeW; + return (sizeW < sizeH) ? sizeW : sizeH; + } + + static size_t BuildAtlasPageExpanded(const std::string& vanillaPath, const std::string& outPath, const std::vector>& modTextures, size_t startIndex, int atlasType, int page, - int gridCols, int gridRows, int iconSize, + int gridCols, int baseRows, + int& outIconSize, int& outTotalRows, std::vector& entries) { - int imgW = gridCols * iconSize; - int imgH = gridRows * iconSize; - - unsigned char* img = (unsigned char*)calloc(imgW * imgH, 4); - if (!img) return false; + outIconSize = 0; + outTotalRows = baseRows; int vw = 0, vh = 0, vc = 0; - bool loadedVanilla = false; + unsigned char* vanilla = nullptr; if (!vanillaPath.empty()) - { - unsigned char* vanilla = stbi_load(vanillaPath.c_str(), &vw, &vh, &vc, 4); - if (vanilla) - { - int copyW = (vw < imgW) ? vw : imgW; - int copyH = (vh < imgH) ? vh : imgH; - for (int y = 0; y < copyH; y++) - memcpy(img + y * imgW * 4, vanilla + y * vw * 4, copyW * 4); - stbi_image_free(vanilla); - loadedVanilla = true; - } - } + vanilla = stbi_load(vanillaPath.c_str(), &vw, &vh, &vc, 4); - if (!loadedVanilla) + if (!vanilla) { - LogUtil::Log("[WeaveLoader] ModAtlas: ERROR could not load vanilla atlas base '%s' (atlasType=%d page=%d)", + LogUtil::Log("[WeaveLoader] ModAtlas: ERROR could not load atlas base '%s' (atlasType=%d page=%d)", vanillaPath.c_str(), atlasType, page); - free(img); return 0; } - // Find all empty (fully transparent) cells in the atlas + int iconSize = ComputeIconSize(vw, vh, gridCols, baseRows); + if (iconSize <= 0) + { + LogUtil::Log("[WeaveLoader] ModAtlas: ERROR invalid atlas size %dx%d for baseRows=%d cols=%d", + vw, vh, baseRows, gridCols); + stbi_image_free(vanilla); + return 0; + } + + const int baseW = gridCols * iconSize; + const int baseH = baseRows * iconSize; + if (vw != baseW || vh != baseH) + { + LogUtil::Log("[WeaveLoader] ModAtlas: atlas base size %dx%d does not match expected %dx%d (icon=%d). Using cropped base.", + vw, vh, baseW, baseH, iconSize); + } + + std::vector baseImg((size_t)baseW * (size_t)baseH * 4, 0); + const int copyW = (vw < baseW) ? vw : baseW; + const int copyH = (vh < baseH) ? vh : baseH; + for (int y = 0; y < copyH; y++) + { + memcpy(baseImg.data() + (size_t)y * baseW * 4, + vanilla + (size_t)y * vw * 4, + (size_t)copyW * 4); + } + stbi_image_free(vanilla); + + // Find all empty (fully transparent) cells in the base atlas std::vector> emptyCells; - for (int row = 0; row < gridRows; row++) + for (int row = 0; row < baseRows; row++) { for (int col = 0; col < gridCols; col++) { - if (IsCellEmpty(img, imgW, imgH, col * iconSize, row * iconSize, iconSize)) + if (IsCellEmpty(baseImg.data(), baseW, baseH, col * iconSize, row * iconSize, iconSize)) emptyCells.push_back({ row, col }); } } - LogUtil::Log("[WeaveLoader] ModAtlas: found %zu empty cells in %dx%d atlas", - emptyCells.size(), gridCols, gridRows); + const size_t totalMods = (startIndex < modTextures.size()) ? (modTextures.size() - startIndex) : 0; + const size_t baseCapacity = emptyCells.size(); + const size_t extraNeeded = (totalMods > baseCapacity) ? (totalMods - baseCapacity) : 0; + const int extraRows = (extraNeeded > 0) + ? (int)((extraNeeded + (size_t)gridCols - 1) / (size_t)gridCols) + : 0; + const int totalRows = baseRows + extraRows; + + const int imgW = baseW; + const int imgH = totalRows * iconSize; + unsigned char* img = (unsigned char*)calloc((size_t)imgW * (size_t)imgH, 4); + if (!img) + return 0; + + // Copy base atlas into the top portion of the expanded atlas + for (int y = 0; y < baseH; y++) + memcpy(img + (size_t)y * imgW * 4, baseImg.data() + (size_t)y * baseW * 4, (size_t)baseW * 4); + + LogUtil::Log("[WeaveLoader] ModAtlas: base %dx%d (rows=%d icon=%d) emptyCells=%zu extraRows=%d totalRows=%d", + baseW, baseH, baseRows, iconSize, emptyCells.size(), extraRows, totalRows); size_t consumed = 0; size_t cellIdx = 0; + size_t extraIdx = 0; for (size_t texIdx = startIndex; texIdx < modTextures.size(); ++texIdx) { const auto& tex = modTextures[texIdx]; const std::string& iconName = tex.first; const std::string& path = tex.second; - if (cellIdx >= emptyCells.size()) + int row = 0; + int col = 0; + if (cellIdx < emptyCells.size()) { - LogUtil::Log("[WeaveLoader] ModAtlas: page %d full for atlasType=%d (stopped at %s)", - page, atlasType, iconName.c_str()); - break; + row = emptyCells[cellIdx].first; + col = emptyCells[cellIdx].second; + cellIdx++; + } + else + { + row = baseRows + (int)(extraIdx / (size_t)gridCols); + col = (int)(extraIdx % (size_t)gridCols); + extraIdx++; } int sw = 0, sh = 0, sc = 0; @@ -241,19 +340,18 @@ namespace ModAtlas continue; } - int row = emptyCells[cellIdx].first; - int col = emptyCells[cellIdx].second; - cellIdx++; - - Blit16x16(img, imgW, imgH, col * iconSize, row * iconSize, src, sw, sh); + BlitIcon(img, imgW, imgH, col * iconSize, row * iconSize, iconSize, src, sw, sh); stbi_image_free(src); std::wstring wname(iconName.begin(), iconName.end()); entries.push_back({ wname, atlasType, page, row, col }); consumed++; - LogUtil::Log("[WeaveLoader] ModAtlas: placed '%s' at page=%d row=%d col=%d", - iconName.c_str(), page, row, col); + if (consumed <= 20) + { + LogUtil::Log("[WeaveLoader] ModAtlas: placed '%s' at page=%d row=%d col=%d", + iconName.c_str(), page, row, col); + } } std::string dir = outPath.substr(0, outPath.find_last_of("\\/")); @@ -262,6 +360,9 @@ namespace ModAtlas int ok = stbi_write_png(outPath.c_str(), imgW, imgH, 4, img, imgW * 4); free(img); if (!ok) return 0; + + outIconSize = iconSize; + outTotalRows = totalRows; return consumed; } @@ -296,7 +397,7 @@ namespace ModAtlas int col = (int)(consumed % (size_t)gridCols); consumed++; - Blit16x16(img, imgW, imgH, col * iconSize, row * iconSize, src, sw, sh); + BlitIcon(img, imgW, imgH, col * iconSize, row * iconSize, iconSize, src, sw, sh); stbi_image_free(src); std::wstring wname(iconName.begin(), iconName.end()); @@ -314,12 +415,16 @@ namespace ModAtlas return consumed; } - std::string BuildAtlases(const std::string& modsPath, const std::string& gameResPath) + static std::string BuildAtlasesInternal(const std::string& modsPath, + const std::string& terrainBasePath, + const std::string& itemsBasePath) { + s_buildInProgress = true; s_blockEntries.clear(); s_itemEntries.clear(); s_modIcons.clear(); s_iconRoutes.clear(); + s_iconAtlasType.clear(); s_hasModTextures = false; s_hasTerrainAtlas = false; s_hasItemsAtlas = false; @@ -329,6 +434,10 @@ namespace ModAtlas s_itemPages = 0; s_mergedTerrainPage1Path.clear(); s_mergedItemsPage1Path.clear(); + s_terrainRows = s_terrainBaseRows; + s_terrainVScale = 1.0f; + s_itemRows = 16; + s_itemVScale = 1.0f; std::vector> blockPaths, itemPaths; FindModTextures(modsPath, blockPaths, itemPaths); @@ -336,6 +445,7 @@ namespace ModAtlas if (blockPaths.empty() && itemPaths.empty()) { LogUtil::Log("[WeaveLoader] ModAtlas: no mod textures found"); + s_buildInProgress = false; return ""; } @@ -355,54 +465,62 @@ namespace ModAtlas s_mergedTerrainPage1Path = BuildPageOutputPath(s_mergedDir, "terrain", 1); s_mergedItemsPage1Path = BuildPageOutputPath(s_mergedDir, "items", 1); - std::string vanillaTerrainPath = gameResPath + "\\terrain.png"; - if (!blockPaths.empty()) { - size_t cursor = 0; - int page = 1; // page 0 remains vanilla; modded blocks use dedicated pages - while (cursor < blockPaths.size()) - { - std::string outPath = BuildPageOutputPath(s_mergedDir, "terrain", page); - // World block rendering binds the terrain atlas once for a whole pass. - // Keep terrain page 1 as a vanilla+mod merged atlas so both vanilla and - // modded block icons resolve in the same draw pass. - size_t placed = BuildAtlasPage(vanillaTerrainPath, outPath, blockPaths, cursor, 0, page, 16, 32, 16, s_blockEntries); - if (placed == 0) - break; - if (!s_virtualAtlasDir.empty() && page > 0) - { - std::string vpath = BuildVirtualPageOutputPath(s_virtualAtlasDir, "terrain", page); - if (!vpath.empty()) - CopyFileA(outPath.c_str(), vpath.c_str(), FALSE); - } - cursor += placed; - page++; - } - - if (cursor > 0) + const std::string outPath = BuildPageOutputPath(s_mergedDir, "terrain", 0); + int iconSize = 0; + int totalRows = s_terrainBaseRows; + size_t placed = BuildAtlasPageExpanded(terrainBasePath, outPath, blockPaths, 0, 0, 0, 16, s_terrainBaseRows, + iconSize, totalRows, s_blockEntries); + if (placed > 0) { s_hasModTextures = true; s_hasTerrainAtlas = true; - s_terrainPages = page; - LogUtil::Log("[WeaveLoader] ModAtlas: built terrain pages count=%d", s_terrainPages); + s_hasTerrainPage0Mods = true; + s_terrainPages = 1; // only page 0 + s_terrainRows = (totalRows > 0) ? totalRows : s_terrainBaseRows; + if (s_terrainRows > 0) + s_terrainVScale = (float)s_terrainBaseRows / (float)s_terrainRows; + LogUtil::Log("[WeaveLoader] ModAtlas: built terrain atlas rows=%d (scaleV=%.4f)", + s_terrainRows, s_terrainVScale); + + if (!s_virtualAtlasDir.empty()) + { + std::string vpath = BuildVirtualPageOutputPath(s_virtualAtlasDir, "terrain", 0); + if (!vpath.empty()) + CopyFileA(outPath.c_str(), vpath.c_str(), FALSE); + } } - if (cursor < blockPaths.size()) + if (placed < blockPaths.size()) { LogUtil::Log("[WeaveLoader] ModAtlas: WARNING terrain overflow, dropped %zu textures", - blockPaths.size() - cursor); + blockPaths.size() - placed); } } if (!itemPaths.empty()) { + int itemIconSize = 16; + if (!itemsBasePath.empty()) + { + int iw = 0, ih = 0, ic = 0; + unsigned char* baseItems = stbi_load(itemsBasePath.c_str(), &iw, &ih, &ic, 4); + if (baseItems) + { + int computed = ComputeIconSize(iw, ih, 16, 16); + if (computed > 0) + itemIconSize = computed; + stbi_image_free(baseItems); + } + } + size_t cursor = 0; int page = 1; // page 0 remains vanilla; modded items use dedicated pages while (cursor < itemPaths.size()) { std::string outPath = BuildPageOutputPath(s_mergedDir, "items", page); - size_t placed = BuildModOnlyAtlasPage(outPath, itemPaths, cursor, 1, page, 16, 16, 16, s_itemEntries); + size_t placed = BuildModOnlyAtlasPage(outPath, itemPaths, cursor, 1, page, 16, 16, itemIconSize, s_itemEntries); if (placed == 0) break; if (!s_virtualAtlasDir.empty() && page > 0) @@ -431,9 +549,350 @@ namespace ModAtlas } } + s_buildInProgress = false; return s_hasModTextures ? s_mergedDir : ""; } + void SetBasePaths(const std::string& modsPath, const std::string& gameResPath) + { + s_modsPath = modsPath; + s_gameResPath = gameResPath; + } + + void SetOverrideAtlasPath(int atlasType, const std::string& path) + { + if (atlasType == 0) s_overrideTerrainPath = path; + else if (atlasType == 1) s_overrideItemsPath = path; + } + + static void UpdateLastBaseDims(int atlasType, int w, int h) + { + if (atlasType == 0) + { + s_lastTerrainBaseWidth = w; + s_lastTerrainBaseHeight = h; + } + else if (atlasType == 1) + { + s_lastItemsBaseWidth = w; + s_lastItemsBaseHeight = h; + } + } + + static bool BaseDimsUnchanged(int atlasType, int w, int h) + { + if (atlasType == 0) + return (w == s_lastTerrainBaseWidth) && (h == s_lastTerrainBaseHeight); + if (atlasType == 1) + return (w == s_lastItemsBaseWidth) && (h == s_lastItemsBaseHeight); + return false; + } + + static bool BuildTerrainFromBaseRgba(const unsigned char* baseRgba, int baseW, int baseH, + std::vector& outRgba, + int& outW, int& outH) + { + outRgba.clear(); + outW = 0; + outH = 0; + if (!baseRgba || baseW <= 0 || baseH <= 0) + return false; + if (s_blockEntries.empty()) + return false; + + int iconSize = ComputeIconSize(baseW, baseH, 16, s_terrainBaseRows); + if (iconSize <= 0) + return false; + + outW = iconSize * 16; + outH = iconSize * ((s_terrainRows > 0) ? s_terrainRows : s_terrainBaseRows); + if (outW <= 0 || outH <= 0) + return false; + + outRgba.assign(static_cast(outW) * static_cast(outH) * 4, 0); + + const int copyW = (baseW < outW) ? baseW : outW; + const int copyH = (baseH < outH) ? baseH : outH; + for (int y = 0; y < copyH; y++) + { + const int srcRow = y * baseW * 4; + const int dstRow = y * outW * 4; + memcpy(outRgba.data() + dstRow, baseRgba + srcRow, static_cast(copyW) * 4); + } + + std::vector> blockPaths, itemPaths; + FindModTextures(s_modsPath, blockPaths, itemPaths); + std::unordered_map pathMap; + pathMap.reserve(blockPaths.size()); + for (const auto& p : blockPaths) + pathMap[p.first] = p.second; + + for (const auto& e : s_blockEntries) + { + auto it = pathMap.find(std::string(e.iconName.begin(), e.iconName.end())); + if (it == pathMap.end()) + continue; + int iw = 0, ih = 0, ic = 0; + unsigned char* src = stbi_load(it->second.c_str(), &iw, &ih, &ic, 4); + if (!src) + continue; + const int dstX = e.col * iconSize; + const int dstY = e.row * iconSize; + BlitIcon(outRgba.data(), outW, outH, dstX, dstY, iconSize, src, iw, ih); + stbi_image_free(src); + } + + return true; + } + + bool OverrideAtlasFromBufferedImage(int atlasType, void* bufferedImage) + { + if (!bufferedImage) + return false; + if (s_modsPath.empty() || s_gameResPath.empty()) + return false; + + // BufferedImage layout: int* data[10] at offset 0, width at +0x50, height at +0x54. + // We treat data[0] as ARGB 32-bit pixels (as used by Texture::transferFromImage). + char* base = static_cast(bufferedImage); + int** data = reinterpret_cast(base); + int* pixels = data[0]; + int w = *reinterpret_cast(base + 0x50); + int h = *reinterpret_cast(base + 0x54); + if (!pixels || w <= 0 || h <= 0) + return false; + + if (atlasType == 0) + { + if (bufferedImage == s_lastTerrainImagePtr) + return false; + s_lastTerrainImagePtr = bufferedImage; + } + else if (atlasType == 1) + { + if (bufferedImage == s_lastItemsImagePtr) + return false; + s_lastItemsImagePtr = bufferedImage; + } + + std::vector rgba(static_cast(w) * static_cast(h) * 4); + for (int i = 0; i < w * h; i++) + { + unsigned int argb = static_cast(pixels[i]); + rgba[i * 4 + 0] = (argb >> 16) & 0xFF; // R + rgba[i * 4 + 1] = (argb >> 8) & 0xFF; // G + rgba[i * 4 + 2] = (argb >> 0) & 0xFF; // B + rgba[i * 4 + 3] = (argb >> 24) & 0xFF; // A + } + + std::vector mergedRgba; + int outW = w; + int outH = h; + bool ok = false; + if (atlasType == 0 && s_hasModTextures && !s_blockEntries.empty()) + { + ok = BuildTerrainFromBaseRgba(rgba.data(), w, h, mergedRgba, outW, outH); + } + else if (atlasType == 0) + { + std::vector pngBytes; + if (!SavePngToBytes(rgba.data(), w, h, pngBytes)) + return false; + + const std::string tempDir = s_gameResPath + "\\modloader\\temp"; + CreateDirectoryA(tempDir.c_str(), nullptr); + const std::string tempPath = tempDir + "\\terrain.png"; + { + std::ofstream out(tempPath, std::ios::binary); + if (!out.is_open()) + return false; + out.write(reinterpret_cast(pngBytes.data()), static_cast(pngBytes.size())); + } + + s_applyMergedToBufferedImage = true; + SetOverrideAtlasPath(0, tempPath); + ok = EnsureAtlasesBuilt(); + s_applyMergedToBufferedImage = false; + UpdateLastBaseDims(0, w, h); + + if (!ok) + return false; + + const std::string mergedPath = GetMergedTerrainPath(); + if (mergedPath.empty()) + return false; + + int mw = 0, mh = 0, mc = 0; + unsigned char* merged = stbi_load(mergedPath.c_str(), &mw, &mh, &mc, 4); + if (!merged) + return false; + outW = (mw > 0) ? mw : w; + outH = (mh > 0) ? mh : h; + mergedRgba.assign(merged, merged + (mw * mh * 4)); + stbi_image_free(merged); + } + else + { + // Do not override item atlases here (keep mod item paging stable). + return false; + } + + if (!ok || mergedRgba.empty()) + return false; + + if (atlasType == 0 && !s_mergedDir.empty()) + { + std::vector pngBytes; + if (SavePngToBytes(mergedRgba.data(), outW, outH, pngBytes)) + { + const std::string outPath = BuildPageOutputPath(s_mergedDir, "terrain", 0); + std::ofstream out(outPath, std::ios::binary); + if (out.is_open()) + out.write(reinterpret_cast(pngBytes.data()), static_cast(pngBytes.size())); + } + } + + const bool needsResize = (outW != w) || (outH != h); + + int* target = pixels; + if (needsResize) + { + target = new int[outW * outH]; + std::fill(target, target + (outW * outH), 0); + } + + const int copyW = outW; + const int copyH = outH; + for (int y = 0; y < copyH; y++) + { + for (int x = 0; x < copyW; x++) + { + const int si = (y * outW + x) * 4; + const int di = y * outW + x; + unsigned int argb = (static_cast(mergedRgba[si + 3]) << 24) | + (static_cast(mergedRgba[si + 0]) << 16) | + (static_cast(mergedRgba[si + 1]) << 8) | + (static_cast(mergedRgba[si + 2]) << 0); + target[di] = static_cast(argb); + } + } + + if (needsResize) + { + delete[] data[0]; + for (int i = 1; i < 10; i++) + { + if (data[i]) + { + delete[] data[i]; + data[i] = nullptr; + } + } + data[0] = target; + *reinterpret_cast(base + 0x50) = outW; + *reinterpret_cast(base + 0x54) = outH; + } + return true; + } + + bool EnsureAtlasesBuilt() + { + if (s_buildInProgress) + return s_hasModTextures; + if (s_modsPath.empty() || s_gameResPath.empty()) + return false; + + s_buildInProgress = true; + + // Pre-scan for mods so we can avoid nuking a valid atlas when a pack + // path can't be loaded (e.g., DLC packs not backed by filesystem files). + std::vector> blockPaths, itemPaths; + FindModTextures(s_modsPath, blockPaths, itemPaths); + + std::string terrainPath = !s_overrideTerrainPath.empty() + ? s_overrideTerrainPath + : (s_gameResPath + "\\terrain.png"); + std::string itemsPath = !s_overrideItemsPath.empty() + ? s_overrideItemsPath + : (s_gameResPath + "\\items.png"); + + if (!s_applyMergedToBufferedImage && + terrainPath == s_lastTerrainBasePath && + itemsPath == s_lastItemsBasePath && + !s_mergedDir.empty()) + { + s_buildInProgress = false; + return s_hasModTextures; + } + + // Fallback to default if override path doesn't exist + if (!s_overrideTerrainPath.empty() && !FileExists(terrainPath)) + { + LogUtil::Log("[WeaveLoader] ModAtlas: override terrain path missing, falling back to default"); + terrainPath = s_gameResPath + "\\terrain.png"; + } + if (!s_overrideItemsPath.empty() && !FileExists(itemsPath)) + { + LogUtil::Log("[WeaveLoader] ModAtlas: override items path missing, falling back to default"); + itemsPath = s_gameResPath + "\\items.png"; + } + + if (!blockPaths.empty()) + { + int tw = 0, th = 0, tc = 0; + unsigned char* probe = stbi_load(terrainPath.c_str(), &tw, &th, &tc, 4); + if (!probe) + { + LogUtil::Log("[WeaveLoader] ModAtlas: cannot load terrain base '%s' (keeping existing atlas)", + terrainPath.c_str()); + s_buildInProgress = false; + return s_hasModTextures; + } + stbi_image_free(probe); + UpdateLastBaseDims(0, tw, th); + } + if (!itemPaths.empty()) + { + int iw = 0, ih = 0, ic = 0; + unsigned char* probe = stbi_load(itemsPath.c_str(), &iw, &ih, &ic, 4); + if (probe) + { + stbi_image_free(probe); + UpdateLastBaseDims(1, iw, ih); + } + } + + BuildAtlasesInternal(s_modsPath, terrainPath, itemsPath); + s_lastTerrainBasePath = terrainPath; + s_lastItemsBasePath = itemsPath; + + // If the CreateFileW hook is installed, refresh redirect targets after rebuild. + if (s_originalCreateFileW) + { + std::string mergedTerrain = GetMergedTerrainPath(); + std::string mergedItems = GetMergedItemsPath(); + if (!mergedTerrain.empty()) + s_mergedTerrainW = std::wstring(mergedTerrain.begin(), mergedTerrain.end()); + if (!mergedItems.empty()) + s_mergedItemsW = std::wstring(mergedItems.begin(), mergedItems.end()); + } + return s_hasModTextures; + } + + std::string BuildAtlases(const std::string& modsPath, const std::string& gameResPath) + { + SetBasePaths(modsPath, gameResPath); + s_overrideTerrainPath.clear(); + s_overrideItemsPath.clear(); + s_lastTerrainBasePath.clear(); + s_lastItemsBasePath.clear(); + s_lastTerrainBaseWidth = 0; + s_lastTerrainBaseHeight = 0; + s_lastItemsBaseWidth = 0; + s_lastItemsBaseHeight = 0; + return EnsureAtlasesBuilt() ? s_mergedDir : ""; + } + void SetVirtualAtlasDirectory(const std::string& dir) { s_virtualAtlasDir = dir; @@ -486,19 +945,42 @@ namespace ModAtlas const std::vector& GetItemEntries() { return s_itemEntries; } bool HasModTextures() { return s_hasModTextures; } + void NoteIconAtlasType(void* iconPtr, int atlasType) + { + if (!iconPtr) return; + s_iconAtlasType[iconPtr] = atlasType; + } + + bool GetIconAtlasType(void* iconPtr, int& outAtlasType) + { + auto it = s_iconAtlasType.find(iconPtr); + if (it == s_iconAtlasType.end()) + return false; + outAtlasType = it->second; + return true; + } + + bool GetAtlasScale(int atlasType, float& outUScale, float& outVScale) + { + if (atlasType == 0) + { + outUScale = 1.0f; + outVScale = s_terrainVScale; + return true; + } + if (atlasType == 1) + { + outUScale = 1.0f; + outVScale = s_itemVScale; + return true; + } + return false; + } + // Per-atlas-type textureMap pointers, saved during CreateModIcons for FixupModIcons. static void* s_terrainTextureMap = nullptr; static void* s_itemsTextureMap = nullptr; - // CreateFileW hook: redirect game file opens to merged atlases - static std::wstring s_mergedTerrainW; - static std::wstring s_mergedItemsW; - static std::wstring s_vanillaTerrainW; - static std::wstring s_vanillaItemsW; - - typedef HANDLE (WINAPI *CreateFileW_fn)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); - static CreateFileW_fn s_originalCreateFileW = nullptr; - static bool EndsWith(const wchar_t* path, const wchar_t* suffix) { size_t pathLen = wcslen(path); @@ -507,12 +989,31 @@ namespace ModAtlas return _wcsicmp(path + pathLen - suffLen, suffix) == 0; } + static bool IsGeneratedAtlasPath(const wchar_t* path) + { + if (!path) return false; + std::wstring lower; + lower.reserve(wcslen(path)); + for (const wchar_t* p = path; *p; ++p) + { + wchar_t c = (*p == L'\\') ? L'/' : *p; + lower.push_back((wchar_t)towlower(c)); + } + return (lower.find(L"/mods/modloader/generated/") != std::wstring::npos) || + (lower.find(L"/modloader/") != std::wstring::npos); + } + static HANDLE WINAPI Hooked_CreateFileW( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { - if (lpFileName && s_hasModTextures) + if (s_buildInProgress) + { + return s_originalCreateFileW(lpFileName, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + } + if (lpFileName && s_hasModTextures && !IsGeneratedAtlasPath(lpFileName)) { if (!s_mergedTerrainW.empty() && EndsWith(lpFileName, L"\\terrain.png")) { @@ -631,6 +1132,7 @@ namespace ModAtlas ctor(icon, &e.iconName, &e.iconName, u0, v0, u1, v1); s_modIcons[e.iconName] = icon; s_iconRoutes[icon] = { iconType, e.page }; + NoteIconAtlasType(icon, iconType); LogUtil::Log("[WeaveLoader] ModAtlas: created icon '%ls' (atlas=%d, page=%d, row=%d, col=%d)", e.iconName.c_str(), iconType, e.page, e.row, e.col); } @@ -638,9 +1140,9 @@ namespace ModAtlas }; if (iconType == 0) - create(s_blockEntries, 1.0f / 32.0f); + create(s_blockEntries, 1.0f / (float)((s_terrainRows > 0) ? s_terrainRows : 32)); else if (iconType == 1) - create(s_itemEntries, 1.0f / 16.0f); + create(s_itemEntries, 1.0f / (float)((s_itemRows > 0) ? s_itemRows : 16)); LogUtil::Log("[WeaveLoader] ModAtlas: s_modIcons now has %zu entries total", s_modIcons.size()); } diff --git a/WeaveLoaderRuntime/src/ModAtlas.h b/WeaveLoaderRuntime/src/ModAtlas.h index a0fb990..8e2b488 100644 --- a/WeaveLoaderRuntime/src/ModAtlas.h +++ b/WeaveLoaderRuntime/src/ModAtlas.h @@ -20,6 +20,21 @@ namespace ModAtlas int row, col; // Grid position in atlas }; + /// Set base paths used by the atlas builder (mods dir + game res dir). + void SetBasePaths(const std::string& modsPath, const std::string& gameResPath); + + /// Optional: override the base atlas path for a specific type (0=terrain, 1=items). + /// Useful for texture packs that live outside the default res directory. + void SetOverrideAtlasPath(int atlasType, const std::string& path); + + /// Capture a loaded BufferedImage as the base atlas, rebuild merged atlases, + /// and replace the BufferedImage contents with the merged atlas. + bool OverrideAtlasFromBufferedImage(int atlasType, void* bufferedImage); + + /// Build atlases if needed. Returns true if mod atlases are available. + bool EnsureAtlasesBuilt(); + + /// Legacy entry point (kept for compatibility). /// Call before textures->stitch(). Returns path to generated dir, or empty if none. std::string BuildAtlases(const std::string& modsPath, const std::string& gameResPath); void SetVirtualAtlasDirectory(const std::string& dir); @@ -68,6 +83,13 @@ namespace ModAtlas void* LookupModIcon(const std::wstring& name); bool TryGetIconRoute(void* iconPtr, int& outAtlasType, int& outPage); + /// Track atlas type for icons (vanilla + mod) so UV scaling can be applied. + void NoteIconAtlasType(void* iconPtr, int atlasType); + bool GetIconAtlasType(void* iconPtr, int& outAtlasType); + + /// Query atlas UV scale for a given atlas type (used when atlas height expands). + bool GetAtlasScale(int atlasType, float& outUScale, float& outVScale); + /// Called by icon UV hooks so bind hooks can route to the right page. void NotifyIconSampled(void* iconPtr); diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 0a419cc..dc0ea20 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -61,6 +61,7 @@ static const char* SYM_PRESENT = "?Present@C4JRender@@QEAAXXZ"; static const char* SYM_GET_STRING = "?GetString@CMinecraftApp@@SAPEB_WH@Z"; static const char* SYM_GET_RESOURCE_AS_STREAM = "?getResourceAsStream@InputStream@@SAPEAV1@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"; static const char* SYM_LOAD_UVS = "?loadUVs@PreStitchedTextureMap@@AEAAXXZ"; +static const char* SYM_PRESTITCHED_STITCH = "?stitch@PreStitchedTextureMap@@QEAAXXZ"; static const char* SYM_SIMPLE_ICON_CTOR = "??0SimpleIcon@@QEAA@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@0MMMM@Z"; static const char* SYM_OPERATOR_NEW = "??2@YAPEAX_K@Z"; static const char* SYM_REGISTER_ICON = "?registerIcon@PreStitchedTextureMap@@UEAAPEAVIcon@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"; @@ -115,10 +116,17 @@ static const char* SYM_TEXTURES_BIND_RESOURCE = "?bindTexture@Textures@@QEAAXPEA static const char* SYM_TEXTURES_LOAD_BY_NAME = "?loadTexture@Textures@@AEAAHW4_TEXTURE_NAME@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"; static const char* SYM_TEXTURES_LOAD_BY_INDEX_PUBLIC = "?loadTexture@Textures@@QEAAHH@Z"; static const char* SYM_TEXTURES_LOAD_BY_INDEX_PRIVATE = "?loadTexture@Textures@@AEAAHH@Z"; +static const char* SYM_TEXTURES_READIMAGE = "?readImage@Textures@@QEAAPEAVBufferedImage@@W4_TEXTURE_NAME@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"; static const char* SYM_STITCHED_GETU0 = "?getU0@StitchedTexture@@UEBAM_N@Z"; static const char* SYM_STITCHED_GETU1 = "?getU1@StitchedTexture@@UEBAM_N@Z"; static const char* SYM_STITCHED_GETV0 = "?getV0@StitchedTexture@@UEBAM_N@Z"; static const char* SYM_STITCHED_GETV1 = "?getV1@StitchedTexture@@UEBAM_N@Z"; +static const char* SYM_BUFFEREDIMAGE_CTOR_FILE = "??0BufferedImage@@QEAA@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@_N10@Z"; +static const char* SYM_BUFFEREDIMAGE_CTOR_DLC = "??0BufferedImage@@QEAA@PEAVDLCPack@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@_N@Z"; +static const char* SYM_TEXTUREMANAGER_CREATETEXTURE = "?createTexture@TextureManager@@QEAAPEAVTexture@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@HHHHHHH_NPEAVBufferedImage@@@Z"; +static const char* SYM_TEXTURE_TRANSFERFROMIMAGE = "?transferFromImage@Texture@@QEAAXPEAVBufferedImage@@@Z"; +static const char* SYM_ABSTRACT_TEXPACK_GETIMAGE = "?getImageResource@AbstractTexturePack@@UEAAPEAVBufferedImage@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@_N10@Z"; +static const char* SYM_DLC_TEXPACK_GETIMAGE = "?getImageResource@DLCTexturePack@@UEAAPEAVBufferedImage@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@_N10@Z"; static const char* SYM_MINECRAFT_SETLEVEL = "?setLevel@Minecraft@@QEAAXPEAVMultiPlayerLevel@@HV?$shared_ptr@VPlayer@@@std@@_N2@Z"; static const char* SYM_LEVEL_ADDENTITY = "?addEntity@Level@@UEAA_NV?$shared_ptr@VEntity@@@std@@@Z"; static const char* SYM_ENTITYIO_NEWBYID = "?newById@EntityIO@@SA?AV?$shared_ptr@VEntity@@@std@@HPEAVLevel@@@Z"; @@ -226,6 +234,9 @@ bool SymbolResolver::ResolveGameFunctions() } pGetResourceAsStream = Resolve(SYM_GET_RESOURCE_AS_STREAM); pLoadUVs = Resolve(SYM_LOAD_UVS); + pPreStitchedTextureMapStitch = Resolve(SYM_PRESTITCHED_STITCH); + if (!pPreStitchedTextureMapStitch) + pPreStitchedTextureMapStitch = ResolveExactProcName(m_moduleBase, "PreStitchedTextureMap::stitch"); pSimpleIconCtor = Resolve(SYM_SIMPLE_ICON_CTOR); pOperatorNew = Resolve(SYM_OPERATOR_NEW); pRegisterIcon = Resolve(SYM_REGISTER_ICON); @@ -287,10 +298,21 @@ bool SymbolResolver::ResolveGameFunctions() pTexturesLoadTextureByIndex = Resolve(SYM_TEXTURES_LOAD_BY_INDEX_PUBLIC); if (!pTexturesLoadTextureByIndex) pTexturesLoadTextureByIndex = Resolve(SYM_TEXTURES_LOAD_BY_INDEX_PRIVATE); + pTexturesReadImage = Resolve(SYM_TEXTURES_READIMAGE); pStitchedGetU0 = Resolve(SYM_STITCHED_GETU0); pStitchedGetU1 = Resolve(SYM_STITCHED_GETU1); pStitchedGetV0 = Resolve(SYM_STITCHED_GETV0); pStitchedGetV1 = Resolve(SYM_STITCHED_GETV1); + pBufferedImageCtorFile = Resolve(SYM_BUFFEREDIMAGE_CTOR_FILE); + pBufferedImageCtorDLCPack = Resolve(SYM_BUFFEREDIMAGE_CTOR_DLC); + pTextureManagerCreateTexture = Resolve(SYM_TEXTUREMANAGER_CREATETEXTURE); + pTextureTransferFromImage = Resolve(SYM_TEXTURE_TRANSFERFROMIMAGE); + pAbstractTexturePackGetImageResource = Resolve(SYM_ABSTRACT_TEXPACK_GETIMAGE); + if (!pAbstractTexturePackGetImageResource) + pAbstractTexturePackGetImageResource = ResolveExactProcName(m_moduleBase, "AbstractTexturePack::getImageResource"); + pDLCTexturePackGetImageResource = Resolve(SYM_DLC_TEXPACK_GETIMAGE); + if (!pDLCTexturePackGetImageResource) + pDLCTexturePackGetImageResource = ResolveExactProcName(m_moduleBase, "DLCTexturePack::getImageResource"); pMinecraftSetLevel = Resolve(SYM_MINECRAFT_SETLEVEL); pLevelAddEntity = Resolve(SYM_LEVEL_ADDENTITY); pEntityIONewById = Resolve(SYM_ENTITYIO_NEWBYID); @@ -335,7 +357,13 @@ bool SymbolResolver::ResolveGameFunctions() if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140d.dll"), SYM_OPERATOR_NEW); if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandle(nullptr), SYM_OPERATOR_NEW); if (!pSimpleIconCtor) PdbParser::DumpMatching("??0SimpleIcon@@"); + if (!pBufferedImageCtorFile && !pBufferedImageCtorDLCPack) PdbParser::DumpMatching("??0BufferedImage@@"); + if (!pTextureManagerCreateTexture) PdbParser::DumpMatching("createTexture@TextureManager"); + if (!pTextureTransferFromImage) PdbParser::DumpMatching("transferFromImage@Texture"); + if (!pAbstractTexturePackGetImageResource) PdbParser::DumpMatching("getImageResource@AbstractTexturePack"); + if (!pDLCTexturePackGetImageResource) PdbParser::DumpMatching("getImageResource@DLCTexturePack"); if (!pLoadUVs) PdbParser::DumpMatching("loadUVs@PreStitchedTextureMap"); + if (!pPreStitchedTextureMapStitch) PdbParser::DumpMatching("stitch@PreStitchedTextureMap"); auto logSym = [](const char* name, void* ptr) { if (ptr) @@ -354,6 +382,7 @@ bool SymbolResolver::ResolveGameFunctions() logSym("CMinecraftApp::GetString", pGetString); logSym("InputStream::getResourceAsStream", pGetResourceAsStream); logSym("PreStitchedTextureMap::loadUVs", pLoadUVs); + logSym("PreStitchedTextureMap::stitch", pPreStitchedTextureMapStitch); logSym("SimpleIcon::SimpleIcon", pSimpleIconCtor); logSym("operator new", pOperatorNew); logSym("registerIcon", pRegisterIcon); @@ -407,10 +436,17 @@ bool SymbolResolver::ResolveGameFunctions() logSym("Textures::bindTexture(ResourceLocation)", pTexturesBindTextureResource); logSym("Textures::loadTexture(TEXTURE_NAME,wstring)", pTexturesLoadTextureByName); logSym("Textures::loadTexture(int)", pTexturesLoadTextureByIndex); + logSym("Textures::readImage(TEXTURE_NAME,wstring)", pTexturesReadImage); logSym("StitchedTexture::getU0", pStitchedGetU0); logSym("StitchedTexture::getU1", pStitchedGetU1); logSym("StitchedTexture::getV0", pStitchedGetV0); logSym("StitchedTexture::getV1", pStitchedGetV1); + logSym("BufferedImage::BufferedImage(file)", pBufferedImageCtorFile); + logSym("BufferedImage::BufferedImage(DLCPack)", pBufferedImageCtorDLCPack); + logSym("TextureManager::createTexture", pTextureManagerCreateTexture); + logSym("Texture::transferFromImage", pTextureTransferFromImage); + logSym("AbstractTexturePack::getImageResource", pAbstractTexturePackGetImageResource); + logSym("DLCTexturePack::getImageResource", pDLCTexturePackGetImageResource); logSym("Minecraft::setLevel", pMinecraftSetLevel); logSym("Level::addEntity", pLevelAddEntity); logSym("EntityIO::newById", pEntityIONewById); diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index 3c33036..4df2496 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -20,6 +20,7 @@ public: void* pGetString = nullptr; // CMinecraftApp::GetString(int) void* pGetResourceAsStream = nullptr; // InputStream::getResourceAsStream(wstring) void* pLoadUVs = nullptr; // PreStitchedTextureMap::loadUVs() + void* pPreStitchedTextureMapStitch = nullptr; // PreStitchedTextureMap::stitch() void* pSimpleIconCtor = nullptr; // SimpleIcon::SimpleIcon(wstring,wstring,float*4) void* pOperatorNew = nullptr; // global operator new(size_t) - for texture injection void* pRegisterIcon = nullptr; // PreStitchedTextureMap::registerIcon(const wstring&) @@ -73,10 +74,17 @@ public: void* pTexturesBindTextureResource = nullptr; // Textures::bindTexture(ResourceLocation*) void* pTexturesLoadTextureByName = nullptr; // Textures::loadTexture(TEXTURE_NAME,const wstring&) void* pTexturesLoadTextureByIndex = nullptr; // Textures::loadTexture(int) + void* pTexturesReadImage = nullptr; // Textures::readImage(TEXTURE_NAME,const wstring&) void* pStitchedGetU0 = nullptr; // StitchedTexture::getU0(bool) const void* pStitchedGetU1 = nullptr; // StitchedTexture::getU1(bool) const void* pStitchedGetV0 = nullptr; // StitchedTexture::getV0(bool) const void* pStitchedGetV1 = nullptr; // StitchedTexture::getV1(bool) const + void* pBufferedImageCtorFile = nullptr; // BufferedImage::BufferedImage(const wstring&,bool,bool,const wstring&) + void* pBufferedImageCtorDLCPack = nullptr; // BufferedImage::BufferedImage(DLCPack*,const wstring&,bool) + void* pTextureManagerCreateTexture = nullptr; // TextureManager::createTexture(wstring,int,int,int,int,int,int,int,bool,BufferedImage*) + void* pTextureTransferFromImage = nullptr; // Texture::transferFromImage(BufferedImage*) + void* pAbstractTexturePackGetImageResource = nullptr; // AbstractTexturePack::getImageResource + void* pDLCTexturePackGetImageResource = nullptr; // DLCTexturePack::getImageResource void* pMinecraftSetLevel = nullptr; // Minecraft::setLevel(MultiPlayerLevel*,int,shared_ptr,bool,bool) void* pLevelAddEntity = nullptr; // Level::addEntity(shared_ptr) void* pEntityIONewById = nullptr; // EntityIO::newById(int,Level*)