~zanneth/BishiLauncher

90728f770e1c8ff491b2aacd875833f9aab39ca9 — Charles Magahern 3 years ago
Adapt code from TAS bot
A  => .gitignore +1 -0
@@ 1,1 @@
CMakeLists.txt.user*

A  => CMakeLists.txt +39 -0
@@ 1,39 @@
cmake_minimum_required(VERSION 2.8)

# target operating system
set(CMAKE_SYSTEM_NAME Windows)

# which compilers to use for C and C++
set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)

# C++ flags
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++ -static-libgcc -m32")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")

# include paths
include_directories(
    ${CMAKE_INCLUDE_DIRECTORIES_BEFORE}
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/external/include
    /usr/include/i686-w64-mingw32
    /usr/i686-w64-mingw32/include
)

project(BishiLauncher)

add_executable(bishilauncher
    src/main.cpp
    src/gameprocess.cpp
    src/util.cpp
)

add_library(bishiinject SHARED
    src/inject.cpp
    src/processmoduletable.cpp
    src/util.cpp
    src/gamecontroller.cpp
)

A  => include/gamecontroller.h +28 -0
@@ 1,28 @@
#pragma once

#include <windows.h>

#include "processmoduletable.h"
#include "util.h"

static DWORD __fastcall __patched_game_tick(void *p);

class GameController
{
public:
    static GameController& instance();
    ~GameController();

    bool initialize();

private: // functions
    explicit GameController();

    static void _patch_exe(void *dst, void *src, size_t len);
    static void _patch_exe_ptr(void **ptr, void *new_value);
    static bool _patch_function(const ProcessModuleTable &modtable,
                                const char *module_name,
                                const char *sym_name,
                                void *new_funcptr,
                                void **out_orig_funcptr);
};

A  => include/gameprocess.h +35 -0
@@ 1,35 @@
#pragma once

#include <functional>
#include <string>
#include <windows.h>

class GameProcess
{
public:
    GameProcess(const std::string &game_exe_path);
    ~GameProcess();

    bool launch();
    bool is_running() const;
    bool wait() const;
    void terminate();

    void set_did_exit_callback(const std::function<void()> &f);

private:
    bool _inject();
    void _close();

    static void CALLBACK _wait_callback(PVOID param, BOOLEAN timer_or_wait_fired);
    static std::wstring _exe_dir();

private:
    std::string           _game_exe_path;
    HANDLE                _game_proc_handle;
    HANDLE                _game_proc_wait_handle;
    HANDLE                _game_thread_handle;
    HANDLE                _inject_thread_handle;

    std::function<void()> _did_exit_callback;
};

A  => include/processmoduletable.h +43 -0
@@ 1,43 @@
#pragma once

#include <iterator>
#include <windows.h>
#include <winternl.h>

class ProcessModuleTable
{
public:
    struct Iterator
    {
        // iterator traits
        using difference_type   = std::ptrdiff_t;
        using value_type        = LDR_DATA_TABLE_ENTRY;
        using pointer           = PLDR_DATA_TABLE_ENTRY;
        using reference         = LDR_DATA_TABLE_ENTRY&;
        using iterator_category = std::forward_iterator_tag;

        explicit Iterator(const LIST_ENTRY *node);

        Iterator& operator++();
        Iterator  operator++(int);
        bool      operator==(Iterator other) const;
        bool      operator!=(Iterator other) const;
        reference operator*();

    private:
        const LIST_ENTRY *_cur;
    };

public:
    ProcessModuleTable();

    Iterator begin() const;
    Iterator end() const;
    size_t count() const;

    LDR_DATA_TABLE_ENTRY* entry_named(const std::string &name) const;
    LDR_DATA_TABLE_ENTRY* entry_named(const std::wstring &name) const;

private:
    const PEB *_env_block;
};

A  => include/util.h +46 -0
@@ 1,46 @@
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
#include <winternl.h>

struct FilePath
{
    std::wstring drive;
    std::wstring directory;
    std::wstring filename;
    std::wstring extension;

    FilePath(const std::wstring &path = L"");
    std::wstring path() const;
};

//! Return the last win32 error description.
std::string last_win_error_string(void);

//! Return the file path to the specified DLL according to the loader table.
FilePath dll_filepath(const LDR_DATA_TABLE_ENTRY &ldr_entry);

//! Get all memory regions for the current process.
std::vector<MEMORY_BASIC_INFORMATION> mem_regions(void);

