mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-07-08 11:48:07 +00:00
Replace file overwrite with CreateFileW hook for mod texture loading
Instead of backing up and overwriting vanilla terrain.png/items.png, hook CreateFileW to redirect file opens to merged atlases in mods/ModLoader/generated/. The hook is active only during init and removed once textures are loaded into GPU memory. Vanilla game files are never modified.
This commit is contained in:
@@ -9,7 +9,7 @@ A runtime mod loader for Minecraft Legacy Edition (Xbox 360 / PS3 / Windows 64-b
|
||||
- **Function hooking** -- MinHook detours on game lifecycle functions (init, tick, static constructors, rendering)
|
||||
- **Full .NET hosting** -- .NET 8 CoreCLR is loaded inside the game process via hostfxr; mods are standard C# class libraries
|
||||
- **Block and item registration** -- Create real game objects (Tile, TileItem, Item) by calling the game's own constructors through resolved PDB symbols
|
||||
- **Dynamic texture atlas merging** -- Mod textures are stitched into the game's `terrain.png` and `items.png` atlases at runtime using empty cells
|
||||
- **Dynamic texture atlas merging** -- Mod textures are merged into copies of the game's atlases at runtime using empty cells; vanilla game files are never touched
|
||||
- **Creative inventory injection** -- Mod items appear in the correct creative tabs with proper pagination
|
||||
- **Localized display names** -- Mod strings are injected directly into the game's `StringTable` vector, bypassing inlined `GetString` calls
|
||||
- **Crash reporting** -- Vectored exception handler produces detailed crash logs with register dumps, symbolicated stack traces, and loaded module lists
|
||||
@@ -342,8 +342,8 @@ The runtime opens the game's PDB file and parses it using [raw_pdb](https://gith
|
||||
2. The vanilla `terrain.png` (16x32 grid) and `items.png` (16x16 grid) are loaded via stb_image
|
||||
3. Empty cells are identified by checking for fully transparent pixels
|
||||
4. Mod textures are placed into empty cells
|
||||
5. The merged atlas is written to a temp directory and swapped in before `Minecraft::init`
|
||||
6. After the game loads textures, the original files are restored from backup
|
||||
5. The merged atlas is written to `mods/ModLoader/generated/` -- **vanilla game files are never modified**
|
||||
6. A `CreateFileW` hook temporarily redirects the game's file opens to the merged atlases during init, then is removed once textures are loaded into GPU memory
|
||||
7. `SimpleIcon` objects are created for each mod texture with correct UV coordinates
|
||||
|
||||
### String Table Injection
|
||||
|
||||
@@ -157,12 +157,15 @@ namespace GameHooks
|
||||
ModAtlas::BuildAtlases(base + "mods", gameResPath);
|
||||
atlas_done:
|
||||
|
||||
// Overwrite the game's atlas PNGs with our merged versions BEFORE init
|
||||
// loads them. The originals are backed up and restored after init.
|
||||
ModAtlas::InstallAtlasFiles(gameResPath);
|
||||
// Redirect terrain.png/items.png file opens to our merged atlases
|
||||
// so the game loads mod textures without modifying vanilla files.
|
||||
ModAtlas::InstallCreateFileHook(gameResPath);
|
||||
|
||||
Original_MinecraftInit(thisPtr);
|
||||
|
||||
// Textures are loaded into GPU memory now; remove the redirect.
|
||||
ModAtlas::RemoveCreateFileHook();
|
||||
|
||||
// After init, vanilla icons have their source-image pointer (field_0x48)
|
||||
// fully populated. Copy it to our mod icons so getSourceHeight() works.
|
||||
ModAtlas::FixupModIcons();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ModAtlas.h"
|
||||
#include "LogUtil.h"
|
||||
#include <Windows.h>
|
||||
#include <MinHook.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
@@ -281,14 +282,103 @@ namespace ModAtlas
|
||||
|
||||
static std::unordered_map<std::wstring, void*> s_modIcons;
|
||||
static RegisterIcon_fn s_originalRegisterIcon = nullptr;
|
||||
static std::string s_gameResPath;
|
||||
static std::string s_backupTerrainPath;
|
||||
static std::string s_backupItemsPath;
|
||||
|
||||
// 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);
|
||||
size_t suffLen = wcslen(suffix);
|
||||
if (suffLen > pathLen) return false;
|
||||
return _wcsicmp(path + pathLen - suffLen, suffix) == 0;
|
||||
}
|
||||
|
||||
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_mergedTerrainW.empty() && EndsWith(lpFileName, L"\\terrain.png"))
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] CreateFileW: redirecting terrain.png to merged atlas");
|
||||
return s_originalCreateFileW(s_mergedTerrainW.c_str(), dwDesiredAccess, dwShareMode,
|
||||
lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
if (!s_mergedItemsW.empty() && EndsWith(lpFileName, L"\\items.png"))
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] CreateFileW: redirecting items.png to merged atlas");
|
||||
return s_originalCreateFileW(s_mergedItemsW.c_str(), dwDesiredAccess, dwShareMode,
|
||||
lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
}
|
||||
return s_originalCreateFileW(lpFileName, dwDesiredAccess, dwShareMode,
|
||||
lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
bool InstallCreateFileHook(const std::string& gameResPath)
|
||||
{
|
||||
if (!s_hasModTextures) return false;
|
||||
|
||||
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());
|
||||
|
||||
void* pCreateFileW = reinterpret_cast<void*>(
|
||||
GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateFileW"));
|
||||
if (!pCreateFileW)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: could not find CreateFileW");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MH_CreateHook(pCreateFileW, reinterpret_cast<void*>(&Hooked_CreateFileW),
|
||||
reinterpret_cast<void**>(&s_originalCreateFileW)) != MH_OK)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: failed to hook CreateFileW");
|
||||
return false;
|
||||
}
|
||||
if (MH_EnableHook(pCreateFileW) != MH_OK)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: failed to enable CreateFileW hook");
|
||||
return false;
|
||||
}
|
||||
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: CreateFileW hook installed (terrain=%s, items=%s)",
|
||||
mergedTerrain.c_str(), mergedItems.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveCreateFileHook()
|
||||
{
|
||||
void* pCreateFileW = reinterpret_cast<void*>(
|
||||
GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateFileW"));
|
||||
if (pCreateFileW)
|
||||
{
|
||||
MH_DisableHook(pCreateFileW);
|
||||
MH_RemoveHook(pCreateFileW);
|
||||
}
|
||||
s_mergedTerrainW.clear();
|
||||
s_mergedItemsW.clear();
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: CreateFileW hook removed");
|
||||
}
|
||||
|
||||
void SetInjectSymbols(void* simpleIconCtor, void* operatorNew)
|
||||
{
|
||||
s_simpleIconCtor = simpleIconCtor;
|
||||
@@ -300,37 +390,6 @@ namespace ModAtlas
|
||||
s_originalRegisterIcon = fn;
|
||||
}
|
||||
|
||||
void InstallAtlasFiles(const std::string& gameResPath)
|
||||
{
|
||||
if (!s_hasModTextures) return;
|
||||
s_gameResPath = gameResPath;
|
||||
|
||||
std::string vanillaTerrain = gameResPath + "\\terrain.png";
|
||||
std::string vanillaItems = gameResPath + "\\items.png";
|
||||
std::string mergedTerrain = GetMergedTerrainPath();
|
||||
std::string mergedItems = GetMergedItemsPath();
|
||||
|
||||
if (!mergedTerrain.empty() && !s_blockEntries.empty())
|
||||
{
|
||||
s_backupTerrainPath = vanillaTerrain + ".weaveloader_backup";
|
||||
CopyFileA(vanillaTerrain.c_str(), s_backupTerrainPath.c_str(), FALSE);
|
||||
if (CopyFileA(mergedTerrain.c_str(), vanillaTerrain.c_str(), FALSE))
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: installed merged terrain.png over game file");
|
||||
else
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: WARNING - failed to copy merged terrain.png (err=%lu)", GetLastError());
|
||||
}
|
||||
|
||||
if (!mergedItems.empty() && !s_itemEntries.empty())
|
||||
{
|
||||
s_backupItemsPath = vanillaItems + ".weaveloader_backup";
|
||||
CopyFileA(vanillaItems.c_str(), s_backupItemsPath.c_str(), FALSE);
|
||||
if (CopyFileA(mergedItems.c_str(), vanillaItems.c_str(), FALSE))
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: installed merged items.png over game file");
|
||||
else
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: WARNING - failed to copy merged items.png (err=%lu)", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void CreateModIcons(void* textureMap)
|
||||
{
|
||||
if (!s_hasModTextures || !s_simpleIconCtor || !textureMap) return;
|
||||
@@ -421,22 +480,6 @@ namespace ModAtlas
|
||||
|
||||
fixForAtlas(s_terrainTextureMap, 0, L"stone");
|
||||
fixForAtlas(s_itemsTextureMap, 1, L"diamond");
|
||||
|
||||
// Restore backed-up vanilla atlas files
|
||||
if (!s_backupTerrainPath.empty())
|
||||
{
|
||||
std::string vanillaTerrain = s_gameResPath + "\\terrain.png";
|
||||
if (MoveFileExA(s_backupTerrainPath.c_str(), vanillaTerrain.c_str(), MOVEFILE_REPLACE_EXISTING))
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: restored original terrain.png");
|
||||
s_backupTerrainPath.clear();
|
||||
}
|
||||
if (!s_backupItemsPath.empty())
|
||||
{
|
||||
std::string vanillaItems = s_gameResPath + "\\items.png";
|
||||
if (MoveFileExA(s_backupItemsPath.c_str(), vanillaItems.c_str(), MOVEFILE_REPLACE_EXISTING))
|
||||
LogUtil::Log("[WeaveLoader] ModAtlas: restored original items.png");
|
||||
s_backupItemsPath.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void* LookupModIcon(const std::wstring& name)
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
/// <summary>
|
||||
/// Builds merged terrain.png and items.png atlases from mod assets.
|
||||
/// Scans mods/*/assets/blocks/*.png and items/*.png, stitches into vanilla atlases.
|
||||
/// Scans mods/*/assets/blocks/*.png and items/*.png, stitches into copies of the
|
||||
/// vanilla atlases stored in mods/ModLoader/generated/. A CreateFileW hook redirects
|
||||
/// the game's file opens to the merged copies so vanilla files are never modified.
|
||||
/// </summary>
|
||||
namespace ModAtlas
|
||||
{
|
||||
@@ -41,9 +43,12 @@ namespace ModAtlas
|
||||
/// Set the original registerIcon function for vanilla icon lookups
|
||||
void SetRegisterIconFn(RegisterIcon_fn fn);
|
||||
|
||||
/// Copy merged atlas PNGs over the game's vanilla atlas files.
|
||||
/// Call before Minecraft::init so the game loads our textures.
|
||||
void InstallAtlasFiles(const std::string& gameResPath);
|
||||
/// Install a CreateFileW hook that redirects terrain.png/items.png reads to
|
||||
/// the merged atlases. Call after BuildAtlases, before Minecraft::init.
|
||||
bool InstallCreateFileHook(const std::string& gameResPath);
|
||||
|
||||
/// Remove the CreateFileW hook after init has loaded textures into memory.
|
||||
void RemoveCreateFileHook();
|
||||
|
||||
/// Create SimpleIcon objects for our mod textures after loadUVs runs.
|
||||
/// Call from loadUVs hook after original returns. atlasType is read from textureMap.
|
||||
|
||||
Reference in New Issue
Block a user