feat(modloader): add item use and summon APIs

Add managed item use hooks, server-authoritative summon support, and side-aware use item context.

Include IdHelper mappings, example ruby wand usage, and related runtime/crash-handler updates.
This commit is contained in:
Jacobwasbeast
2026-03-07 19:54:52 -06:00
parent eabf8fe858
commit 0b4c87acbc
21 changed files with 1862 additions and 45 deletions

View File

@@ -3,14 +3,30 @@
#include "PdbParser.h"
#include <Windows.h>
#include <TlHelp32.h>
#include <eh.h>
#include <csignal>
#include <exception>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
static HMODULE s_runtimeModule = nullptr;
static uintptr_t s_gameBase = 0;
static volatile LONG s_handling = 0;
#ifndef STATUS_STACK_BUFFER_OVERRUN
#define STATUS_STACK_BUFFER_OVERRUN ((DWORD)0xC0000409)
#endif
#ifndef STATUS_INVALID_CRUNTIME_PARAMETER
#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD)0xC0000417)
#endif
#ifndef STATUS_FAIL_FAST_EXCEPTION
#define STATUS_FAIL_FAST_EXCEPTION ((DWORD)0xC0000602)
#endif
static const char* ExceptionCodeToString(DWORD code)
{
switch (code)
@@ -34,8 +50,11 @@ static const char* ExceptionCodeToString(DWORD code)
case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION";
case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW";
case STATUS_STACK_BUFFER_OVERRUN: return "STATUS_STACK_BUFFER_OVERRUN";
case STATUS_INVALID_CRUNTIME_PARAMETER: return "STATUS_INVALID_CRUNTIME_PARAMETER";
case STATUS_FAIL_FAST_EXCEPTION: return "STATUS_FAIL_FAST_EXCEPTION";
default: return "UNKNOWN_EXCEPTION";
}
}
@@ -60,6 +79,9 @@ static bool IsFatalException(DWORD code)
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
case EXCEPTION_PRIV_INSTRUCTION:
case EXCEPTION_STACK_OVERFLOW:
case STATUS_STACK_BUFFER_OVERRUN:
case STATUS_INVALID_CRUNTIME_PARAMETER:
case STATUS_FAIL_FAST_EXCEPTION:
return true;
default:
return false;
@@ -143,40 +165,18 @@ static void WalkStack(CONTEXT* ctx)
}
}
static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
static void WriteCrashReport(const char* origin, EXCEPTION_RECORD* er, CONTEXT* ctx)
{
if (!ep || !ep->ExceptionRecord || !ep->ContextRecord)
return EXCEPTION_CONTINUE_SEARCH;
if (!er || !ctx)
return;
DWORD code = ep->ExceptionRecord->ExceptionCode;
// The game's debug build uses __debugbreak() (int 3) as assertions throughout
// the texture system and other subsystems. Without a debugger the default
// handler terminates the process. We skip past the 1-byte int 3 instruction
// so the game's fallback code (e.g. missing-texture) can run normally.
if (code == EXCEPTION_BREAKPOINT && s_gameBase != 0)
{
DWORD64 rip = ep->ContextRecord->Rip;
HMODULE hMod = nullptr;
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(rip), &hMod) &&
reinterpret_cast<uintptr_t>(hMod) == s_gameBase)
{
ep->ContextRecord->Rip += 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
if (!IsFatalException(code))
return EXCEPTION_CONTINUE_SEARCH;
// Prevent re-entrancy if the crash handler itself crashes
if (InterlockedCompareExchange(&s_handling, 1, 0) != 0)
return EXCEPTION_CONTINUE_SEARCH;
return;
EXCEPTION_RECORD* er = ep->ExceptionRecord;
CONTEXT* ctx = ep->ContextRecord;
DWORD code = er->ExceptionCode;
DWORD64 faultAddr = er->ExceptionAddress
? reinterpret_cast<DWORD64>(er->ExceptionAddress)
: ctx->Rip;
SYSTEMTIME st;
GetLocalTime(&st);
@@ -190,8 +190,9 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
LogUtil::LogCrash(" %s", timeBuf);
LogUtil::LogCrash("========================================");
LogUtil::LogCrash("");
LogUtil::LogCrash("Origin: %s", origin ? origin : "<unknown>");
LogUtil::LogCrash("Exception: %s (0x%08X)", ExceptionCodeToString(code), code);
LogUtil::LogCrash("Address: 0x%016llX", reinterpret_cast<DWORD64>(er->ExceptionAddress));
LogUtil::LogCrash("Address: 0x%016llX", faultAddr);
LogUtil::LogCrash("Thread: %lu", GetCurrentThreadId());
if (code == EXCEPTION_ACCESS_VIOLATION && er->NumberParameters >= 2)
@@ -202,9 +203,7 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
LogUtil::LogCrash("Fault: %s of address 0x%016llX", op, er->ExceptionInformation[1]);
}
// Module containing the faulting address + PDB symbol resolution
{
DWORD64 faultAddr = reinterpret_cast<DWORD64>(er->ExceptionAddress);
char modPath[MAX_PATH] = {0};
DWORD64 modBase = 0;
GetModuleForAddr(faultAddr, modPath, sizeof(modPath), &modBase);
@@ -237,7 +236,6 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
LogUtil::LogCrash("");
WalkStack(ctx);
// Loaded modules
LogUtil::LogCrash("");
LogUtil::LogCrash("Loaded modules:");
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
@@ -266,6 +264,96 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
LogUtil::Log("[WeaveLoader] CRASH DETECTED - see logs/crash.log for details");
InterlockedExchange(&s_handling, 0);
}
static void LogCapturedCrash(const char* origin, DWORD code)
{
EXCEPTION_RECORD er{};
er.ExceptionCode = code;
er.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
CONTEXT ctx{};
RtlCaptureContext(&ctx);
er.ExceptionAddress = reinterpret_cast<void*>(ctx.Rip);
WriteCrashReport(origin, &er, &ctx);
}
static void __cdecl PurecallHandler()
{
LogCapturedCrash("_purecall", STATUS_INVALID_CRUNTIME_PARAMETER);
TerminateProcess(GetCurrentProcess(), STATUS_INVALID_CRUNTIME_PARAMETER);
}
static void __cdecl TerminateHandler()
{
LogCapturedCrash("std::terminate", STATUS_FAIL_FAST_EXCEPTION);
TerminateProcess(GetCurrentProcess(), STATUS_FAIL_FAST_EXCEPTION);
}
static void __cdecl SignalHandler(int sig)
{
DWORD code = STATUS_FAIL_FAST_EXCEPTION;
switch (sig)
{
case SIGABRT: code = STATUS_STACK_BUFFER_OVERRUN; break;
case SIGSEGV: code = EXCEPTION_ACCESS_VIOLATION; break;
case SIGILL: code = EXCEPTION_ILLEGAL_INSTRUCTION; break;
case SIGFPE: code = EXCEPTION_FLT_INVALID_OPERATION; break;
default: break;
}
LogCapturedCrash("signal", code);
TerminateProcess(GetCurrentProcess(), code);
}
static void InvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, uintptr_t)
{
LogCapturedCrash("_invalid_parameter", STATUS_INVALID_CRUNTIME_PARAMETER);
TerminateProcess(GetCurrentProcess(), STATUS_INVALID_CRUNTIME_PARAMETER);
}
static LONG WINAPI TopLevelHandler(EXCEPTION_POINTERS* ep)
{
if (!ep || !ep->ExceptionRecord || !ep->ContextRecord)
return EXCEPTION_CONTINUE_SEARCH;
if (!IsFatalException(ep->ExceptionRecord->ExceptionCode))
return EXCEPTION_CONTINUE_SEARCH;
WriteCrashReport("UnhandledExceptionFilter", ep->ExceptionRecord, ep->ContextRecord);
return EXCEPTION_CONTINUE_SEARCH;
}
static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
{
if (!ep || !ep->ExceptionRecord || !ep->ContextRecord)
return EXCEPTION_CONTINUE_SEARCH;
DWORD code = ep->ExceptionRecord->ExceptionCode;
// The game's debug build uses __debugbreak() (int 3) as assertions throughout
// the texture system and other subsystems. Without a debugger the default
// handler terminates the process. We skip past the 1-byte int 3 instruction
// so the game's fallback code (e.g. missing-texture) can run normally.
if (code == EXCEPTION_BREAKPOINT && s_gameBase != 0)
{
DWORD64 rip = ep->ContextRecord->Rip;
HMODULE hMod = nullptr;
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(rip), &hMod) &&
reinterpret_cast<uintptr_t>(hMod) == s_gameBase)
{
ep->ContextRecord->Rip += 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
if (!IsFatalException(code))
return EXCEPTION_CONTINUE_SEARCH;
WriteCrashReport("VectoredExceptionHandler", ep->ExceptionRecord, ep->ContextRecord);
return EXCEPTION_CONTINUE_SEARCH;
}
@@ -276,6 +364,14 @@ void Install(HMODULE runtimeModule)
{
s_runtimeModule = runtimeModule;
AddVectoredExceptionHandler(1, VectoredHandler);
SetUnhandledExceptionFilter(TopLevelHandler);
_set_invalid_parameter_handler(InvalidParameterHandler);
_set_purecall_handler(PurecallHandler);
std::set_terminate(TerminateHandler);
std::signal(SIGABRT, SignalHandler);
std::signal(SIGSEGV, SignalHandler);
std::signal(SIGILL, SignalHandler);
std::signal(SIGFPE, SignalHandler);
}
void SetGameBase(uintptr_t base)