//! Get the memory region containing the provided address.
MEMORY_BASIC_INFORMATION mem_region(const void *ptr);

//! Returns true if the provided memory region contains the provided address.
bool mem_region_contains(const MEMORY_BASIC_INFORMATION &region, const void *ptr);

//! Generate a temporary file path.
std::string temp_file_path(const std::string &prefix = "TEMP");

//! Convert a wide UTF-16 string into a UTF-8 string.
std::string to_string(const std::wstring &wstr);

//! Convert a UTF-8 string into a wide UTF-16 string.
std::wstring to_wstring(const std::string &str);

//! Show a message box with an error icon and title using the provided text.
int show_err(const std::string &text);
int show_err(const std::wstring &text);

A  => src/gamecontroller.cpp +117 -0
@@ 1,117 @@
#include "gamecontroller.h"
#include "processmoduletable.h"

#include <winternl.h>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>

#define DEBUG_LOG(...) printf("[BishiInject] " __VA_ARGS__)
#define ERROR_LOG(...) fprintf(stderr, "[BishiInject] " __VA_ARGS__)

namespace
{
    constexpr uint32_t GAME_LOOP_TICK_VTABLE_OFFSET = 0x000887f4;
    constexpr uint32_t ACIO_HBHI_GET_CSB_OFFSET = 0x00001490;
    constexpr uint32_t AVS_STDBOOT_HEAP_BOOT_CALL_OFFSET = 0x000291BA;
    constexpr uint32_t AVS_PBOOT_HEAP_BOOT_CALL_OFFSET = 0x0003D900;
    constexpr uint32_t AFP_LOGGING_FLAGS_OFFSET = 0x00081148;

    constexpr const char *ACIO_MODULE_NAME = "libacio.dll";
    constexpr const char *ACIO_HBHI_GET_CSB_SYM_NAME = "ac_io_hbhi_get_control_status_buffer";

    constexpr const char *AFP_MODULE_NAME = "libafp-win32.dll";
    constexpr const char *AFP_BOOT_SYM_NAME = "afp_boot";

    constexpr const char *AVS_MODULE_NAME = "libavs-win32.dll";
    constexpr const char *AVS_HEAP_BOOT_SYM_NAME = "heap_boot";
}

GameController& GameController::instance()
{
    static GameController __controller;
    return __controller;
}

GameController::GameController()
{}

GameController::~GameController()
{}

#pragma mark - API

bool GameController::initialize()
{
    DEBUG_LOG("Attempting to patch game code...\n");
    bool success = false;

    show_err("initialize called");

    ProcessModuleTable table;
    return success;
}

#pragma mark - Internal

// static
void GameController::_patch_exe(void *dst, void *src, size_t len)
{
    DWORD cur_protect = 0x0;

    VirtualProtect(dst, len, PAGE_EXECUTE_READWRITE, &cur_protect);
    std::memcpy(dst, src, len);
    VirtualProtect(dst, len, cur_protect, &cur_protect);
}

// static
void GameController::_patch_exe_ptr(void **ptr, void *new_value)
{
    _patch_exe(ptr, &new_value, sizeof(void *));
}

// static
bool GameController::_patch_function(const ProcessModuleTable &modtable,
                                     const char *module_name,
                                     const char *sym_name,
                                     void *new_funcptr,
                                     void **out_orig_funcptr)
{
    bool patched = false;

    for (const LDR_DATA_TABLE_ENTRY &module : modtable) {
        const uint8_t *base = (uint8_t *)module.DllBase;
        const IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)base;
        const IMAGE_NT_HEADERS *hdrs = (IMAGE_NT_HEADERS *)(base + dosh->e_lfanew);
        const IMAGE_DATA_DIRECTORY *data_dir = &hdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

        IMAGE_IMPORT_DESCRIPTOR *iid;
        for (   iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + data_dir->VirtualAddress);
                iid->Name != 0 && iid->Name != 0xffff;
                ++iid) {
            const char *iid_name = (char *)(base + iid->Name);
            if (strcmp(iid_name, module_name) != 0) { continue; }

            const uintptr_t *import_rvas = (uintptr_t *)(base + iid->OriginalFirstThunk);
            for (size_t i = 0; import_rvas[i] != 0; ++i) {
                const IMAGE_IMPORT_BY_NAME *import = (IMAGE_IMPORT_BY_NAME *)(base + import_rvas[i]);
                const char *import_name = (const char *)import->Name;
                if (strcmp(import_name, sym_name) != 0) { continue; }

                void **pointers = (void **)(base + iid->FirstThunk);
                void **ppointer = &pointers[i];
                void *new_value = new_funcptr;

                if (out_orig_funcptr) {
                    *out_orig_funcptr = *ppointer;
                }

                _patch_exe_ptr(ppointer, new_value);
                patched = true;
            }
        }
    }

    return patched;
}

