полный переход на warcraft xl
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// MoonWell compatibility module for WarcraftXL.
|
||||
//
|
||||
// The stock 3.3.5a executable stays untouched on disk. These narrowly scoped
|
||||
// changes are verified and applied to the process image during WarcraftXL's
|
||||
// boot phase, before GlueXML is loaded.
|
||||
|
||||
#include "core/Logger.hpp"
|
||||
#include "core/Mem.hpp"
|
||||
#include "runtime/ModuleInstall.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace moonwell
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct Patch
|
||||
{
|
||||
const char* name;
|
||||
uintptr_t address;
|
||||
const uint8_t* expected;
|
||||
const uint8_t* replacement;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
bool Apply(const char* name, uintptr_t address,
|
||||
const std::array<uint8_t, N>& expected,
|
||||
const std::array<uint8_t, N>& replacement)
|
||||
{
|
||||
const auto* current = reinterpret_cast<const uint8_t*>(address);
|
||||
if (std::memcmp(current, replacement.data(), N) == 0)
|
||||
return true;
|
||||
if (std::memcmp(current, expected.data(), N) != 0)
|
||||
{
|
||||
WLOG_ERROR("moonwell: '%s' byte mismatch at %p; stock build 12340 required",
|
||||
name, reinterpret_cast<void*>(address));
|
||||
return false;
|
||||
}
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(address), replacement.data(), N))
|
||||
{
|
||||
WLOG_ERROR("moonwell: '%s' patch failed at %p", name,
|
||||
reinterpret_cast<void*>(address));
|
||||
return false;
|
||||
}
|
||||
WLOG_INFO("moonwell: applied '%s' at %p", name, reinterpret_cast<void*>(address));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InstallGlueUnlock()
|
||||
{
|
||||
// Same stock-client checks formerly changed in the distributed Wow.exe.
|
||||
// They allow custom GlueXML while keeping WarcraftXL's callback validator intact.
|
||||
bool ok = true;
|
||||
ok &= Apply("glue archive override", 0x005F4DBF,
|
||||
std::array<uint8_t, 1>{0x74}, std::array<uint8_t, 1>{0xEB});
|
||||
ok &= Apply("glue callback gate", 0x00816625,
|
||||
std::array<uint8_t, 1>{0x75}, std::array<uint8_t, 1>{0xEB});
|
||||
ok &= Apply("glue callback result A", 0x0081663F,
|
||||
std::array<uint8_t, 1>{0x01}, std::array<uint8_t, 1>{0x03});
|
||||
ok &= Apply("glue callback result B", 0x00816695,
|
||||
std::array<uint8_t, 1>{0x01}, std::array<uint8_t, 1>{0x03});
|
||||
ok &= Apply("glue callback compare", 0x00816746,
|
||||
std::array<uint8_t, 1>{0x7F}, std::array<uint8_t, 1>{0xEB});
|
||||
ok &= Apply("glue callback return", 0x0081675F,
|
||||
std::array<uint8_t, 7>{0x83,0xC0,0x03,0x5E,0x8B,0xE5,0x5D},
|
||||
std::array<uint8_t, 7>{0xB8,0x03,0x00,0x00,0x00,0xEB,0xED});
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool InstallCharacterModePacket()
|
||||
{
|
||||
// CreateCharacter(name, modeId): capture Lua argument 2 and place it in
|
||||
// the final CMSG_CHAR_CREATE byte that is zero in the stock client.
|
||||
constexpr std::array<uint8_t, 64> expectedCreate = {
|
||||
0x55,0x8B,0xEC,0x56,0x8B,0x75,0x08,0x6A,0x01,0x56,0xE8,0xF1,0xD2,0x36,0x00,
|
||||
0x83,0xC4,0x08,0x85,0xC0,0x74,0x0D,0x6A,0x00,0x6A,0x01,0x56,0xE8,0x60,0xD4,
|
||||
0x36,0x00,0x83,0xC4,0x0C,0x6A,0x00,0x6A,0x01,0x56,0xE8,0x53,0xD4,0x36,0x00,
|
||||
0x50,0xE8,0xED,0xF6,0xFF,0xFF,0x83,0xC4,0x10,0x33,0xC0,0x5E,0x5D,0xC3,
|
||||
0xCC,0xCC,0xCC,0xCC,0xCC
|
||||
};
|
||||
constexpr std::array<uint8_t, 64> replacementCreate = {
|
||||
0x55,0x8B,0xEC,0x56,0x8B,0x75,0x08,0x6A,0x02,0x56,0xE8,0xC1,0xD3,0x36,0x00,
|
||||
0xDB,0x1C,0x24,0x58,0x83,0xC4,0x04,0xA2,0x21,0x42,0xAC,0x00,0x6A,0x00,0x6A,
|
||||
0x01,0x56,0xE8,0x5B,0xD4,0x36,0x00,0x50,0xE8,0xF5,0xF6,0xFF,0xFF,0x83,0xC4,
|
||||
0x10,0x33,0xC0,0x5E,0x5D,0xC3,0x88,0x55,0xF0,0x8A,0x15,0x21,0x42,0xAC,
|
||||
0x00,0x88,0x55,0xF4,0xC3
|
||||
};
|
||||
constexpr std::array<uint8_t, 7> expectedPacket =
|
||||
{0x88,0x55,0xF0,0xC6,0x45,0xF4,0x00};
|
||||
constexpr std::array<uint8_t, 7> replacementPacket =
|
||||
{0xE8,0x5F,0x08,0x00,0x00,0x90,0x90};
|
||||
|
||||
return Apply("CreateCharacter mode argument", 0x004E0C60,
|
||||
expectedCreate, replacementCreate)
|
||||
&& Apply("CMSG_CHAR_CREATE mode byte", 0x004E042F,
|
||||
expectedPacket, replacementPacket);
|
||||
}
|
||||
|
||||
bool InstallCharacterFlags()
|
||||
{
|
||||
// GetCharacterInfo(index) gains return value 11: the character flags
|
||||
// from CharacterInfo+0x170. The glue code uses bit 0x40000000.
|
||||
constexpr uintptr_t patchAddress = 0x004E332D;
|
||||
constexpr uintptr_t returnAddress = 0x004E3336;
|
||||
constexpr uintptr_t luaPushNumber = 0x0084E2A0;
|
||||
constexpr std::array<uint8_t, 5> expected = {0x83,0xC4,0x2C,0x5E,0xB8};
|
||||
|
||||
if (*reinterpret_cast<const uint8_t*>(patchAddress) == 0xE9)
|
||||
return true;
|
||||
if (std::memcmp(reinterpret_cast<const void*>(patchAddress), expected.data(), expected.size()) != 0)
|
||||
{
|
||||
WLOG_ERROR("moonwell: GetCharacterInfo byte mismatch at %p",
|
||||
reinterpret_cast<void*>(patchAddress));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* cave = static_cast<uint8_t*>(VirtualAlloc(nullptr, 30, MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE));
|
||||
if (!cave)
|
||||
{
|
||||
WLOG_ERROR("moonwell: cannot allocate GetCharacterInfo thunk (win32=%lu)", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t prefix[] = {
|
||||
0x83,0xEC,0x08, // sub esp, 8
|
||||
0xDB,0x86,0x70,0x01,0x00,0x00, // fild dword ptr [esi+170h]
|
||||
0xDD,0x1C,0x24, // fstp qword ptr [esp]
|
||||
0x57, // push edi (lua_State*)
|
||||
0xE8,0,0,0,0, // call lua_pushnumber
|
||||
0x83,0xC4,0x38, // cleanup own args + stolen add esp,2Ch
|
||||
0x5E, // stolen pop esi
|
||||
0x6A,0x0B,0x58, // eax = 11
|
||||
0xE9,0,0,0,0 // jump to stock epilogue
|
||||
};
|
||||
static_assert(sizeof(prefix) == 30);
|
||||
std::memcpy(cave, prefix, sizeof(prefix));
|
||||
|
||||
const auto callRel = static_cast<int32_t>(luaPushNumber -
|
||||
(reinterpret_cast<uintptr_t>(cave) + 18));
|
||||
const auto backRel = static_cast<int32_t>(returnAddress -
|
||||
(reinterpret_cast<uintptr_t>(cave) + 30));
|
||||
std::memcpy(cave + 14, &callRel, sizeof(callRel));
|
||||
std::memcpy(cave + 26, &backRel, sizeof(backRel));
|
||||
FlushInstructionCache(GetCurrentProcess(), cave, 30);
|
||||
|
||||
std::array<uint8_t, 5> jump{0xE9,0,0,0,0};
|
||||
const auto caveRel = static_cast<int32_t>(reinterpret_cast<uintptr_t>(cave) -
|
||||
(patchAddress + jump.size()));
|
||||
std::memcpy(jump.data() + 1, &caveRel, sizeof(caveRel));
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(patchAddress), jump.data(), jump.size()))
|
||||
{
|
||||
VirtualFree(cave, 0, MEM_RELEASE);
|
||||
WLOG_ERROR("moonwell: GetCharacterInfo jump patch failed");
|
||||
return false;
|
||||
}
|
||||
WLOG_INFO("moonwell: GetCharacterInfo now returns charFlags as value 11");
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallBoot()
|
||||
{
|
||||
const bool glue = InstallGlueUnlock();
|
||||
const bool create = InstallCharacterModePacket();
|
||||
const bool flags = InstallCharacterFlags();
|
||||
if (!glue || !create || !flags)
|
||||
WLOG_ERROR("moonwell: compatibility module incomplete; see mismatches above");
|
||||
else
|
||||
WLOG_INFO("moonwell: compatibility module ready (stock Wow.exe remains untouched)");
|
||||
wxl::core::log::Flush();
|
||||
}
|
||||
|
||||
DWORD WINAPI FlushRuntimeLog(LPVOID)
|
||||
{
|
||||
// Let the remainder of RunAll() and the core-ready line reach the
|
||||
// shared buffered logger, then make startup diagnostics durable.
|
||||
Sleep(1000);
|
||||
wxl::core::log::Flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void InstallRuntimeLogFlush()
|
||||
{
|
||||
HANDLE thread = CreateThread(nullptr, 0, &FlushRuntimeLog, nullptr, 0, nullptr);
|
||||
if (thread) CloseHandle(thread);
|
||||
}
|
||||
|
||||
struct Registration
|
||||
{
|
||||
Registration()
|
||||
{
|
||||
wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot);
|
||||
wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush);
|
||||
}
|
||||
} g_registration;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user