View File

@@ -23,6 +23,8 @@ static managed_entry_fn fn_PostInit = nullptr;
static managed_entry_fn fn_Tick = nullptr;
static managed_entry_fn fn_Shutdown = nullptr;
static managed_entry_fn fn_ItemMineBlock = nullptr;
static managed_entry_fn fn_ItemUse = nullptr;
static managed_entry_fn fn_EntitySummoned = nullptr;
static bool LoadHostfxr()
{
@@ -179,6 +181,8 @@ bool DotNetHost::Initialize()
ok &= resolve(L"Tick", &fn_Tick);
ok &= resolve(L"Shutdown", &fn_Shutdown);
ok &= resolve(L"OnItemMineBlock", &fn_ItemMineBlock);
ok &= resolve(L"OnItemUse", &fn_ItemUse);
ok &= resolve(L"OnEntitySummoned", &fn_EntitySummoned);
if (!ok)
{
@@ -236,6 +240,29 @@ int DotNetHost::CallItemMineBlock(const void* args, int sizeBytes)
return fn_ItemMineBlock(const_cast<void*>(args), sizeBytes);
}
int DotNetHost::CallItemUse(const void* args, int sizeBytes)
{
if (!fn_ItemUse || !args || sizeBytes <= 0)
return 0;
return fn_ItemUse(const_cast<void*>(args), sizeBytes);
}
void DotNetHost::CallEntitySummoned(int entityNumericId, float x, float y, float z)
{
if (!fn_EntitySummoned)
return;
struct EntitySummonedNativeArgs
{
int entityNumericId;
float x;
float y;
float z;
} native{ entityNumericId, x, y, z };
fn_EntitySummoned(&native, sizeof(native));
}
void DotNetHost::Cleanup()
{
}

View File

@@ -16,4 +16,6 @@ namespace DotNetHost
void CallTick();
void CallShutdown();
int CallItemMineBlock(const void* args, int sizeBytes);
int CallItemUse(const void* args, int sizeBytes);
void CallEntitySummoned(int entityNumericId, float x, float y, float z);
}

View File

