diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index ea263d7f9..dd3f57632 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -32,6 +32,9 @@ obj\$(Configuration)\ + + + true $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) @@ -70,6 +73,7 @@ false false + module.def @@ -83,9 +87,13 @@ true true + module.def + + + nethost.dll @@ -181,6 +189,12 @@ + + + + + + diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index 8b4483684..a1b1650e2 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -147,4 +147,14 @@ + + + Dalamud.Boot DLL + + + + + Dalamud.Boot DLL + + \ No newline at end of file diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 2566016e8..cf31b7016 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -159,7 +159,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { return 0; } -DllExport DWORD WINAPI Initialize(LPVOID lpParam) { +extern "C" DWORD WINAPI Initialize(LPVOID lpParam) { return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr)); } diff --git a/Dalamud.Boot/module.def b/Dalamud.Boot/module.def new file mode 100644 index 000000000..047d825e5 --- /dev/null +++ b/Dalamud.Boot/module.def @@ -0,0 +1,5 @@ +LIBRARY Dalamud.Boot +EXPORTS + Initialize @1 + RewriteRemoteEntryPointW @2 + RewrittenEntryPoint @3 diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h index 3302a44fb..6dda9d03b 100644 --- a/Dalamud.Boot/pch.h +++ b/Dalamud.Boot/pch.h @@ -61,9 +61,6 @@ #include "unicode.h" -// Commonly used macros -#define DllExport extern "C" __declspec(dllexport) - // Global variables extern HMODULE g_hModule; extern HINSTANCE g_hGameInstance; diff --git a/Dalamud.Boot/rewrite_entrypoint.cpp b/Dalamud.Boot/rewrite_entrypoint.cpp index 85a3a950b..a47254701 100644 --- a/Dalamud.Boot/rewrite_entrypoint.cpp +++ b/Dalamud.Boot/rewrite_entrypoint.cpp @@ -5,111 +5,87 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue); struct RewrittenEntryPointParameters { - void* pAllocation; char* pEntrypoint; - char* pEntrypointBytes; size_t entrypointLength; - char* pLoadInfo; - HANDLE hMainThread; - HANDLE hMainThreadContinue; }; -#pragma pack(push, 1) -struct EntryPointThunkTemplate { - struct DUMMYSTRUCTNAME { - struct { - const uint8_t op_mov_rdi[2]{ 0x48, 0xbf }; - void* ptr = nullptr; - } fn; +namespace thunks { + constexpr uint64_t Terminator = 0xCCCCCCCCCCCCCCCCu; + constexpr uint64_t Placeholder = 0x0606060606060606u; + + extern "C" void EntryPointReplacement(); + extern "C" void RewrittenEntryPoint_Standalone(); - const uint8_t op_call_rdi[2]{ 0xff, 0xd7 }; - } CallTrampoline; -}; + void* resolve_thunk_address(void (*pfn)()) { + const auto ptr = reinterpret_cast(pfn); + if (*ptr == 0xe9) + return ptr + 5 + *reinterpret_cast(ptr + 1); + return ptr; + } -struct TrampolineTemplate { - const struct { - const uint8_t op_sub_rsp_imm[3]{ 0x48, 0x81, 0xec }; - const uint32_t length = 0x80; - } stack_alloc; + size_t get_thunk_length(void (*pfn)()) { + size_t length = 0; + for (auto ptr = reinterpret_cast(resolve_thunk_address(pfn)); *reinterpret_cast(ptr) != Terminator; ptr++) + length++; + return length; + } - struct DUMMYSTRUCTNAME { - struct { - const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 }; - void* val = nullptr; - } lpLibFileName; + template + void* fill_placeholders(void* pfn, const T& value) { + auto ptr = static_cast(pfn); - struct { - const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf }; - decltype(&LoadLibraryW) ptr = nullptr; - } fn; + while (*reinterpret_cast(ptr) != Placeholder) + ptr++; - const uint8_t op_call_rdi[2]{ 0xff, 0xd7 }; - } CallLoadLibrary_nethost; + *reinterpret_cast(ptr) = 0; + *reinterpret_cast(ptr) = value; + return ptr + sizeof(value); + } - struct DUMMYSTRUCTNAME { - struct { - const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 }; - void* val = nullptr; - } lpLibFileName; + template + void* fill_placeholders(void* ptr, const T& value, TArgs&&...more_values) { + return fill_placeholders(fill_placeholders(ptr, value), std::forward(more_values)...); + } - struct { - const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf }; - decltype(&LoadLibraryW) ptr = nullptr; - } fn; + std::vector create_entrypointreplacement() { + std::vector buf(get_thunk_length(&EntryPointReplacement)); + memcpy(buf.data(), resolve_thunk_address(&EntryPointReplacement), buf.size()); + return buf; + } - const uint8_t op_call_rdi[2]{ 0xff, 0xd7 }; - } CallLoadLibrary_DalamudBoot; + std::vector create_standalone_rewrittenentrypoint(const std::filesystem::path& dalamud_path) { + const auto nethost_path = std::filesystem::path(dalamud_path).replace_filename(L"nethost.dll"); - struct { - const uint8_t hModule_op_mov_rcx_rax[3]{ 0x48, 0x89, 0xc1 }; + // These are null terminated, since pointers are returned from .c_str() + const auto dalamud_path_wview = std::wstring_view(dalamud_path.c_str()); + const auto nethost_path_wview = std::wstring_view(nethost_path.c_str()); - struct { - const uint8_t op_mov_rdx_imm[2]{ 0x48, 0xba }; - void* val = nullptr; - } lpProcName; + // +2 is for null terminator + const auto dalamud_path_view = std::span(reinterpret_cast(dalamud_path_wview.data()), dalamud_path_wview.size() * 2 + 2); + const auto nethost_path_view = std::span(reinterpret_cast(nethost_path_wview.data()), nethost_path_wview.size() * 2 + 2); - struct { - const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf }; - decltype(&GetProcAddress) ptr = nullptr; - } fn; + std::vector buffer; + const auto thunk_template_length = thunks::get_thunk_length(&thunks::RewrittenEntryPoint_Standalone); + buffer.reserve(thunk_template_length + dalamud_path_view.size() + nethost_path_view.size()); + buffer.resize(thunk_template_length); + memcpy(buffer.data(), resolve_thunk_address(&thunks::RewrittenEntryPoint_Standalone), thunk_template_length); - const uint8_t op_call_rdi[2]{ 0xff, 0xd7 }; - } CallGetProcAddress; + // &::GetProcAddress will return Dalamud.dll's import table entry. + // GetProcAddress(..., "GetProcAddress") returns the address inside kernel32.dll. + const auto kernel32 = GetModuleHandleA("kernel32.dll"); - struct { - const uint8_t op_add_rsp_imm[3]{ 0x48, 0x81, 0xc4 }; - const uint32_t length = 0x80; - } stack_release; - - struct DUMMYSTRUCTNAME2 { - // rdi := returned value from GetProcAddress - const uint8_t op_mov_rdi_rax[3]{ 0x48, 0x89, 0xc7 }; - // rax := return address - const uint8_t op_pop_rax[1]{ 0x58 }; - - // rax := rax - sizeof thunk (last instruction must be call) - struct { - const uint8_t op_sub_rax_imm4[2]{ 0x48, 0x2d }; - const uint32_t displacement = static_cast(sizeof EntryPointThunkTemplate); - } op_sub_rax_to_entry_point; - - struct { - const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 }; - void* val = nullptr; - } param; - - const uint8_t op_push_rax[1]{ 0x50 }; - const uint8_t op_jmp_rdi[2]{ 0xff, 0xe7 }; - } CallInjectEntryPoint; - - const char buf_CallGetProcAddress_lpProcName[20] = "RewrittenEntryPoint"; - uint8_t buf_EntryPointBackup[sizeof EntryPointThunkTemplate]{}; - -#pragma pack(push, 8) - RewrittenEntryPointParameters parameters{}; -#pragma pack(pop) -}; -#pragma pack(pop) + thunks::fill_placeholders(buffer.data(), + /* pfnLoadLibraryW = */ GetProcAddress(kernel32, "LoadLibraryW"), + /* pfnGetProcAddress = */ GetProcAddress(kernel32, "GetProcAddress"), + /* pRewrittenEntryPointParameters = */ Placeholder, + /* nNethostOffset = */ 0, + /* nDalamudOffset = */ nethost_path_view.size_bytes() + ); + buffer.insert(buffer.end(), nethost_path_view.begin(), nethost_path_view.end()); + buffer.insert(buffer.end(), dalamud_path_view.begin(), dalamud_path_view.end()); + return buffer; + } +} void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) { SIZE_T read = 0; @@ -170,10 +146,17 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path exe.read(reinterpret_cast(&exe_section_headers[0]), sizeof IMAGE_SECTION_HEADER * exe_section_headers.size()); if (!exe) throw std::runtime_error("Game executable is corrupt (Truncated section header)."); + + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); for (MEMORY_BASIC_INFORMATION mbi{}; VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi); mbi.BaseAddress = static_cast(mbi.BaseAddress) + mbi.RegionSize) { + + // wine: apparently there exists a RegionSize of 0xFFF + mbi.RegionSize = (mbi.RegionSize + sysinfo.dwPageSize - 1) / sysinfo.dwPageSize * sysinfo.dwPageSize; + if (!(mbi.State & MEM_COMMIT) || mbi.Type != MEM_IMAGE) continue; @@ -241,18 +224,6 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path throw std::runtime_error("corresponding base address not found"); } -std::string from_utf16(const std::wstring& wstr, UINT codePage = CP_UTF8) { - std::string str(WideCharToMultiByte(codePage, 0, &wstr[0], static_cast(wstr.size()), nullptr, 0, nullptr, nullptr), 0); - WideCharToMultiByte(codePage, 0, &wstr[0], static_cast(wstr.size()), &str[0], static_cast(str.size()), nullptr, nullptr); - return str; -} - -std::wstring to_utf16(const std::string& str, UINT codePage = CP_UTF8, bool errorOnInvalidChars = false) { - std::wstring wstr(MultiByteToWideChar(codePage, 0, &str[0], static_cast(str.size()), nullptr, 0), 0); - MultiByteToWideChar(codePage, errorOnInvalidChars ? MB_ERR_INVALID_CHARS : 0, &str[0], static_cast(str.size()), &wstr[0], static_cast(wstr.size())); - return wstr; -} - /// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first. /// @param hProcess Process handle. /// @param pcwzPath Path to target process. @@ -263,9 +234,9 @@ std::wstring to_utf16(const std::string& str, UINT codePage = CP_UTF8, bool erro /// Instead, we have to enumerate through all the files mapped into target process' virtual address space and find the base address /// of memory region corresponding to the path given. /// -DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) { +extern "C" DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) { try { - const auto base_address = reinterpret_cast(get_mapped_image_base_address(hProcess, pcwzPath)); + const auto base_address = static_cast(get_mapped_image_base_address(hProcess, pcwzPath)); IMAGE_DOS_HEADER dos_header{}; union { @@ -279,60 +250,35 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* ? nt_header32.OptionalHeader.AddressOfEntryPoint : nt_header64.OptionalHeader.AddressOfEntryPoint); - auto path = get_path_from_local_module(g_hModule).wstring(); - path.resize(path.size() + 1); // ensure null termination - auto path_bytes = std::span(reinterpret_cast(&path[0]), std::span(path).size_bytes()); + auto standalone_rewrittenentrypoint = thunks::create_standalone_rewrittenentrypoint(get_path_from_local_module(g_hModule)); + auto entrypoint_replacement = thunks::create_entrypointreplacement(); - auto nethost_path = (get_path_from_local_module(g_hModule).parent_path() / L"nethost.dll").wstring(); - nethost_path.resize(nethost_path.size() + 1); // ensure null termination - auto nethost_path_bytes = std::span(reinterpret_cast(&nethost_path[0]), std::span(nethost_path).size_bytes()); - - auto load_info = from_utf16(pcwzLoadInfo); + auto load_info = unicode::convert(pcwzLoadInfo); load_info.resize(load_info.size() + 1); //ensure null termination - // Allocate full buffer in advance to keep reference to trampoline valid. - std::vector buffer(sizeof TrampolineTemplate + load_info.size() + nethost_path_bytes.size() + path_bytes.size()); - auto& trampoline = *reinterpret_cast(&buffer[0]); - const auto load_info_buffer = std::span(buffer).subspan(sizeof trampoline, load_info.size()); - const auto nethost_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size(), nethost_path_bytes.size()); - const auto dalamud_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size() + nethost_path_bytes.size(), path_bytes.size()); - - new(&trampoline)TrampolineTemplate(); // this line initializes given buffer instead of allocating memory - memcpy(&load_info_buffer[0], &load_info[0], load_info_buffer.size()); - memcpy(&nethost_path_buffer[0], &nethost_path_bytes[0], nethost_path_buffer.size()); - memcpy(&dalamud_path_buffer[0], &path_bytes[0], dalamud_path_buffer.size()); - - // Backup remote process' original entry point. - read_process_memory_or_throw(hProcess, entrypoint, trampoline.buf_EntryPointBackup); + std::vector buffer(sizeof(RewrittenEntryPointParameters) + entrypoint_replacement.size() + load_info.size() + standalone_rewrittenentrypoint.size()); // Allocate buffer in remote process, which will be used to fill addresses in the local buffer. - const auto remote_buffer = reinterpret_cast(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); - - // Fill the values to be used in RewrittenEntryPoint - trampoline.parameters = { - .pAllocation = remote_buffer, - .pEntrypoint = entrypoint, - .pEntrypointBytes = remote_buffer + offsetof(TrampolineTemplate, buf_EntryPointBackup), - .entrypointLength = sizeof trampoline.buf_EntryPointBackup, - .pLoadInfo = remote_buffer + (&load_info_buffer[0] - &buffer[0]), - }; + const auto remote_buffer = static_cast(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); + + auto& params = *reinterpret_cast(buffer.data()); + params.entrypointLength = entrypoint_replacement.size(); + params.pEntrypoint = entrypoint; - // Fill the addresses referred in machine code. - trampoline.CallLoadLibrary_nethost.lpLibFileName.val = remote_buffer + (&nethost_path_buffer[0] - &buffer[0]); - trampoline.CallLoadLibrary_nethost.fn.ptr = LoadLibraryW; - trampoline.CallLoadLibrary_DalamudBoot.lpLibFileName.val = remote_buffer + (&dalamud_path_buffer[0] - &buffer[0]); - trampoline.CallLoadLibrary_DalamudBoot.fn.ptr = LoadLibraryW; - trampoline.CallGetProcAddress.lpProcName.val = remote_buffer + offsetof(TrampolineTemplate, buf_CallGetProcAddress_lpProcName); - trampoline.CallGetProcAddress.fn.ptr = GetProcAddress; - trampoline.CallInjectEntryPoint.param.val = remote_buffer + offsetof(TrampolineTemplate, parameters); + // Backup original entry point. + read_process_memory_or_throw(hProcess, entrypoint, &buffer[sizeof params], entrypoint_replacement.size()); + + memcpy(&buffer[sizeof params + entrypoint_replacement.size()], load_info.data(), load_info.size()); + + thunks::fill_placeholders(standalone_rewrittenentrypoint.data(), remote_buffer); + memcpy(&buffer[sizeof params + entrypoint_replacement.size() + load_info.size()], standalone_rewrittenentrypoint.data(), standalone_rewrittenentrypoint.size()); // Write the local buffer into the buffer in remote process. write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), buffer.size()); - // Overwrite remote process' entry point with a thunk that immediately calls our trampoline function. - EntryPointThunkTemplate thunk{}; - thunk.CallTrampoline.fn.ptr = remote_buffer; - write_process_memory_or_throw(hProcess, entrypoint, thunk); + thunks::fill_placeholders(entrypoint_replacement.data(), remote_buffer + sizeof params + entrypoint_replacement.size() + load_info.size()); + // Overwrite remote process' entry point with a thunk that will load our DLLs and call our trampoline function. + write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size()); return 0; } catch (const std::exception& e) { @@ -341,44 +287,43 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* } } -/// @deprecated -DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* pcwzPath, const char* pcszLoadInfo) { - return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str()); +static void AbortRewrittenEntryPoint(DWORD err, const std::wstring& clue) { + wchar_t* pwszMsg = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + err, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + reinterpret_cast(&pwszMsg), + 0, + nullptr); + + if (MessageBoxW(nullptr, std::format( + L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\nError: 0x{:08X} {}\n\n{}", + err, pwszMsg ? pwszMsg : L"", clue).c_str(), + L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO) + ExitProcess(-1); } /// @brief Entry point function "called" instead of game's original main entry point. /// @param params Parameters set up from RewriteRemoteEntryPoint. -DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) { - params.hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr); - if (!params.hMainThreadContinue) - ExitProcess(-1); +extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) { + const auto pOriginalEntryPointBytes = reinterpret_cast(¶ms) + sizeof(params); + const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength; - // Do whatever the work in a separate thread to minimize the stack usage at this context, - // as this function really should have been a naked procedure but __declspec(naked) isn't supported in x64 version of msvc. - params.hMainThread = CreateThread(nullptr, 0, [](void* p) -> DWORD { - try { - std::string loadInfo; - auto& params = *reinterpret_cast(p); - { - // Restore original entry point. - // Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect. - write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, params.pEntrypointBytes, params.entrypointLength); + // Restore original entry point. + // Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect. + if (SIZE_T written; !WriteProcessMemory(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength, &written)) + return AbortRewrittenEntryPoint(GetLastError(), L"WriteProcessMemory(entrypoint restoration)"); - // Make a copy of load info, as the whole params will be freed after this code block. - loadInfo = params.pLoadInfo; - } + const auto hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr); + if (!hMainThreadContinue) + return AbortRewrittenEntryPoint(GetLastError(), L"CreateEventW"); - InitializeImpl(&loadInfo[0], params.hMainThreadContinue); - return 0; - } catch (const std::exception& e) { - MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR); - ExitProcess(-1); - } - }, ¶ms, 0, nullptr); - if (!params.hMainThread) - ExitProcess(-1); + if (const auto result = InitializeImpl(pLoadInfo, hMainThreadContinue)) + return AbortRewrittenEntryPoint(result, L"InitializeImpl"); - CloseHandle(params.hMainThread); - WaitForSingleObject(params.hMainThreadContinue, INFINITE); - VirtualFree(params.pAllocation, 0, MEM_RELEASE); + WaitForSingleObject(hMainThreadContinue, INFINITE); + VirtualFree(¶ms, 0, MEM_RELEASE); } diff --git a/Dalamud.Boot/rewrite_entrypoint_thunks.asm b/Dalamud.Boot/rewrite_entrypoint_thunks.asm new file mode 100644 index 000000000..af7be8287 --- /dev/null +++ b/Dalamud.Boot/rewrite_entrypoint_thunks.asm @@ -0,0 +1,82 @@ +PUBLIC EntryPointReplacement +PUBLIC RewrittenEntryPoint_Standalone +PUBLIC RewrittenEntryPoint + +; 06 and 07 are invalid opcodes +; CC is int3 = bp +; using 0CCCCCCCCCCCCCCCCh as function terminator +; using 00606060606060606h as placeholders + +TERMINATOR = 0CCCCCCCCCCCCCCCCh +PLACEHOLDER = 00606060606060606h + +.code + +EntryPointReplacement PROC + start: + ; rsp % 0x10 = 0x08 + lea rax, [start] + push rax + + ; rsp % 0x10 = 0x00 + mov rax, PLACEHOLDER + + ; this calls RewrittenEntryPoint_Standalone + jmp rax + + dq TERMINATOR +EntryPointReplacement ENDP + +RewrittenEntryPoint_Standalone PROC + start: + ; stack is aligned to 0x10; see above + sub rsp, 20h + lea rcx, [embeddedData] + add rcx, qword ptr [nNethostOffset] + call qword ptr [pfnLoadLibraryW] + + lea rcx, [embeddedData] + add rcx, qword ptr [nDalamudOffset] + call qword ptr [pfnLoadLibraryW] + + mov rcx, rax + lea rdx, [pcszEntryPointName] + call qword ptr [pfnGetProcAddress] + + mov rcx, qword ptr [pRewrittenEntryPointParameters] + ; this calls RewrittenEntryPoint + jmp rax + + pfnLoadLibraryW: + dq PLACEHOLDER + + pfnGetProcAddress: + dq PLACEHOLDER + + pRewrittenEntryPointParameters: + dq PLACEHOLDER + + nNethostOffset: + dq PLACEHOLDER + + nDalamudOffset: + dq PLACEHOLDER + + pcszEntryPointName: + db "RewrittenEntryPoint", 0 + + embeddedData: + + dq TERMINATOR +RewrittenEntryPoint_Standalone ENDP + +EXTERN RewrittenEntryPoint_AdjustedStack :PROC + +RewrittenEntryPoint PROC + ; stack is aligned to 0x10; see above + call RewrittenEntryPoint_AdjustedStack + add rsp, 20h + ret +RewrittenEntryPoint ENDP + +END