A  => src/gameprocess.cpp +242 -0
@@ 1,242 @@
#include "gameprocess.h"
#include "util.h"

#include <iostream>
#include <sstream>

GameProcess::GameProcess(const std::string &game_exe_path)
    : _game_exe_path(game_exe_path),
      _game_proc_handle(INVALID_HANDLE_VALUE),
      _game_proc_wait_handle(INVALID_HANDLE_VALUE),
      _game_thread_handle(INVALID_HANDLE_VALUE),
      _inject_thread_handle(INVALID_HANDLE_VALUE)
{}

GameProcess::~GameProcess()
{
    _close();
}

#pragma mark - API

bool GameProcess::launch()
{
    STARTUPINFO         si = {0};
    PROCESS_INFORMATION pi = {0};
    BOOL                success = false;

    do {
        // ensure we're not launched already
        if (_game_proc_handle != INVALID_HANDLE_VALUE) {
            success = true;
            break;
        }

        // set current directory to the game's path.
        const FilePath game_exe_path(to_wstring(_game_exe_path));
        success = SetCurrentDirectoryW(game_exe_path.directory.c_str());
        if (!success) {
            fprintf(stderr, "Failed to change current directory.\n");
            break;
        }

        // create the process in a suspended state
        success = CreateProcess(
            NULL,                           // lpApplicationName
            (LPSTR)_game_exe_path.c_str(),  // lpCommandLine
            NULL,                           // lpProcessAttributes
            NULL,                           // lpThreadAttributes
            FALSE,                          // bInheritHandles
            CREATE_SUSPENDED,               // dwCreationFlags
            NULL,                           // lpEnvironment
            NULL,                           // lpCurrentDirectory
            &si,                            // lpStartupInfo
            &pi                             // lpProcessInformation
        );
        if (!success) {
            fprintf(stderr, "Failed to create game process.\n");
            break;
        }

        _game_proc_handle = pi.hProcess;
        _game_thread_handle = pi.hThread;

        // inject our payload
        success = _inject();
        if (!success) {
            fprintf(stderr, "Failed to inject into game program.\n");
            break;
        }

        // register wait for process handle
        success = RegisterWaitForSingleObject(
            &_game_proc_wait_handle,        // phNewWaitObject
            _game_proc_handle,              // hObject
            &GameProcess::_wait_callback,  // callback
            (PVOID)this,                    // context
            INFINITE,                       // dwMilliseconds
            WT_EXECUTEONLYONCE              // dwFlags
        );
        if (!success) {
            fprintf(stderr, "Failed to register wait object.\n");
            break;
        }

        // resume the game thread
        success = (ResumeThread(_game_thread_handle) != 0);
        if (!success) {
            fprintf(stderr, "Failed to resume game thread.\n");
            break;
        }
    } while (0);

    return success;
}

bool GameProcess::is_running() const
{
    bool running = false;

    if (_game_proc_handle != INVALID_HANDLE_VALUE) {
        running = (GetProcessId(_game_proc_handle) > 0);
    }

    return running;
}

bool GameProcess::wait() const
{
    // register wait for process handle
    DWORD result = WaitForSingleObject(_game_proc_handle, INFINITE);
    return (result == WAIT_OBJECT_0);
}

void GameProcess::terminate()
{
    if (_game_proc_handle != INVALID_HANDLE_VALUE) {
        TerminateProcess(_game_proc_handle, 0);
    }
}

void GameProcess::set_did_exit_callback(const std::function<void()> &f)
{
    _did_exit_callback = f;
}

#pragma mark - Internal