@@ -10,6 +10,9 @@
#include <cstdio>
#include <cstring>
#include <cwctype>
#include <memory>
#include <cstddef>
#include <vector>
namespace GameHooks
{
@@ -29,6 +32,9 @@ namespace GameHooks
ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock = nullptr;
ItemMineBlock_fn Original_ItemMineBlock = nullptr;
ItemMineBlock_fn Original_DiggerItemMineBlock = nullptr;
GameModeUseItem_fn Original_ServerPlayerGameModeUseItem = nullptr;
GameModeUseItem_fn Original_MultiPlayerGameModeUseItem = nullptr;
MinecraftSetLevel_fn Original_MinecraftSetLevel = nullptr;
TexturesBindTextureResource_fn Original_TexturesBindTextureResource = nullptr;
TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName = nullptr;
TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex = nullptr;
@@ -37,6 +43,29 @@ namespace GameHooks
StitchedTextureUV_fn Original_StitchedGetV0 = nullptr;
StitchedTextureUV_fn Original_StitchedGetV1 = nullptr;
static int s_itemMineBlockHookCalls = 0;
static void* s_currentLevel = nullptr;
static thread_local void* s_activeUseLevel = nullptr;
static LevelAddEntity_fn s_levelAddEntity = nullptr;
static EntityIONewById_fn s_entityIoNewById = nullptr;
static EntityMoveTo_fn s_entityMoveTo = nullptr;
static EntitySetPos_fn s_entitySetPos = nullptr;
static EntityGetLookAngle_fn s_entityGetLookAngle = nullptr;
static LivingEntityGetViewVector_fn s_livingEntityGetViewVector = nullptr;
static EntityLerpMotion_fn s_entityLerpMotion = nullptr;
static InventoryRemoveResource_fn s_inventoryRemoveResource = nullptr;
static void* s_inventoryVtable = nullptr;
static ItemInstanceHurtAndBreak_fn s_itemInstanceHurtAndBreak = nullptr;
// Verified from compiled Player::inventory accesses in this game build.
static constexpr ptrdiff_t kPlayerInventoryOffset = 0x340;
static constexpr ptrdiff_t kLevelIsClientSideOffset = 0x268;
static constexpr ptrdiff_t kEntityXOffset = 0x78;
static constexpr ptrdiff_t kEntityYOffset = 0x80;
static constexpr ptrdiff_t kEntityZOffset = 0x88;
static constexpr ptrdiff_t kEntityRemovedOffset = 0xC7;
static constexpr ptrdiff_t kFireballOwnerOffset = 0x1D0;
static constexpr ptrdiff_t kFireballXPowerOffset = 0x1E8;
static constexpr ptrdiff_t kFireballYPowerOffset = 0x1F0;
static constexpr ptrdiff_t kFireballZPowerOffset = 0x1F8;
static void* s_textureAtlasLocationBlocks = nullptr;
static void* s_textureAtlasLocationItems = nullptr;
static int s_textureAtlasIdBlocks = -1;
@@ -60,6 +89,12 @@ namespace GameHooks
static bool s_pageResourceInit = false;
static int s_pageRouteLogCount = 0;
static int s_forcedTerrainRouteLogCount = 0;
static uintptr_t s_gameModuleBase = 0;
static uintptr_t s_gameModuleEnd = 0;
static std::vector<void*> s_spawnedEntities;
static int s_outOfWorldGuardLogCount = 0;
static int s_pendingServerUseItemId = -1;
static ULONGLONG s_pendingServerUseExpiryMs = 0;
static void EnsurePageResourcesInitialized()
{
@@ -71,6 +106,73 @@ namespace GameHooks
s_pageResourceInit = true;
}
static void EnsureGameModuleRange()
{
if (s_gameModuleBase != 0 && s_gameModuleEnd > s_gameModuleBase)
return;
HMODULE exe = GetModuleHandleW(nullptr);
if (!exe)
return;
auto* dos = reinterpret_cast<IMAGE_DOS_HEADER*>(exe);
if (!dos || dos->e_magic != IMAGE_DOS_SIGNATURE)
return;
auto* nt = reinterpret_cast<IMAGE_NT_HEADERS*>(
reinterpret_cast<unsigned char*>(exe) + dos->e_lfanew);
if (!nt || nt->Signature != IMAGE_NT_SIGNATURE)
return;
s_gameModuleBase = reinterpret_cast<uintptr_t>(exe);
s_gameModuleEnd = s_gameModuleBase + nt->OptionalHeader.SizeOfImage;
}
static bool IsCanonicalUserPtr(const void* ptr)
{
uintptr_t p = reinterpret_cast<uintptr_t>(ptr);
if (p < 0x10000ULL)
return false;
// User-mode canonical range on x64 Windows.
if (p >= 0x0000800000000000ULL)
return false;
return true;
}
static bool IsGameCodePtr(const void* ptr)
{
if (!ptr)
return false;
EnsureGameModuleRange();
if (s_gameModuleBase == 0 || s_gameModuleEnd <= s_gameModuleBase)
return false;
uintptr_t p = reinterpret_cast<uintptr_t>(ptr);
return p >= s_gameModuleBase && p < s_gameModuleEnd;
}
static bool IsReadableRange(const void* ptr, size_t bytes)
{
if (!ptr || bytes == 0 || !IsCanonicalUserPtr(ptr))
return false;
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(ptr, &mbi, sizeof(mbi)) != sizeof(mbi))
return false;
if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS || (mbi.Protect & PAGE_GUARD))
return false;
const DWORD readMask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY |
PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
if ((mbi.Protect & readMask) == 0)
return false;
uintptr_t p = reinterpret_cast<uintptr_t>(ptr);
uintptr_t end = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
if (p + bytes < p)
return false;
return (p + bytes) <= end;
}
static std::wstring BuildVirtualAtlasPath(int atlasType, int page)
{
std::wstring base = L"/modloader/";
@@ -193,6 +295,20 @@ namespace GameHooks
int z;
};
struct UseItemNativeArgs
{
int itemId;
int isTestUseOnly;
int isClientSide;
void* itemInstancePtr;
void* playerPtr;
void* playerSharedPtr;
};
static bool IsFireballFamilyEntityId(int entityNumericId);
static void* DecodeItemInstancePtrFromSharedArg(void* sharedArg);
static void* DecodePlayerPtrFromSharedArg(void* sharedArg);
static bool TryReadItemId(void* itemInstancePtr, int& outItemId)
{
if (!itemInstancePtr)
@@ -203,16 +319,16 @@ namespace GameHooks
static const int kCandidateOffsets[] = { 0x20, 0x18, 0x10, 0x28 };
for (int off : kCandidateOffsets)
{
__try
const char* idPtr = static_cast<char*>(itemInstancePtr) + off;
if (!IsReadableRange(idPtr, sizeof(int)))
continue;
int id = *reinterpret_cast<const int*>(idPtr);
if (id > 0 && id < 32000)
{
int id = *reinterpret_cast<int*>(static_cast<char*>(itemInstancePtr) + off);
if (id > 0 && id < 32000)
{
outItemId = id;
return true;
}
outItemId = id;
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
}
return false;
@@ -232,6 +348,386 @@ namespace GameHooks
return action;
}
static int TryDispatchUseItemFromSharedItemArg(void* itemInstanceSharedPtr, void* playerSharedPtr, bool bTestUseOnly, const char* sourceTag)
{
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
if (!itemInstancePtr)
return 0;
int itemId = 0;
if (!TryReadItemId(itemInstancePtr, itemId))
return 0;
int isClientSide = 0;
if (s_activeUseLevel &&
IsReadableRange(static_cast<char*>(s_activeUseLevel) + kLevelIsClientSideOffset, sizeof(bool)))
{
isClientSide =
*reinterpret_cast<bool*>(static_cast<char*>(s_activeUseLevel) + kLevelIsClientSideOffset) ? 1 : 0;
}
UseItemNativeArgs args{ itemId, bTestUseOnly ? 1 : 0, isClientSide, itemInstancePtr, playerPtr, playerSharedPtr };
return DotNetHost::CallItemUse(&args, sizeof(args));
}
void SetSummonSymbols(void* levelAddEntity,
void* entityIoNewById,
void* entityMoveTo,
void* entitySetPos)
{
s_levelAddEntity = reinterpret_cast<LevelAddEntity_fn>(levelAddEntity);
s_entityIoNewById = reinterpret_cast<EntityIONewById_fn>(entityIoNewById);
s_entityMoveTo = reinterpret_cast<EntityMoveTo_fn>(entityMoveTo);
s_entitySetPos = reinterpret_cast<EntitySetPos_fn>(entitySetPos);
}
void SetUseActionSymbols(void* inventoryRemoveResource,
void* inventoryVtable,
void* itemInstanceHurtAndBreak,
void* containerBroadcastChanges,
void* entityGetLookAngle,
void* livingEntityGetViewVector,
void* entityLerpMotion,
void* entitySetPos)
{
s_inventoryRemoveResource = reinterpret_cast<InventoryRemoveResource_fn>(inventoryRemoveResource);
s_inventoryVtable = inventoryVtable;
s_itemInstanceHurtAndBreak = reinterpret_cast<ItemInstanceHurtAndBreak_fn>(itemInstanceHurtAndBreak);
s_entityGetLookAngle = reinterpret_cast<EntityGetLookAngle_fn>(entityGetLookAngle);
s_livingEntityGetViewVector = reinterpret_cast<LivingEntityGetViewVector_fn>(livingEntityGetViewVector);
s_entityLerpMotion = reinterpret_cast<EntityLerpMotion_fn>(entityLerpMotion);
s_entitySetPos = reinterpret_cast<EntitySetPos_fn>(entitySetPos);
}
static bool IsInventoryObjectPtr(void* objectPtr)
{
if (!objectPtr || !s_inventoryVtable || !IsReadableRange(objectPtr, sizeof(void*)))
return false;
void* vt = *reinterpret_cast<void**>(objectPtr);
if (!IsCanonicalUserPtr(vt))
return false;
return vt == s_inventoryVtable;
}
static void* FindInventoryPtrFromPlayer(void* playerPtr)
{
if (!playerPtr || !s_inventoryRemoveResource || !IsCanonicalUserPtr(playerPtr))
return nullptr;
const char* base = static_cast<const char*>(playerPtr);
const void* slotPtr = base + kPlayerInventoryOffset;
if (!IsReadableRange(slotPtr, sizeof(void*)))
return nullptr;
void* inventoryPtr = *reinterpret_cast<void* const*>(slotPtr);
if (!IsInventoryObjectPtr(inventoryPtr))
return nullptr;
return inventoryPtr;
}
bool ConsumePlayerResource(void* playerPtr, int itemId, int count)
{
static int s_consumeFailLogCount = 0;
if (!playerPtr || !s_inventoryRemoveResource || !s_inventoryVtable || itemId < 0 || count <= 0)
return false;
void* inventoryPtr = FindInventoryPtrFromPlayer(playerPtr);
if (!inventoryPtr)
{
if (s_consumeFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] ConsumePlayerResource: no inventory ptr (player=%p item=%d count=%d)",
playerPtr, itemId, count);
s_consumeFailLogCount++;
}
return false;
}
for (int i = 0; i < count; ++i)
{
__try
{
if (!s_inventoryRemoveResource(inventoryPtr, itemId))
{
if (s_consumeFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] ConsumePlayerResource: removeResource false (inv=%p item=%d)",
inventoryPtr, itemId);
s_consumeFailLogCount++;
}
return false;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
if (s_consumeFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] ConsumePlayerResource: exception (inv=%p item=%d)", inventoryPtr, itemId);
s_consumeFailLogCount++;
}
return false;
}
}
return true;
}
bool DamageItemInstance(void* itemInstancePtr, int amount, void* ownerSharedPtr)
{
if (!itemInstancePtr || !ownerSharedPtr || amount <= 0 || !s_itemInstanceHurtAndBreak)
return false;
__try
{
s_itemInstanceHurtAndBreak(itemInstancePtr, amount, ownerSharedPtr);
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
static bool TryReadLookVector(void* playerPtr, double& dx, double& dy, double& dz)
{
dx = 0.0;
dy = 0.0;
dz = 0.0;
if (!playerPtr)
return false;
void* vec = nullptr;
if (s_livingEntityGetViewVector)
vec = s_livingEntityGetViewVector(playerPtr, 1.0f);
else if (s_entityGetLookAngle)
vec = s_entityGetLookAngle(playerPtr);
if (!IsReadableRange(vec, sizeof(double) * 3))
return false;
dx = *reinterpret_cast<double*>(static_cast<char*>(vec) + 0x00);
dy = *reinterpret_cast<double*>(static_cast<char*>(vec) + 0x08);
dz = *reinterpret_cast<double*>(static_cast<char*>(vec) + 0x10);
return true;
}
static bool LooksLikeEntityPtr(void* candidate)
{
if (!candidate || !IsReadableRange(candidate, sizeof(void*)))
return false;
void* vt = *reinterpret_cast<void**>(candidate);
if (!IsCanonicalUserPtr(vt))
return false;
if (!IsGameCodePtr(vt))
return false;
return true;
}
static bool TryReadPlayerPos(void* playerPtr, double& x, double& y, double& z)
{
x = y = z = 0.0;
if (!playerPtr)
return false;
char* base = static_cast<char*>(playerPtr);
if (!IsReadableRange(base + kEntityXOffset, sizeof(double)) ||
!IsReadableRange(base + kEntityYOffset, sizeof(double)) ||
!IsReadableRange(base + kEntityZOffset, sizeof(double)))
return false;
x = *reinterpret_cast<double*>(base + kEntityXOffset);
y = *reinterpret_cast<double*>(base + kEntityYOffset);
z = *reinterpret_cast<double*>(base + kEntityZOffset);
return x > -32000000.0 && x < 32000000.0 &&
y > -2048.0 && y < 4096.0 &&
z > -32000000.0 && z < 32000000.0;
}
static bool IsEntityMarkedRemoved(void* entityPtr)
{
if (!entityPtr || !IsReadableRange(static_cast<char*>(entityPtr) + kEntityRemovedOffset, sizeof(bool)))
return true;
return *reinterpret_cast<bool*>(static_cast<char*>(entityPtr) + kEntityRemovedOffset);
}
static void MarkEntityRemoved(void* entityPtr)
{
if (!entityPtr || !IsReadableRange(static_cast<char*>(entityPtr) + kEntityRemovedOffset, sizeof(bool)))
return;
*reinterpret_cast<bool*>(static_cast<char*>(entityPtr) + kEntityRemovedOffset) = true;
}
static void TrackSpawnedEntity(void* entityPtr)
{
if (!entityPtr)
return;
s_spawnedEntities.push_back(entityPtr);
}
static void CullSpawnedEntitiesBelowWorld()
{
if (s_spawnedEntities.empty())
return;
size_t write = 0;
for (size_t read = 0; read < s_spawnedEntities.size(); ++read)
{
void* entityPtr = s_spawnedEntities[read];
if (!entityPtr || !LooksLikeEntityPtr(entityPtr))
continue;
if (IsEntityMarkedRemoved(entityPtr))
continue;
double x = 0.0, y = 0.0, z = 0.0;
if (!TryReadPlayerPos(entityPtr, x, y, z))
continue;
if (y < -1.0)
{
MarkEntityRemoved(entityPtr);
if (s_outOfWorldGuardLogCount < 20)
{
LogUtil::Log("[WeaveLoader] OutOfWorldGuard: removed spawned entity=%p at y=%.3f", entityPtr, y);
s_outOfWorldGuardLogCount++;
}
continue;
}
s_spawnedEntities[write++] = entityPtr;
}
s_spawnedEntities.resize(write);
}
bool SummonEntityFromPlayerLook(void* playerPtr,
void* playerSharedPtr,
int entityNumericId,
double speed,
double spawnForward,
double spawnUp)
{
static int s_summonFailLogCount = 0;
void* levelPtr = s_activeUseLevel ? s_activeUseLevel : s_currentLevel;
if (!levelPtr || !s_levelAddEntity || !playerPtr)
{
if (s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: preconditions level=%p add=%p player=%p shared=%p id=%d",
levelPtr, s_levelAddEntity, playerPtr, playerSharedPtr, entityNumericId);
s_summonFailLogCount++;
}
return false;
}
double dx = 0.0, dy = 0.0, dz = 0.0;
if (!TryReadLookVector(playerPtr, dx, dy, dz))
{
if (s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: look vector unavailable (player=%p)", playerPtr);
s_summonFailLogCount++;
}
return false;
}
double px = 0.0, py = 0.0, pz = 0.0;
if (!TryReadPlayerPos(playerPtr, px, py, pz))
{
if (s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: player position unavailable (player=%p)", playerPtr);
s_summonFailLogCount++;
}
return false;
}
const double sx = px + (dx * spawnForward);
const double sy = py + spawnUp + (dy * spawnForward);
const double sz = pz + (dz * spawnForward);
std::shared_ptr<void> entity;
if (!s_entityIoNewById)
{
if (s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: newById unavailable (id=%d)", entityNumericId);
s_summonFailLogCount++;
}
return false;
}
s_entityIoNewById(&entity, entityNumericId, levelPtr);
if (!entity)
{
if (s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: EntityIO::newById returned null (id=%d)", entityNumericId);
s_summonFailLogCount++;
}
return false;
}
if (s_entityMoveTo)
s_entityMoveTo(entity.get(), sx, sy, sz, 0.0f, 0.0f);
else if (s_entitySetPos)
s_entitySetPos(entity.get(), sx, sy, sz);
if (s_entityLerpMotion)
s_entityLerpMotion(entity.get(), dx * speed, dy * speed, dz * speed);
if (IsFireballFamilyEntityId(entityNumericId) &&
IsReadableRange(entity.get(), kFireballZPowerOffset + sizeof(double)))
{
// Source + PDB: Fireball packets initialize xPower/yPower/zPower separately from xd/yd/zd.
// EntityIO::newById gives us a game-owned shared_ptr<Entity>; patch the fireball fields in place.
*reinterpret_cast<void**>(static_cast<char*>(entity.get()) + kFireballOwnerOffset) = nullptr;
*reinterpret_cast<double*>(static_cast<char*>(entity.get()) + kFireballXPowerOffset) = dx * 0.10;
*reinterpret_cast<double*>(static_cast<char*>(entity.get()) + kFireballYPowerOffset) = dy * 0.10;
*reinterpret_cast<double*>(static_cast<char*>(entity.get()) + kFireballZPowerOffset) = dz * 0.10;
}
bool added = s_levelAddEntity(levelPtr, &entity);
if (!added && s_summonFailLogCount < 20)
{
LogUtil::Log("[WeaveLoader] SummonFromLook fail: Level::addEntity returned false (id=%d)", entityNumericId);
s_summonFailLogCount++;
}
if (added)
{
TrackSpawnedEntity(entity.get());
DotNetHost::CallEntitySummoned(entityNumericId, static_cast<float>(sx), static_cast<float>(sy), static_cast<float>(sz));
}
return added;
}
bool SummonEntityByNumericId(int entityNumericId, double x, double y, double z)
{
void* levelPtr = s_activeUseLevel ? s_activeUseLevel : s_currentLevel;
if (!levelPtr || !s_levelAddEntity || !s_entityIoNewById)
return false;
std::shared_ptr<void> entity;
s_entityIoNewById(&entity, entityNumericId, levelPtr);
if (!entity)
return false;
if (s_entityMoveTo)
s_entityMoveTo(entity.get(), x, y, z, 0.0f, 0.0f);
bool added = s_levelAddEntity(levelPtr, &entity);
if (added)
{
TrackSpawnedEntity(entity.get());
DotNetHost::CallEntitySummoned(entityNumericId, static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
}
return added;
}
void SetAtlasLocationPointers(void* blocksLocation, void* itemsLocation)
{
s_textureAtlasLocationBlocks = blocksLocation;
@@ -250,16 +746,34 @@ namespace GameHooks
LogUtil::Log("[WeaveLoader] Atlas IDs: blocks=%d items=%d", s_textureAtlasIdBlocks, s_textureAtlasIdItems);
}
static bool TryReadVec3(void* vecPtr, double& x, double& y, double& z)
{
if (!IsReadableRange(vecPtr, sizeof(double) * 3))
return false;
x = *reinterpret_cast<double*>(static_cast<char*>(vecPtr) + 0x00);
y = *reinterpret_cast<double*>(static_cast<char*>(vecPtr) + 0x08);
z = *reinterpret_cast<double*>(static_cast<char*>(vecPtr) + 0x10);
return true;
}
static bool IsFireballFamilyEntityId(int entityNumericId)
{
return entityNumericId == 12
|| entityNumericId == 13
|| entityNumericId == 19
|| entityNumericId == 1000;
}
static void* DecodeItemInstancePtrFromSharedArg(void* sharedArg)
{
if (!sharedArg)
if (!sharedArg || !IsCanonicalUserPtr(sharedArg))
return nullptr;
// Candidate A: shared_ptr<ItemInstance> object where first field is raw ItemInstance*.
__try
{
void* p = *reinterpret_cast<void**>(sharedArg);
if (p)
if (p && IsCanonicalUserPtr(p))
{
int id = 0;
if (TryReadItemId(p, id)) return p;
@@ -278,6 +792,37 @@ namespace GameHooks
return nullptr;
}
static void* DecodePlayerPtrFromSharedArg(void* sharedArg)
{
static int s_decodePlayerLogCount = 0;
if (!sharedArg || !IsCanonicalUserPtr(sharedArg))
return nullptr;
// shared_ptr<Player> is passed by reference; first field is raw Player*.
__try
{
void* p = *reinterpret_cast<void**>(sharedArg);
if (LooksLikeEntityPtr(p))
{
if (s_decodePlayerLogCount < 8)
{
LogUtil::Log("[WeaveLoader] DecodePlayer: shared=%p -> player=%p", sharedArg, p);
s_decodePlayerLogCount++;
}
return p;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
if (s_decodePlayerLogCount < 8)
{
LogUtil::Log("[WeaveLoader] DecodePlayer: failed shared=%p", sharedArg);
s_decodePlayerLogCount++;
}
return nullptr;
}
void __fastcall Hooked_LoadUVs(void* thisPtr)
{
LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: ENTER (textureMap=%p)", thisPtr);
@@ -510,6 +1055,79 @@ namespace GameHooks
return false;
}
bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly)
{
void* previousLevel = s_activeUseLevel;
s_activeUseLevel = level;
static int s_serverUseLogCount = 0;
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
int itemId = -1;
TryReadItemId(itemInstancePtr, itemId);
bool effectiveTestUseOnly = bTestUseOnly;
const ULONGLONG nowMs = GetTickCount64();
if (effectiveTestUseOnly &&
itemId >= 0 &&
s_pendingServerUseItemId == itemId &&
nowMs <= s_pendingServerUseExpiryMs)
{
effectiveTestUseOnly = false;
s_pendingServerUseItemId = -1;
s_pendingServerUseExpiryMs = 0;
}
if (s_serverUseLogCount < 40)
{
LogUtil::Log("[WeaveLoader] UseHook ServerPlayerGameMode::useItem test=%d effective=%d item=%d itemPtr=%p playerShared=%p level=%p",
bTestUseOnly ? 1 : 0, effectiveTestUseOnly ? 1 : 0, itemId, itemInstancePtr, playerSharedPtr, level);
s_serverUseLogCount++;
}
int action = TryDispatchUseItemFromSharedItemArg(itemInstanceSharedPtr, playerSharedPtr, effectiveTestUseOnly, "ServerPlayerGameMode::useItem");
s_activeUseLevel = previousLevel;
if (action == 2)
return true;
if (Original_ServerPlayerGameModeUseItem)
return Original_ServerPlayerGameModeUseItem(thisPtr, playerSharedPtr, level, itemInstanceSharedPtr, bTestUseOnly);
return false;
}
bool __fastcall Hooked_MultiPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly)
{
static int s_multiUseLogCount = 0;
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
int itemId = -1;
TryReadItemId(itemInstancePtr, itemId);
if (!bTestUseOnly && itemId >= 0)
{
s_pendingServerUseItemId = itemId;
s_pendingServerUseExpiryMs = GetTickCount64() + 1000;
}
if (s_multiUseLogCount < 40)
{
LogUtil::Log("[WeaveLoader] UseHook MultiPlayerGameMode::useItem test=%d item=%d itemPtr=%p playerShared=%p level=%p",
bTestUseOnly ? 1 : 0, itemId, itemInstancePtr, playerSharedPtr, level);
s_multiUseLogCount++;
}
void* previousLevel = s_activeUseLevel;
s_activeUseLevel = level;
int action = TryDispatchUseItemFromSharedItemArg(itemInstanceSharedPtr, playerSharedPtr, bTestUseOnly, "MultiPlayerGameMode::useItem");
s_activeUseLevel = previousLevel;
if (action == 2)
return true;
if (Original_MultiPlayerGameModeUseItem)
return Original_MultiPlayerGameModeUseItem(thisPtr, playerSharedPtr, level, itemInstanceSharedPtr, bTestUseOnly);
return false;
}
void __fastcall Hooked_MinecraftSetLevel(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut)
{
if (Original_MinecraftSetLevel)
{
Original_MinecraftSetLevel(thisPtr, level, message, forceInsertPlayerSharedPtr, doForceStatsSave, bPrimaryPlayerSignedOut);
}
s_currentLevel = level;
}
void* Hooked_GetResourceAsStream(const void* fileName)
{
const std::wstring* path = static_cast<const std::wstring*>(fileName);
@@ -587,7 +1205,9 @@ namespace GameHooks
void __fastcall Hooked_MinecraftTick(void* thisPtr, bool bFirst, bool bUpdateTextures)
{
CullSpawnedEntitiesBelowWorld();
Original_MinecraftTick(thisPtr, bFirst, bUpdateTextures);
CullSpawnedEntitiesBelowWorld();
if (bFirst)
{

View File

@@ -21,6 +21,18 @@ typedef void* (__fastcall *RegisterIcon_fn)(void* thisPtr, const std::wstring& n
typedef void* (__fastcall *ItemInstanceGetIcon_fn)(void* thisPtr);
typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
typedef bool (__fastcall *GameModeUseItem_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
typedef void (__fastcall *MinecraftSetLevel_fn)(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut);
typedef bool (__fastcall *LevelAddEntity_fn)(void* thisPtr, void* entitySharedPtr);
typedef void (__fastcall *EntityMoveTo_fn)(void* thisPtr, double x, double y, double z, float yRot, float xRot);
typedef void (__fastcall *EntityIONewById_fn)(void* outSharedPtr, int entityNumericId, void* level);
typedef void (__fastcall *EntitySetPos_fn)(void* thisPtr, double x, double y, double z);
typedef void* (__fastcall *EntityGetLookAngle_fn)(void* thisPtr);
typedef void* (__fastcall *LivingEntityGetViewVector_fn)(void* thisPtr, float partialTicks);
typedef void (__fastcall *EntityLerpMotion_fn)(void* thisPtr, double x, double y, double z);
typedef bool (__fastcall *InventoryRemoveResource_fn)(void* thisPtr, int itemId);
typedef void (__fastcall *ItemInstanceHurtAndBreak_fn)(void* thisPtr, int amount, void* ownerSharedPtr);
typedef void (__fastcall *AbstractContainerMenuBroadcastChanges_fn)(void* thisPtr);
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);
@@ -44,6 +56,9 @@ namespace GameHooks
extern ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock;
extern ItemMineBlock_fn Original_ItemMineBlock;
extern ItemMineBlock_fn Original_DiggerItemMineBlock;
extern GameModeUseItem_fn Original_ServerPlayerGameModeUseItem;
extern GameModeUseItem_fn Original_MultiPlayerGameModeUseItem;
extern MinecraftSetLevel_fn Original_MinecraftSetLevel;
extern TexturesBindTextureResource_fn Original_TexturesBindTextureResource;
extern TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName;
extern TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex;
@@ -68,6 +83,9 @@ namespace GameHooks
void __fastcall Hooked_ItemInstanceMineBlock(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
bool __fastcall Hooked_DiggerItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
bool __fastcall Hooked_MultiPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
void __fastcall Hooked_MinecraftSetLevel(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut);
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);
@@ -76,4 +94,25 @@ namespace GameHooks
float __fastcall Hooked_StitchedGetV0(void* thisPtr, bool adjust);
float __fastcall Hooked_StitchedGetV1(void* thisPtr, bool adjust);
void SetAtlasLocationPointers(void* blocksLocation, void* itemsLocation);
void SetSummonSymbols(void* levelAddEntity,
void* entityIoNewById,
void* entityMoveTo,
void* entitySetPos);
void SetUseActionSymbols(void* inventoryRemoveResource,
void* inventoryVtable,
void* itemInstanceHurtAndBreak,
void* containerBroadcastChanges,
void* entityGetLookAngle,
void* livingEntityGetViewVector,
void* entityLerpMotion,
void* entitySetPos);
bool SummonEntityByNumericId(int entityNumericId, double x, double y, double z);
bool ConsumePlayerResource(void* playerPtr, int itemId, int count);
bool DamageItemInstance(void* itemInstancePtr, int amount, void* ownerSharedPtr);
bool SummonEntityFromPlayerLook(void* playerPtr,
void* playerSharedPtr,
int entityNumericId,
double speed,
double spawnForward,
double spawnUp);
}

View File

@@ -54,6 +54,20 @@ bool HookManager::Install(const SymbolResolver& symbols)
LogUtil::Log("[WeaveLoader] Hooked Minecraft::init");
}
if (symbols.pMinecraftSetLevel)
{
if (MH_CreateHook(symbols.pMinecraftSetLevel,
reinterpret_cast<void*>(&GameHooks::Hooked_MinecraftSetLevel),
reinterpret_cast<void**>(&GameHooks::Original_MinecraftSetLevel)) != MH_OK)
{
LogUtil::Log("[WeaveLoader] Warning: Failed to hook Minecraft::setLevel");
}
else
{
LogUtil::Log("[WeaveLoader] Hooked Minecraft::setLevel (active level tracking)");
}
}
if (symbols.pItemInstanceMineBlock)
{
if (MH_CreateHook(symbols.pItemInstanceMineBlock,
@@ -110,6 +124,34 @@ bool HookManager::Install(const SymbolResolver& symbols)
}
}
if (symbols.pServerPlayerGameModeUseItem)
{
if (MH_CreateHook(symbols.pServerPlayerGameModeUseItem,
reinterpret_cast<void*>(&GameHooks::Hooked_ServerPlayerGameModeUseItem),
reinterpret_cast<void**>(&GameHooks::Original_ServerPlayerGameModeUseItem)) != MH_OK)
{
LogUtil::Log("[WeaveLoader] Warning: Failed to hook ServerPlayerGameMode::useItem");
}
else
{
LogUtil::Log("[WeaveLoader] Hooked ServerPlayerGameMode::useItem (managed item callbacks)");
}
}
if (symbols.pMultiPlayerGameModeUseItem)
{
if (MH_CreateHook(symbols.pMultiPlayerGameModeUseItem,
reinterpret_cast<void*>(&GameHooks::Hooked_MultiPlayerGameModeUseItem),
reinterpret_cast<void**>(&GameHooks::Original_MultiPlayerGameModeUseItem)) != MH_OK)
{
LogUtil::Log("[WeaveLoader] Warning: Failed to hook MultiPlayerGameMode::useItem");
}
else
{
LogUtil::Log("[WeaveLoader] Hooked MultiPlayerGameMode::useItem (managed item callbacks)");
}
}
GameHooks::SetAtlasLocationPointers(symbols.pTextureAtlasLocationBlocks, symbols.pTextureAtlasLocationItems);
if (symbols.pTexturesBindTextureResource)
@@ -207,6 +249,20 @@ bool HookManager::Install(const SymbolResolver& symbols)
GameObjectFactory::ResolveSymbols(const_cast<SymbolResolver&>(symbols));
FurnaceRecipeRegistry::ResolveSymbols(const_cast<SymbolResolver&>(symbols));
GameHooks::SetSummonSymbols(
symbols.pLevelAddEntity,
symbols.pEntityIONewById,
symbols.pEntityMoveTo,
symbols.pEntitySetPos);
GameHooks::SetUseActionSymbols(
symbols.pInventoryRemoveResource,
symbols.pInventoryVtable,
symbols.pItemInstanceHurtAndBreak,
symbols.pAbstractContainerMenuBroadcastChanges,
symbols.pEntityGetLookAngle,
symbols.pLivingEntityGetViewVector,
symbols.pEntityLerpMotion,
symbols.pEntitySetPos);
if (symbols.pLoadUVs && symbols.pSimpleIconCtor && symbols.pOperatorNew)
{

View File

@@ -3,6 +3,7 @@
#include "CreativeInventory.h"
#include "GameObjectFactory.h"
#include "FurnaceRecipeRegistry.h"
#include "GameHooks.h"
#include "ModStrings.h"
#include "LogUtil.h"
#include <Windows.h>
@@ -249,6 +250,57 @@ int native_get_entity_id(const char* namespacedId)
return IdRegistry::Instance().GetNumericId(IdRegistry::Type::Entity, namespacedId);
}
int native_consume_item_from_player(void* playerPtr, int numericItemId, int count)
{
if (numericItemId < 0 || count <= 0)
return 0;
return GameHooks::ConsumePlayerResource(playerPtr, numericItemId, count) ? 1 : 0;
}
int native_damage_item_instance(void* itemInstancePtr, int amount, void* ownerSharedPtr)
{
if (amount <= 0)
return 0;
return GameHooks::DamageItemInstance(itemInstancePtr, amount, ownerSharedPtr) ? 1 : 0;
}
int native_spawn_entity_from_player_look(void* playerPtr, void* playerSharedPtr, int numericEntityId, double speed, double spawnForward, double spawnUp)
{
if (numericEntityId < 0)
return 0;
return GameHooks::SummonEntityFromPlayerLook(playerPtr, playerSharedPtr, numericEntityId, speed, spawnForward, spawnUp) ? 1 : 0;
}
int native_summon_entity_by_id(int numericEntityId, double x, double y, double z)
{
if (!GameHooks::SummonEntityByNumericId(numericEntityId, x, y, z))
{
LogUtil::Log("[WeaveLoader] Summon failed: entity=%d at (%.2f, %.2f, %.2f)",
numericEntityId, x, y, z);
return 0;
}
LogUtil::Log("[WeaveLoader] Summoned entity=%d at (%.2f, %.2f, %.2f)",
numericEntityId, x, y, z);
return 1;
}
int native_summon_entity(const char* namespacedId, double x, double y, double z)
{
if (!namespacedId || !namespacedId[0])
return 0;
const int entityNumericId =
IdRegistry::Instance().GetNumericId(IdRegistry::Type::Entity, namespacedId);
if (entityNumericId < 0)
{
LogUtil::Log("[WeaveLoader] Summon failed: unknown entity id '%s'", namespacedId);
return 0;
}
return native_summon_entity_by_id(entityNumericId, x, y, z);
}
void native_subscribe_event(const char* eventName, void* managedFnPtr)
{
LogUtil::Log("[WeaveLoader] Event subscription: %s", eventName ? eventName : "(null)");

View File

@@ -57,6 +57,11 @@ extern "C"
__declspec(dllexport) int native_get_block_id(const char* namespacedId);
__declspec(dllexport) int native_get_item_id(const char* namespacedId);
__declspec(dllexport) int native_get_entity_id(const char* namespacedId);
__declspec(dllexport) int native_consume_item_from_player(void* playerPtr, int numericItemId, int count);
__declspec(dllexport) int native_damage_item_instance(void* itemInstancePtr, int amount, void* ownerSharedPtr);
__declspec(dllexport) int native_spawn_entity_from_player_look(void* playerPtr, void* playerSharedPtr, int numericEntityId, double speed, double spawnForward, double spawnUp);
__declspec(dllexport) int native_summon_entity(const char* namespacedId, double x, double y, double z);
__declspec(dllexport) int native_summon_entity_by_id(int numericEntityId, double x, double y, double z);
__declspec(dllexport) void native_subscribe_event(
const char* eventName,

View File

@@ -22,6 +22,8 @@ static const char* SYM_ITEMINSTANCE_GETICON = "?getIcon@ItemInstance@@QEAAPEAVIc
static const char* SYM_ITEMINSTANCE_MINEBLOCK = "?mineBlock@ItemInstance@@QEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@Z";
static const char* SYM_ITEM_MINEBLOCK = "?mineBlock@Item@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z";
static const char* SYM_DIGGERITEM_MINEBLOCK = "?mineBlock@DiggerItem@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z";
static const char* SYM_SERVER_PLAYER_GAMEMODE_USEITEM = "?useItem@ServerPlayerGameMode@@QEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z";
static const char* SYM_MULTI_PLAYER_GAMEMODE_USEITEM = "?useItem@MultiPlayerGameMode@@UEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z";
static const char* SYM_TEXTURES_BIND_RESOURCE = "?bindTexture@Textures@@QEAAXPEAVResourceLocation@@@Z";
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";
@@ -30,6 +32,19 @@ 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_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";
static const char* SYM_ENTITY_MOVETO = "?moveTo@Entity@@QEAAXNNNMM@Z";
static const char* SYM_ENTITY_SETPOS = "?setPos@Entity@@QEAAXNNN@Z";
static const char* SYM_LIVINGENTITY_GETLOOKANGLE = "?getLookAngle@LivingEntity@@UEAAPEAVVec3@@XZ";
static const char* SYM_LIVINGENTITY_GETVIEWVECTOR = "?getViewVector@LivingEntity@@UEAAPEAVVec3@@M@Z";
static const char* SYM_ENTITY_GETLOOKANGLE = "?getLookAngle@Entity@@UEAAPEAVVec3@@XZ";
static const char* SYM_ENTITY_LERPMOTION = "?lerpMotion@Entity@@UEAAXNNN@Z";
static const char* SYM_INVENTORY_REMOVERESOURCE = "?removeResource@Inventory@@QEAA_NH@Z";
static const char* SYM_INVENTORY_VFTABLE = "??_7Inventory@@6B@";
static const char* SYM_ITEMINSTANCE_HURTANDBREAK = "?hurtAndBreak@ItemInstance@@QEAAXHV?$shared_ptr@VLivingEntity@@@std@@@Z";
static const char* SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES = "?broadcastChanges@AbstractContainerMenu@@UEAAXXZ";
static const char* SYM_TEXATLAS_BLOCKS = "?LOCATION_BLOCKS@TextureAtlas@@2VResourceLocation@@A";
static const char* SYM_TEXATLAS_ITEMS = "?LOCATION_ITEMS@TextureAtlas@@2VResourceLocation@@A";
@@ -106,6 +121,8 @@ bool SymbolResolver::ResolveGameFunctions()
pItemInstanceMineBlock = Resolve(SYM_ITEMINSTANCE_MINEBLOCK);
pItemMineBlock = Resolve(SYM_ITEM_MINEBLOCK);
pDiggerItemMineBlock = Resolve(SYM_DIGGERITEM_MINEBLOCK);
pServerPlayerGameModeUseItem = Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEM);
pMultiPlayerGameModeUseItem = Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEM);
pTexturesBindTextureResource = Resolve(SYM_TEXTURES_BIND_RESOURCE);
pTexturesLoadTextureByName = Resolve(SYM_TEXTURES_LOAD_BY_NAME);
pTexturesLoadTextureByIndex = Resolve(SYM_TEXTURES_LOAD_BY_INDEX_PUBLIC);
@@ -115,6 +132,20 @@ bool SymbolResolver::ResolveGameFunctions()
pStitchedGetU1 = Resolve(SYM_STITCHED_GETU1);
pStitchedGetV0 = Resolve(SYM_STITCHED_GETV0);
pStitchedGetV1 = Resolve(SYM_STITCHED_GETV1);
pMinecraftSetLevel = Resolve(SYM_MINECRAFT_SETLEVEL);
pLevelAddEntity = Resolve(SYM_LEVEL_ADDENTITY);
pEntityIONewById = Resolve(SYM_ENTITYIO_NEWBYID);
pEntityMoveTo = Resolve(SYM_ENTITY_MOVETO);
pEntitySetPos = Resolve(SYM_ENTITY_SETPOS);
pEntityGetLookAngle = Resolve(SYM_LIVINGENTITY_GETLOOKANGLE);
pLivingEntityGetViewVector = Resolve(SYM_LIVINGENTITY_GETVIEWVECTOR);
if (!pEntityGetLookAngle)
pEntityGetLookAngle = Resolve(SYM_ENTITY_GETLOOKANGLE);
pEntityLerpMotion = Resolve(SYM_ENTITY_LERPMOTION);
pInventoryRemoveResource = Resolve(SYM_INVENTORY_REMOVERESOURCE);
pInventoryVtable = Resolve(SYM_INVENTORY_VFTABLE);
pItemInstanceHurtAndBreak = Resolve(SYM_ITEMINSTANCE_HURTANDBREAK);
pAbstractContainerMenuBroadcastChanges = Resolve(SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES);
pTextureAtlasLocationBlocks = Resolve(SYM_TEXATLAS_BLOCKS);
pTextureAtlasLocationItems = Resolve(SYM_TEXATLAS_ITEMS);
if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140.dll"), SYM_OPERATOR_NEW);
@@ -147,6 +178,8 @@ bool SymbolResolver::ResolveGameFunctions()
logSym("ItemInstance::mineBlock", pItemInstanceMineBlock);
logSym("Item::mineBlock", pItemMineBlock);
logSym("DiggerItem::mineBlock", pDiggerItemMineBlock);
logSym("ServerPlayerGameMode::useItem", pServerPlayerGameModeUseItem);
logSym("MultiPlayerGameMode::useItem", pMultiPlayerGameModeUseItem);
logSym("Textures::bindTexture(ResourceLocation)", pTexturesBindTextureResource);
logSym("Textures::loadTexture(TEXTURE_NAME,wstring)", pTexturesLoadTextureByName);
logSym("Textures::loadTexture(int)", pTexturesLoadTextureByIndex);
@@ -154,6 +187,18 @@ bool SymbolResolver::ResolveGameFunctions()
logSym("StitchedTexture::getU1", pStitchedGetU1);
logSym("StitchedTexture::getV0", pStitchedGetV0);
logSym("StitchedTexture::getV1", pStitchedGetV1);
logSym("Minecraft::setLevel", pMinecraftSetLevel);
logSym("Level::addEntity", pLevelAddEntity);
logSym("EntityIO::newById", pEntityIONewById);
logSym("Entity::moveTo", pEntityMoveTo);
logSym("Entity::setPos", pEntitySetPos);
logSym("LivingEntity/Entity::getLookAngle", pEntityGetLookAngle);
logSym("LivingEntity::getViewVector", pLivingEntityGetViewVector);
logSym("Entity::lerpMotion", pEntityLerpMotion);
logSym("Inventory::removeResource", pInventoryRemoveResource);
logSym("Inventory::vftable", pInventoryVtable);
logSym("ItemInstance::hurtAndBreak", pItemInstanceHurtAndBreak);
logSym("AbstractContainerMenu::broadcastChanges", pAbstractContainerMenuBroadcastChanges);
logSym("TextureAtlas::LOCATION_BLOCKS", pTextureAtlasLocationBlocks);
logSym("TextureAtlas::LOCATION_ITEMS", pTextureAtlasLocationItems);

View File

@@ -27,6 +27,8 @@ public:
void* pItemInstanceMineBlock = nullptr; // ItemInstance::mineBlock(Level*,int,int,int,int,shared_ptr<Player>)
void* pItemMineBlock = nullptr; // Item::mineBlock(shared_ptr<ItemInstance>,Level*,int,int,int,int,shared_ptr<LivingEntity>)
void* pDiggerItemMineBlock = nullptr; // DiggerItem::mineBlock(shared_ptr<ItemInstance>,Level*,int,int,int,int,shared_ptr<LivingEntity>)
void* pServerPlayerGameModeUseItem = nullptr; // ServerPlayerGameMode::useItem(shared_ptr<Player>,Level*,shared_ptr<ItemInstance>,bool)
void* pMultiPlayerGameModeUseItem = nullptr; // MultiPlayerGameMode::useItem(shared_ptr<Player>,Level*,shared_ptr<ItemInstance>,bool)
void* pTexturesBindTextureResource = nullptr; // Textures::bindTexture(ResourceLocation*)
void* pTexturesLoadTextureByName = nullptr; // Textures::loadTexture(TEXTURE_NAME,const wstring&)
void* pTexturesLoadTextureByIndex = nullptr; // Textures::loadTexture(int)
@@ -34,6 +36,18 @@ public:
void* pStitchedGetU1 = nullptr; // StitchedTexture::getU1(bool) const
void* pStitchedGetV0 = nullptr; // StitchedTexture::getV0(bool) const
void* pStitchedGetV1 = nullptr; // StitchedTexture::getV1(bool) const
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*)
void* pEntityMoveTo = nullptr; // Entity::moveTo(double,double,double,float,float)
void* pEntitySetPos = nullptr; // Entity::setPos(double,double,double)
void* pEntityGetLookAngle = nullptr; // Entity::getLookAngle()
void* pLivingEntityGetViewVector = nullptr; // LivingEntity::getViewVector(float)
void* pEntityLerpMotion = nullptr; // Entity::lerpMotion(double,double,double)
void* pInventoryRemoveResource = nullptr; // Inventory::removeResource(int)
void* pInventoryVtable = nullptr; // Inventory vftable
void* pItemInstanceHurtAndBreak = nullptr; // ItemInstance::hurtAndBreak(int,shared_ptr<LivingEntity>)
void* pAbstractContainerMenuBroadcastChanges = nullptr; // AbstractContainerMenu::broadcastChanges()
void* pTextureAtlasLocationBlocks = nullptr; // TextureAtlas::LOCATION_BLOCKS
void* pTextureAtlasLocationItems = nullptr; // TextureAtlas::LOCATION_ITEMS