mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-25 23:24:31 +00:00
fix(runtime): rework atlas merging and texture pack hooks
This commit is contained in:
@@ -15,8 +15,10 @@
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <cwctype>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<const char*>(thisPtr) + 8, sizeof(int)))
|
||||
iconType = *reinterpret_cast<const int*>(static_cast<const char*>(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<const char*>(thisPtr) + 8, sizeof(int)))
|
||||
s_activeStitchAtlasType = *reinterpret_cast<const int*>(static_cast<const char*>(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<void*, std::wstring> 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<const char*>(thisPtr) + 8, sizeof(int)))
|
||||
iconType = *reinterpret_cast<const int*>(static_cast<const char*>(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<const std::wstring*>(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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -722,6 +722,79 @@ bool HookManager::Install(const SymbolResolver& symbols)
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.pTexturesReadImage)
|
||||
{
|
||||
if (MH_CreateHook(symbols.pTexturesReadImage,
|
||||
reinterpret_cast<void*>(&GameHooks::Hooked_TexturesReadImage),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TextureManagerCreateTexture),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TextureTransferFromImage),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_AbstractTexturePackGetImageResource),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_DLCTexturePackGetImageResource),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_PreStitchedTextureMapStitch),
|
||||
reinterpret_cast<void**>(&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);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Windows.h>
|
||||
#include <MinHook.h>
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -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<ModTextureEntry> 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<std::wstring, void*> s_modIcons;
|
||||
static std::unordered_map<void*, int> s_iconAtlasType;
|
||||
struct IconRouteInfo { int atlasType; int page; };
|
||||
static std::unordered_map<void*, IconRouteInfo> 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<unsigned char>& outBytes)
|
||||
{
|
||||
outBytes.clear();
|
||||
if (!img || w <= 0 || h <= 0) return false;
|
||||
|
||||
auto writeFunc = [](void* context, void* data, int size)
|
||||
{
|
||||
auto* bytes = reinterpret_cast<std::vector<unsigned char>*>(context);
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(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<std::pair<std::string, std::string>>& modTextures, size_t startIndex,
|
||||
int atlasType, int page,
|
||||
int gridCols, int gridRows, int iconSize,
|
||||
int gridCols, int baseRows,
|
||||
int& outIconSize, int& outTotalRows,
|
||||
std::vector<ModTextureEntry>& 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<unsigned char> 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<std::pair<int, int>> 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<std::pair<std::string, std::string>> 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<unsigned char>& 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<size_t>(outW) * static_cast<size_t>(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<size_t>(copyW) * 4);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> blockPaths, itemPaths;
|
||||
FindModTextures(s_modsPath, blockPaths, itemPaths);
|
||||
std::unordered_map<std::string, std::string> 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<char*>(bufferedImage);
|
||||
int** data = reinterpret_cast<int**>(base);
|
||||
int* pixels = data[0];
|
||||
int w = *reinterpret_cast<int*>(base + 0x50);
|
||||
int h = *reinterpret_cast<int*>(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<unsigned char> rgba(static_cast<size_t>(w) * static_cast<size_t>(h) * 4);
|
||||
for (int i = 0; i < w * h; i++)
|
||||
{
|
||||
unsigned int argb = static_cast<unsigned int>(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<unsigned char> 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<unsigned char> 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<const char*>(pngBytes.data()), static_cast<std::streamsize>(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<unsigned char> 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<const char*>(pngBytes.data()), static_cast<std::streamsize>(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<unsigned int>(mergedRgba[si + 3]) << 24) |
|
||||
(static_cast<unsigned int>(mergedRgba[si + 0]) << 16) |
|
||||
(static_cast<unsigned int>(mergedRgba[si + 1]) << 8) |
|
||||
(static_cast<unsigned int>(mergedRgba[si + 2]) << 0);
|
||||
target[di] = static_cast<int>(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<int*>(base + 0x50) = outW;
|
||||
*reinterpret_cast<int*>(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<std::pair<std::string, std::string>> 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<ModTextureEntry>& 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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Player>,bool,bool)
|
||||
void* pLevelAddEntity = nullptr; // Level::addEntity(shared_ptr<Entity>)
|
||||
void* pEntityIONewById = nullptr; // EntityIO::newById(int,Level*)
|
||||
|
||||
Reference in New Issue
Block a user