bool GameProcess::_inject()
{
    bool success = false;

    do {
        // get handle to kernel32 module
        HMODULE kern_mod = GetModuleHandle("kernel32.dll");
        if (!kern_mod) {
            fprintf(stderr, "Cannot get kernel32.dll module.\n");
            break;
        }

        // find address of LoadLibraryW
        LPVOID load_lib_addr = (LPVOID)GetProcAddress(kern_mod, "LoadLibraryW");
        if (!load_lib_addr) {
            fprintf(stderr, "Cannot get proc address of LoadLibraryA.\n");
            break;
        }

        const std::wstring exe_path = _exe_dir();
        const std::wstring inject_dll_path = exe_path + L"\\libbishiinject.dll";
        const size_t path_size = (inject_dll_path.length() + 1) * sizeof(wchar_t);

        LPVOID arg_mem = (LPVOID)VirtualAllocEx(
            _game_proc_handle,          // hProcess
            NULL,                       // lpAddress (let kernel decide)
            path_size,                  // dwSize
            MEM_RESERVE | MEM_COMMIT,   // flAllocationType
            PAGE_READWRITE              // flProtect
        );
        if (!arg_mem) {
            fprintf(stderr, "Failed to allocate memory in process.\n");
            break;
        }

        BOOL write_success = WriteProcessMemory(
            _game_proc_handle,          // hProcess
            arg_mem,                    // lpBaseAddress
            inject_dll_path.data(),     // lpBuffer
            path_size,                  // nSize
            NULL                        // lpNumberOfBytesWritten
        );
        if (!write_success) {
            fprintf(stderr, "Failed to write process memory.\n");
            break;
        }

        HANDLE rem_thread = CreateRemoteThread(
            _game_proc_handle,                      // hProcess
            NULL,                                   // lpThreadAttributes
            0,                                      // dwStackSize (let kernel decide)
            (LPTHREAD_START_ROUTINE)load_lib_addr,  // lpStartAddress
            arg_mem,                                // lpParameter
            0,                                      // dwCreationFlags
            NULL                                    // lpThreadId
        );
        if (!rem_thread) {
            fprintf(stderr, "Failed to create remote thread.\n");
            break;
        }

        DWORD wait_result = WaitForSingleObject(rem_thread, INFINITE);
        if (wait_result != 0) {
            fprintf(stderr, "Timed out waiting for remote inject thread.\n");
            break;
        }

        success = true;
    } while (0);

    return success;
}

void GameProcess::_close()
{
    if (_game_proc_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_proc_handle);
        _game_proc_handle = INVALID_HANDLE_VALUE;
    }

    if (_game_proc_wait_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_proc_wait_handle);
        _game_proc_wait_handle = INVALID_HANDLE_VALUE;
    }

    if (_game_thread_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_thread_handle);
        _game_thread_handle = INVALID_HANDLE_VALUE;
    }

    if (_inject_thread_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_inject_thread_handle);
        _inject_thread_handle = INVALID_HANDLE_VALUE;
    }
}

void CALLBACK GameProcess::_wait_callback(PVOID param, BOOLEAN timer_or_wait_fired)
{
    GameProcess *proc = static_cast<GameProcess *>(param);
    proc->_close();

    if (proc->_did_exit_callback) {
        proc->_did_exit_callback();
    }
}

std::wstring GameProcess::_exe_dir()
{
    wchar_t exe_path[MAX_PATH];
    HMODULE exe_module = GetModuleHandleW(nullptr);
    GetModuleFileNameW(exe_module, exe_path, MAX_PATH);

    FilePath exe_splitpath(exe_path);
    return exe_splitpath.drive + exe_splitpath.directory;
}

A  => src/inject.cpp +12 -0
@@ 1,12 @@
#include <windows.h>
#include "gamecontroller.h"

extern "C"
BOOL APIENTRY DllMain(HMODULE dll, DWORD reason, LPVOID reserved)
{
    if (reason == DLL_PROCESS_ATTACH) {
        GameController::instance().initialize();
    }

    return TRUE;
}

A  => src/main.cpp +43 -0
@@ 1,43 @@
#include <exception>
#include <iostream>
#include <shlwapi.h>
#include <system_error>
#include <windows.h>

#include "gameprocess.h"
#include "util.h"

namespace
{
    constexpr const wchar_t *GAME_EXE_FILENAME = L"bishi09";
}

