wip
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "SpellOverrides.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <cwchar>
|
||||
|
||||
namespace moonwell
|
||||
{
|
||||
@@ -69,6 +71,259 @@ namespace moonwell
|
||||
constexpr uint32_t kTraitorFlag = 0x40000000u;
|
||||
bool g_loginCharacterIsTraitor = false;
|
||||
|
||||
constexpr char kLaunchAccountVariable[] = "MOONWELL_LAUNCH_ACCOUNT";
|
||||
constexpr char kLaunchTicketVariable[] = "MOONWELL_LAUNCH_TICKET";
|
||||
constexpr char kDeveloperLoginVariable[] = "MOONWELL_DEV_LOGIN";
|
||||
constexpr char kLauncherLoginMarker[] = "__MOONWELL_LAUNCHER__";
|
||||
constexpr wchar_t kLauncherProtocol[] = L"moonwell://authorize?source=client";
|
||||
constexpr std::array<const wchar_t*, 2> kLauncherExecutables = {
|
||||
L"MoonWell.exe", L"MoonWellLauncher.exe",
|
||||
};
|
||||
constexpr size_t kMaxLaunchAccountLength = 320;
|
||||
constexpr size_t kLaunchTicketLength = 16;
|
||||
|
||||
std::array<char, kMaxLaunchAccountLength + 1> g_launchAccount{};
|
||||
std::array<char, kLaunchTicketLength + 1> g_launchTicket{};
|
||||
volatile LONG g_hasLauncherAuth = 0;
|
||||
bool g_launchedWithTicket = false;
|
||||
bool g_developerLogin = false;
|
||||
volatile LONG g_launcherLoginState = 0; // 0=pending, 1=claimed, 2=submitted
|
||||
DWORD g_launcherAuthCapturedAt = 0;
|
||||
DWORD g_clientThreadId = 0;
|
||||
|
||||
template <size_t Size>
|
||||
bool ConsumeEnvironmentVariable(const char* name, std::array<char, Size>& output)
|
||||
{
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
const DWORD required = GetEnvironmentVariableA(name, nullptr, 0);
|
||||
if (!required)
|
||||
{
|
||||
SetEnvironmentVariableA(name, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (required > output.size())
|
||||
{
|
||||
SetEnvironmentVariableA(name, nullptr);
|
||||
WLOG_ERROR("moonwell: rejected oversized launcher environment value %s", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const DWORD written = GetEnvironmentVariableA(
|
||||
name, output.data(), static_cast<DWORD>(output.size()));
|
||||
SetEnvironmentVariableA(name, nullptr);
|
||||
return written > 0 && written < output.size();
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
bool IsPrintableAscii(const std::array<char, Size>& value)
|
||||
{
|
||||
for (const unsigned char character : value)
|
||||
{
|
||||
if (character == 0)
|
||||
return true;
|
||||
if (character < 0x21 || character > 0x7e)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidLauncherTicket()
|
||||
{
|
||||
if (std::strlen(g_launchTicket.data()) != kLaunchTicketLength)
|
||||
return false;
|
||||
|
||||
for (size_t index = 0; index < kLaunchTicketLength; ++index)
|
||||
{
|
||||
const char character = g_launchTicket[index];
|
||||
if (!((character >= 'A' && character <= 'Z')
|
||||
|| (character >= '0' && character <= '9')))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClearLauncherAuth()
|
||||
{
|
||||
SecureZeroMemory(g_launchAccount.data(), g_launchAccount.size());
|
||||
SecureZeroMemory(g_launchTicket.data(), g_launchTicket.size());
|
||||
InterlockedExchange(&g_hasLauncherAuth, 0);
|
||||
}
|
||||
|
||||
void CaptureLaunchEnvironment()
|
||||
{
|
||||
const bool hasAccount = ConsumeEnvironmentVariable(
|
||||
kLaunchAccountVariable, g_launchAccount);
|
||||
const bool hasTicket = ConsumeEnvironmentVariable(
|
||||
kLaunchTicketVariable, g_launchTicket);
|
||||
|
||||
g_hasLauncherAuth = hasAccount && hasTicket
|
||||
&& IsPrintableAscii(g_launchAccount)
|
||||
&& IsValidLauncherTicket() ? 1 : 0;
|
||||
InterlockedExchange(&g_launcherLoginState, 0);
|
||||
g_launchedWithTicket = g_hasLauncherAuth != 0;
|
||||
g_launcherAuthCapturedAt = g_hasLauncherAuth ? GetTickCount() : 0;
|
||||
|
||||
if (!g_hasLauncherAuth)
|
||||
{
|
||||
if (hasAccount || hasTicket)
|
||||
WLOG_ERROR("moonwell: incomplete or invalid launcher authorization data");
|
||||
ClearLauncherAuth();
|
||||
}
|
||||
|
||||
std::array<char, 8> developerValue{};
|
||||
const bool developerRequested = ConsumeEnvironmentVariable(
|
||||
kDeveloperLoginVariable, developerValue);
|
||||
#if defined(_DEBUG)
|
||||
g_developerLogin = developerRequested
|
||||
&& std::strcmp(developerValue.data(), "1") == 0;
|
||||
#else
|
||||
g_developerLogin = false;
|
||||
#endif
|
||||
SecureZeroMemory(developerValue.data(), developerValue.size());
|
||||
|
||||
if (g_hasLauncherAuth)
|
||||
WLOG_INFO("moonwell: launcher authorization data accepted");
|
||||
else if (g_developerLogin)
|
||||
WLOG_INFO("moonwell: developer login enabled for this Debug build");
|
||||
}
|
||||
|
||||
int __cdecl GetLaunchMode(void* state)
|
||||
{
|
||||
const char* mode = g_launchedWithTicket
|
||||
? "launcher"
|
||||
: (g_developerLogin ? "developer" : "locked");
|
||||
wxl::game::script::PushString(state, mode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __cdecl ConsumeLauncherAuth(void* state)
|
||||
{
|
||||
constexpr uintptr_t kLoginGlueReady = 0x00B6B474;
|
||||
constexpr uintptr_t kLoginServerHost = 0x00B6AF54;
|
||||
constexpr uintptr_t kLoginServerPort = 0x00B6AF5C;
|
||||
constexpr uintptr_t kLoginBusy = 0x00B6AA38;
|
||||
if (!g_hasLauncherAuth
|
||||
|| !*reinterpret_cast<volatile uint8_t*>(kLoginGlueReady)
|
||||
|| !*reinterpret_cast<void* volatile*>(kLoginServerHost)
|
||||
|| !*reinterpret_cast<void* volatile*>(kLoginServerPort)
|
||||
|| *reinterpret_cast<volatile uint32_t*>(kLoginBusy))
|
||||
return 0;
|
||||
|
||||
if (InterlockedCompareExchange(&g_launcherLoginState, 1, 0) != 0)
|
||||
return 0;
|
||||
if (!g_hasLauncherAuth)
|
||||
{
|
||||
InterlockedExchange(&g_launcherLoginState, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxl::game::script::PushString(state, g_launchAccount.data());
|
||||
wxl::game::script::PushString(state, g_launchTicket.data());
|
||||
ClearLauncherAuth();
|
||||
InterlockedExchange(&g_launcherLoginState, 2);
|
||||
WLOG_INFO("moonwell: launcher authorization consumed by Lua bridge");
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool TryOpenAdjacentLauncher()
|
||||
{
|
||||
std::array<wchar_t, MAX_PATH> path{};
|
||||
const DWORD length = GetModuleFileNameW(nullptr, path.data(),
|
||||
static_cast<DWORD>(path.size()));
|
||||
if (!length || length >= path.size())
|
||||
return false;
|
||||
|
||||
wchar_t* slash = std::wcsrchr(path.data(), L'\\');
|
||||
if (!slash)
|
||||
return false;
|
||||
|
||||
++slash;
|
||||
const size_t prefixLength = static_cast<size_t>(slash - path.data());
|
||||
for (const wchar_t* executable : kLauncherExecutables)
|
||||
{
|
||||
const size_t launcherLength = std::wcslen(executable);
|
||||
if (prefixLength + launcherLength >= path.size())
|
||||
continue;
|
||||
|
||||
std::wmemcpy(slash, executable, launcherLength + 1);
|
||||
if (GetFileAttributesW(path.data()) == INVALID_FILE_ATTRIBUTES)
|
||||
continue;
|
||||
|
||||
if (reinterpret_cast<INT_PTR>(ShellExecuteW(
|
||||
nullptr, L"open", path.data(), nullptr, nullptr, SW_SHOWNORMAL)) > 32)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int __cdecl OpenLauncher(void* state)
|
||||
{
|
||||
bool opened = TryOpenAdjacentLauncher();
|
||||
if (!opened)
|
||||
{
|
||||
opened = reinterpret_cast<INT_PTR>(ShellExecuteW(
|
||||
nullptr, L"open", kLauncherProtocol, nullptr, nullptr, SW_SHOWNORMAL)) > 32;
|
||||
}
|
||||
|
||||
wxl::game::script::PushBoolean(state, opened);
|
||||
return 1;
|
||||
}
|
||||
|
||||
using DefaultServerLoginCallbackFn = int(__cdecl*)(void*);
|
||||
using BeginServerLoginFn = void(__cdecl*)(const char*, const char*);
|
||||
DefaultServerLoginCallbackFn g_nextDefaultServerLogin = nullptr;
|
||||
|
||||
bool SubmitLauncherAuthorization()
|
||||
{
|
||||
if (!g_hasLauncherAuth || g_launcherLoginState != 0)
|
||||
return false;
|
||||
|
||||
constexpr uintptr_t kBeginServerLogin = 0x004D8A30;
|
||||
constexpr uintptr_t kLoginGlueReady = 0x00B6B474;
|
||||
constexpr uintptr_t kLoginServerHost = 0x00B6AF54;
|
||||
constexpr uintptr_t kLoginServerPort = 0x00B6AF5C;
|
||||
constexpr uintptr_t kLoginBusy = 0x00B6AA38;
|
||||
if (!*reinterpret_cast<volatile uint8_t*>(kLoginGlueReady)
|
||||
|| !*reinterpret_cast<void* volatile*>(kLoginServerHost)
|
||||
|| !*reinterpret_cast<void* volatile*>(kLoginServerPort)
|
||||
|| *reinterpret_cast<volatile uint32_t*>(kLoginBusy))
|
||||
return false;
|
||||
|
||||
if (InterlockedCompareExchange(&g_launcherLoginState, 1, 0) != 0)
|
||||
return false;
|
||||
if (!g_hasLauncherAuth)
|
||||
{
|
||||
InterlockedExchange(&g_launcherLoginState, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
reinterpret_cast<BeginServerLoginFn>(kBeginServerLogin)(
|
||||
g_launchAccount.data(), g_launchTicket.data());
|
||||
if (!*reinterpret_cast<volatile uint32_t*>(kLoginBusy))
|
||||
{
|
||||
InterlockedExchange(&g_launcherLoginState, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearLauncherAuth();
|
||||
InterlockedExchange(&g_launcherLoginState, 2);
|
||||
WLOG_INFO("moonwell: launcher authorization submitted to login engine");
|
||||
return true;
|
||||
}
|
||||
|
||||
int __cdecl DefaultServerLoginHook(void* state)
|
||||
{
|
||||
const char* account = wxl::game::script::IsString(state, 1)
|
||||
? wxl::game::script::ToString(state, 1)
|
||||
: nullptr;
|
||||
if (!account || std::strcmp(account, kLauncherLoginMarker) != 0)
|
||||
return g_nextDefaultServerLogin ? g_nextDefaultServerLogin(state) : 0;
|
||||
|
||||
SubmitLauncherAuthorization();
|
||||
return 0;
|
||||
}
|
||||
|
||||
using GxSetProjectionFn = void(__fastcall*)(void* self, void* edx, const void* projection);
|
||||
GxSetProjectionFn g_nextSetProjection = nullptr;
|
||||
|
||||
@@ -439,55 +694,207 @@ namespace moonwell
|
||||
using RegisterFunctionFn = void(__cdecl*)(const char*, wxl::game::script::Function);
|
||||
using ValidateCallbackFn = void(__cdecl*)(uintptr_t);
|
||||
using GetContextFn = void*(__cdecl*)();
|
||||
RegisterFunctionFn g_nextRegisterFunction = nullptr;
|
||||
using ExecuteFn = void(__cdecl*)(const char*, uintptr_t, uintptr_t);
|
||||
using InitializeLuaFn = int(__cdecl*)(void*);
|
||||
using FramePumpFn = void(__cdecl*)(float, uint32_t);
|
||||
using FileOpenFn = int(__stdcall*)(void*, const char*, uint32_t, void**);
|
||||
using GlueModelRenderFn = void(__cdecl*)(void*);
|
||||
ValidateCallbackFn g_nextValidateCallback = nullptr;
|
||||
void* g_registeredState = nullptr;
|
||||
ExecuteFn g_nextExecute = nullptr;
|
||||
InitializeLuaFn g_nextInitializeLua = nullptr;
|
||||
FramePumpFn g_nextFramePump = nullptr;
|
||||
FileOpenFn g_nextFileOpen = nullptr;
|
||||
GlueModelRenderFn g_nextGlueModelRender = nullptr;
|
||||
PVOID volatile g_registeredState = nullptr;
|
||||
bool g_registeringMoonWell = false;
|
||||
|
||||
bool IsMoonWellCallback(uintptr_t callback)
|
||||
{
|
||||
return callback == reinterpret_cast<uintptr_t>(&SetLoginCharacterFlags)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&IsTraitor)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&SetCharacterCreateCamera);
|
||||
|| callback == reinterpret_cast<uintptr_t>(&SetCharacterCreateCamera)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&GetLaunchMode)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&ConsumeLauncherAuth)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&OpenLauncher);
|
||||
}
|
||||
|
||||
void RegisterLuaFunctionsForCurrentState();
|
||||
|
||||
void __cdecl ValidateCallbackHook(uintptr_t callback)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
if (!IsMoonWellCallback(callback) && g_nextValidateCallback)
|
||||
g_nextValidateCallback(callback);
|
||||
}
|
||||
|
||||
void RegisterLuaFunctionsForCurrentState()
|
||||
void RegisterLuaFunctions(void* state)
|
||||
{
|
||||
if (!g_nextRegisterFunction || g_registeringMoonWell) return;
|
||||
constexpr uintptr_t kGetContext = 0x00817DB0;
|
||||
void* state = reinterpret_cast<GetContextFn>(kGetContext)();
|
||||
if (g_registeringMoonWell) return;
|
||||
if (!state || state == g_registeredState) return;
|
||||
|
||||
constexpr uintptr_t kRegisterFunction = 0x00817F90;
|
||||
const auto registrar = reinterpret_cast<RegisterFunctionFn>(kRegisterFunction);
|
||||
g_registeringMoonWell = true;
|
||||
g_nextRegisterFunction("MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
g_nextRegisterFunction("MoonWellIsTraitor", &IsTraitor);
|
||||
g_nextRegisterFunction("MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
registrar("MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
registrar("MoonWellIsTraitor", &IsTraitor);
|
||||
registrar("MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
registrar("MoonWellGetLaunchMode", &GetLaunchMode);
|
||||
registrar("MoonWellConsumeLauncherAuth", &ConsumeLauncherAuth);
|
||||
registrar("MoonWellOpenLauncher", &OpenLauncher);
|
||||
g_registeringMoonWell = false;
|
||||
g_registeredState = state;
|
||||
InterlockedExchangePointer(&g_registeredState, state);
|
||||
WLOG_INFO("moonwell: Lua functions registered for state %p", state);
|
||||
}
|
||||
|
||||
void __cdecl RegisterFunctionHook(const char* name, wxl::game::script::Function function)
|
||||
void RegisterLuaFunctionsForCurrentState()
|
||||
{
|
||||
constexpr uintptr_t kGetContext = 0x00817DB0;
|
||||
RegisterLuaFunctions(reinterpret_cast<GetContextFn>(kGetContext)());
|
||||
}
|
||||
|
||||
void RegisterLuaFunctionsBeforeHooksEnable()
|
||||
{
|
||||
if (g_nextRegisterFunction) g_nextRegisterFunction(name, function);
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
}
|
||||
|
||||
void __cdecl OnClientMessage(void*, const void*)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
}
|
||||
|
||||
struct WindowSearch
|
||||
{
|
||||
DWORD processId;
|
||||
HWND window;
|
||||
};
|
||||
|
||||
BOOL CALLBACK FindClientWindow(HWND window, LPARAM parameter)
|
||||
{
|
||||
auto* search = reinterpret_cast<WindowSearch*>(parameter);
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
if (processId != search->processId || !IsWindowVisible(window))
|
||||
return TRUE;
|
||||
|
||||
search->window = window;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD WINAPI ScheduleLuaBootstrap(LPVOID)
|
||||
{
|
||||
HWND clientWindow = nullptr;
|
||||
WLOG_INFO("moonwell: Lua bootstrap scheduler started");
|
||||
for (unsigned attempt = 0; attempt < 600; ++attempt)
|
||||
{
|
||||
if (!clientWindow)
|
||||
{
|
||||
clientWindow = FindWindowW(L"GxWindowClass", nullptr);
|
||||
DWORD ownerProcessId = 0;
|
||||
if (clientWindow)
|
||||
GetWindowThreadProcessId(clientWindow, &ownerProcessId);
|
||||
if (ownerProcessId != GetCurrentProcessId())
|
||||
clientWindow = nullptr;
|
||||
if (!clientWindow)
|
||||
{
|
||||
WindowSearch search{GetCurrentProcessId(), nullptr};
|
||||
EnumWindows(&FindClientWindow, reinterpret_cast<LPARAM>(&search));
|
||||
clientWindow = search.window;
|
||||
}
|
||||
}
|
||||
if (clientWindow)
|
||||
{
|
||||
WLOG_INFO("moonwell: client window %p found for Lua bootstrap", clientWindow);
|
||||
// BeginServerLogin only snapshots the supplied credentials and starts
|
||||
// the client's asynchronous login state machine. Waiting here keeps the
|
||||
// call clear of engine initialization and Glue archive mounting.
|
||||
for (unsigned attempt = 0;
|
||||
attempt < 80 && g_hasLauncherAuth;
|
||||
++attempt)
|
||||
{
|
||||
Sleep(250);
|
||||
SubmitLauncherAuthorization();
|
||||
}
|
||||
return g_hasLauncherAuth ? 1 : 0;
|
||||
}
|
||||
Sleep(100);
|
||||
}
|
||||
WLOG_ERROR("moonwell: timed out waiting for UI-thread Lua bootstrap");
|
||||
return 1;
|
||||
}
|
||||
|
||||
void __cdecl ExecuteHook(const char* source, uintptr_t argument2, uintptr_t argument3)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
if (g_nextExecute) g_nextExecute(source, argument2, argument3);
|
||||
}
|
||||
|
||||
int __cdecl InitializeLuaHook(void* allocatorContext)
|
||||
{
|
||||
const int initialized = g_nextInitializeLua
|
||||
? g_nextInitializeLua(allocatorContext)
|
||||
: 0;
|
||||
if (initialized)
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
return initialized;
|
||||
}
|
||||
|
||||
void __cdecl FramePumpHook(float deltaSeconds, uint32_t frameTimeMs)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
if (g_nextFramePump) g_nextFramePump(deltaSeconds, frameTimeMs);
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
if (g_hasLauncherAuth
|
||||
&& GetTickCount() - g_launcherAuthCapturedAt >= 3000)
|
||||
SubmitLauncherAuthorization();
|
||||
}
|
||||
|
||||
int __stdcall FileOpenHook(void* archive, const char* name, uint32_t flags, void** out)
|
||||
{
|
||||
// The engine-init callback and synchronous Glue loader run on the same client
|
||||
// thread. Polling here catches Lua becoming live before AccountLogin executes,
|
||||
// without ever touching the state from background asset I/O workers.
|
||||
if (GetCurrentThreadId() == g_clientThreadId)
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
const int result = g_nextFileOpen ? g_nextFileOpen(archive, name, flags, out) : 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void __cdecl GlueModelRenderHook(void* frame)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
if (g_nextGlueModelRender) g_nextGlueModelRender(frame);
|
||||
}
|
||||
|
||||
void __cdecl OnFrame(void*, const void*)
|
||||
{
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
// Glue scripting is not a reliable authentication trigger: a broken
|
||||
// cosmetic widget can abort AccountLogin_OnShow before it reaches the
|
||||
// login call. Present is emitted on the client thread after the engine
|
||||
// and Glue subsystem are ready, so submit once after a short grace period.
|
||||
if (g_hasLauncherAuth
|
||||
&& GetTickCount() - g_launcherAuthCapturedAt >= 3000)
|
||||
SubmitLauncherAuthorization();
|
||||
}
|
||||
|
||||
bool InstallLuaBridge()
|
||||
{
|
||||
const bool validator = HookByName("Lua.ValidateFunctionPointer", &ValidateCallbackHook,
|
||||
&g_nextValidateCallback);
|
||||
const bool registrar = HookByName("Lua.RegisterFunction", &RegisterFunctionHook,
|
||||
&g_nextRegisterFunction);
|
||||
if (!validator || !registrar)
|
||||
const bool executor = HookByName("Lua.Execute", &ExecuteHook, &g_nextExecute);
|
||||
const bool initializer = Hook("Lua.Initialize", 0x00819BB0,
|
||||
&InitializeLuaHook, &g_nextInitializeLua);
|
||||
const bool defaultLogin = Hook("MoonWell.DefaultServerLogin", 0x004DC260,
|
||||
&DefaultServerLoginHook, &g_nextDefaultServerLogin);
|
||||
const bool framePump = HookByName("Frame.Pump", &FramePumpHook, &g_nextFramePump);
|
||||
const bool fileOpen = HookByName("Io.FileOpen", &FileOpenHook, &g_nextFileOpen);
|
||||
const bool glueRender = HookByName("Gx.GlueModelRender", &GlueModelRenderHook,
|
||||
&g_nextGlueModelRender);
|
||||
if (!validator || !executor || !initializer || !defaultLogin
|
||||
|| !framePump || !fileOpen || !glueRender)
|
||||
WLOG_ERROR("moonwell: Lua bridge hook installation failed");
|
||||
return validator && registrar;
|
||||
return validator && executor && initializer && defaultLogin
|
||||
&& framePump && fileOpen && glueRender;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,9 +911,24 @@ int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
moonwell::g_api = api;
|
||||
moonwell::g_clientThreadId = GetCurrentThreadId();
|
||||
|
||||
moonwell::CaptureLaunchEnvironment();
|
||||
moonwell::InstallBoot();
|
||||
const bool lua = moonwell::InstallLuaBridge();
|
||||
// Event ordinal 5 is wxl::events::Event::OnFrame in API v1. It is emitted
|
||||
// from Present on the client thread even while only the Glue UI is active.
|
||||
if (api->Subscribe)
|
||||
{
|
||||
api->Subscribe(5, &moonwell::OnFrame, nullptr);
|
||||
api->Subscribe(17, &moonwell::OnClientMessage, nullptr);
|
||||
}
|
||||
moonwell::RegisterLuaFunctionsBeforeHooksEnable();
|
||||
if (HANDLE bootstrapThread = CreateThread(
|
||||
nullptr, 0, &moonwell::ScheduleLuaBootstrap, nullptr, 0, nullptr))
|
||||
CloseHandle(bootstrapThread);
|
||||
else
|
||||
api->Log(WXL_LOG_ERROR, "MoonWell", "%s", "failed to start Lua bootstrap scheduler");
|
||||
moonwell::InstallCharacterCreateCamera();
|
||||
moonwell::InstallEncounterJournalModelPreview();
|
||||
const bool spells = moonwell::spells::Install(api);
|
||||
|
||||
Reference in New Issue
Block a user