int main(int argc, const char **argv)
{
    std::wstring exe_path_str(MAX_PATH, L'\0');
    DWORD result = GetModuleFileNameW(nullptr, exe_path_str.data(), exe_path_str.size());

    if (result == 0) {
        show_err("Failed to get path for current executable.");
        return 1;
    }

    FilePath game_exe_path(exe_path_str);
    game_exe_path.filename = GAME_EXE_FILENAME;
    const std::string game_exe_path_str = to_string(game_exe_path.path());

    if (GetFileAttributesA(game_exe_path_str.c_str()) == INVALID_FILE_ATTRIBUTES) {
        const std::wstring err_msg = std::wstring(GAME_EXE_FILENAME) + L" could not be found.";
        show_err(err_msg);
        return 1;
    }

    GameProcess game_proc(to_string(game_exe_path.path()));
    const bool launch_success = game_proc.launch();

    if (launch_success) {
        game_proc.wait();
    }

    return (launch_success ? 0 : 1);
}

A  => src/processmoduletable.cpp +116 -0
@@ 1,116 @@
#include "processmoduletable.h"
#include "util.h"

#include <codecvt>
#include <locale>

#define containerof(PTR, OUTER_TYPE, MEMBER) \
    ((void *)(((uint8_t *)PTR) - offsetof(OUTER_TYPE, MEMBER)))

static inline const PEB* _peb_get(void)
{
#ifdef __amd64
    return (const PEB *)__readgsqword(0x60);
#else
    return (const PEB *)__readfsdword(0x30);
#endif
}

#pragma mark -

ProcessModuleTable::Iterator::Iterator(const LIST_ENTRY *node)
    : _cur(node)
{}

ProcessModuleTable::Iterator& ProcessModuleTable::Iterator::operator++()
{
    _cur = _cur->Flink;
    return *this;
}

ProcessModuleTable::Iterator ProcessModuleTable::Iterator::operator++(int)
{
    Iterator cp = *this;
    ++(*this);
    return cp;
}

bool ProcessModuleTable::Iterator::operator==(Iterator other) const
{
    return _cur == other._cur;
}

bool ProcessModuleTable::Iterator::operator!=(Iterator other) const
{
    return !operator==(other);
}

ProcessModuleTable::Iterator::reference ProcessModuleTable::Iterator::operator*()
{
    return *(LDR_DATA_TABLE_ENTRY *)containerof(
        _cur,
        LDR_DATA_TABLE_ENTRY,
        InMemoryOrderLinks
    );
}

#pragma mark - Internal

ProcessModuleTable::ProcessModuleTable() :
    _env_block(_peb_get())
{}

ProcessModuleTable::Iterator ProcessModuleTable::begin() const
{
    return Iterator(_env_block->Ldr->InMemoryOrderModuleList.Flink);
}

ProcessModuleTable::Iterator ProcessModuleTable::end() const
{
    const LIST_ENTRY *first;
    const LIST_ENTRY *node;
    first = node = _env_block->Ldr->InMemoryOrderModuleList.Flink;

    do {
        node = node->Flink;
    } while (node && node->Flink != first);

    return Iterator(node);
}

size_t ProcessModuleTable::count() const
{
    const LIST_ENTRY *first;
    const LIST_ENTRY *node;
    size_t count = 0;

    first = node = _env_block->Ldr->InMemoryOrderModuleList.Flink;

    do {
        node = node->Flink;
        ++count;
    } while (node && node != first);

    return count;
}

LDR_DATA_TABLE_ENTRY* ProcessModuleTable::entry_named(const std::string &name) const
{
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    return entry_named(converter.from_bytes(name));
}

LDR_DATA_TABLE_ENTRY* ProcessModuleTable::entry_named(const std::wstring &name) const
{
    LDR_DATA_TABLE_ENTRY *entry = nullptr;

    for (Iterator it = begin(); it != end(); ++it) {
        const FilePath dll_path = dll_filepath(*it);
        if (dll_path.filename == name || (dll_path.filename + dll_path.extension) == name) {
            entry = &(*it);
            break;
        }
    }

    return entry;
}

A  => src/util.cpp +178 -0
@@ 1,178 @@
#include "util.h"

#include <codecvt>
#include <iostream>
#include <locale>
#include <string>
#include <system_error>

#pragma mark - FilePath

FilePath::FilePath(const std::wstring &path)
    : drive(MAX_PATH, L'\0'),
      directory(MAX_PATH, L'\0'),
      filename(MAX_PATH, L'\0'),
      extension(MAX_PATH, L'\0')
{
    _wsplitpath_s(
        path.c_str(),
        drive.data(),
        drive.length() * sizeof(wchar_t),
        directory.data(),
        directory.length() * sizeof(wchar_t),
        filename.data(),
        filename.length() * sizeof(wchar_t),
        extension.data(),
        extension.length() * sizeof(wchar_t)
    );

    drive.resize(wcslen(drive.c_str()));
    directory.resize(wcslen(directory.c_str()));
    filename.resize(wcslen(filename.c_str()));
    extension.resize(wcslen(extension.c_str()));
}

std::wstring FilePath::path() const
{
    std::wstring path(MAX_PATH, L'\0');
    _wmakepath_s(
        path.data(),
        path.length() * sizeof(wchar_t),
        drive.c_str(),
        directory.c_str(),
        filename.c_str(),
        extension.c_str()
    );

    path.resize(wcslen(path.c_str()));
    return path;
}

#pragma mark - Functions

std::string last_win_error_string(void)
{
    DWORD err_code = GetLastError();
    if (err_code != 0) {
        LPSTR msg_buf = nullptr;
        const size_t size = FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags
            nullptr,                                    // lpSource
            err_code,                                   // dwMessageId
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // dwLanguageId
            (LPTSTR)&msg_buf,                           // lpBuffer
            0,                                          // nSize
            nullptr                                     // Arguments
        );

        std::string msg(msg_buf);
        LocalFree(msg_buf);

        return msg;
    } else {
        return "";
    }
}

FilePath dll_filepath(const LDR_DATA_TABLE_ENTRY &ldr_entry)
{
    wchar_t dll_name[MAX_PATH];
    wcsncpy(dll_name, ldr_entry.FullDllName.Buffer, ldr_entry.FullDllName.Length);
    dll_name[ldr_entry.FullDllName.Length + 1] = '\0';

    return FilePath(dll_name);
}

std::vector<MEMORY_BASIC_INFORMATION> mem_regions(void)
{
    std::vector<MEMORY_BASIC_INFORMATION> result;
    HANDLE proc = GetCurrentProcess();

    SYSTEM_INFO sys_info;
    GetSystemInfo(&sys_info);

    MEMORY_BASIC_INFORMATION mbi;
    const void *ptr = (const void *)sys_info.lpMinimumApplicationAddress;
    const void *end = (const void *)sys_info.lpMaximumApplicationAddress;
    while (ptr < end) {
        size_t query_result = VirtualQuery(ptr, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
        if (query_result != sizeof(MEMORY_BASIC_INFORMATION)) {
            break;
        }

        result.push_back(mbi);
        ptr = (const void *)((uint8_t *)mbi.BaseAddress + mbi.RegionSize);
    }

    return result;
}

MEMORY_BASIC_INFORMATION mem_region(const void *ptr)
{
    MEMORY_BASIC_INFORMATION mbi;
    size_t result = VirtualQuery(ptr, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    if (result != sizeof(MEMORY_BASIC_INFORMATION)) {
        throw std::runtime_error("Unknown memory region " + std::to_string(DWORD(ptr)));
    }

    return mbi;
}

bool mem_region_contains(const MEMORY_BASIC_INFORMATION &region, const void *ptr)
{
    return (
        ptr >= region.BaseAddress &&
        ptr < (uint8_t *)region.BaseAddress + region.RegionSize
    );
}

std::string temp_file_path(const std::string &prefix)
{
    UINT ret = 0;
    char path_buf[MAX_PATH];
    char tmp_filename[MAX_PATH];

    ret = GetTempPath(MAX_PATH, path_buf);
    if (!ret) {
        throw std::runtime_error("Failed to get temporary directory.");
    }

    ret = GetTempFileName(path_buf, prefix.c_str(), 0, tmp_filename);
    if (!ret) {
        throw std::runtime_error("Failed to get temporary filename.");
    }

    return std::string(tmp_filename);
}

std::string to_string(const std::wstring &wstr)
{
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    return converter.to_bytes(wstr);
}

std::wstring to_wstring(const std::string &str)
{
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    return converter.from_bytes(str);
}

int show_err(const std::string &text)
{
    return MessageBoxA(
        nullptr,                // hWnd
        text.c_str(),           // lpText
        nullptr,                // lpCaption
        (MB_OK | MB_ICONERROR)  // uType
    );
}

int show_err(const std::wstring &text)
{
    return MessageBoxW(
        nullptr,                // hWnd
        text.c_str(),           // lpText
        nullptr,                // lpCaption
        (MB_OK | MB_ICONERROR)  // uType
    );
}