Sign in Sign up
kretrod/lodestone-cpp Public
Branches
main
199 lines (180 loc) · 7.5 KB Raw
/* build.exe — one-click builder for the Lodestone C++ client.
 *
 * Double-click it (or run it from a terminal) in the project folder. It:
 *   1. downloads a portable mingw-w64 toolchain (w64devkit) the first time,
 *      into a `w64devkit\` subfolder — nothing is installed system-wide;
 *   2. compiles the client DLL and the loader EXE with that toolchain;
 *   3. checks the DLL imports only the always-present system libraries.
 *
 * No JDK is needed: jni.h is vendored under third_party/jni. Re-running skips
 * the download once w64devkit is present, so a rebuild is just a recompile.
 *
 * The compiler and flags mirror the Makefile; this file exists so someone with
 * a bare Windows box can build without first learning make or installing a
 * toolchain. To build this bootstrapper itself (done by the maintainer, on any
 * mingw): x86_64-w64-mingw32-gcc -O2 build_bootstrap.c -o build.exe
 */

#include <windows.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Pinned so the build is reproducible; old release assets stay downloadable.
 * w64devkit is msvcrt-based, which is why the DLL imports only msvcrt.dll. */
#define DEVKIT_VER "1.23.0"
#define DEVKIT_ZIP "w64devkit-" DEVKIT_VER ".zip"
#define DEVKIT_URL \
    "https://github.com/skeeto/w64devkit/releases/download/v" DEVKIT_VER "/" DEVKIT_ZIP

#define GXX     "w64devkit\\bin\\g++.exe"
#define GCC     "w64devkit\\bin\\gcc.exe"
#define OBJDUMP "w64devkit\\bin\\objdump.exe"

static const char *INCLUDES =
    "-Isrc -Isrc/jni_md -Ithird_party/jni "
    "-Ithird_party/imgui -Ithird_party/imgui/backends -Ithird_party/minhook/include";
static const char *DEFINES = "-DWIN32_LEAN_AND_MEAN -DNOMINMAX -DUNICODE -D_UNICODE";
static const char *CXXFLAGS = "-std=c++20 -O2 -Wall -Wextra -Wno-unused-parameter -fno-rtti";
static const char *CFLAGS = "-O2 -Wall";

static const char *CXX_SRC[] = {
    "src/main.cpp",
    "src/overlay.cpp",
    "third_party/imgui/imgui.cpp",
    "third_party/imgui/imgui_draw.cpp",
    "third_party/imgui/imgui_tables.cpp",
    "third_party/imgui/imgui_widgets.cpp",
    "third_party/imgui/backends/imgui_impl_opengl3.cpp",
};
static const char *C_SRC[] = {
    "third_party/minhook/src/hook.c",
    "third_party/minhook/src/buffer.c",
    "third_party/minhook/src/trampoline.c",
    "third_party/minhook/src/hde/hde64.c",
};
#define N_CXX (int)(sizeof(CXX_SRC) / sizeof(*CXX_SRC))
#define N_C (int)(sizeof(C_SRC) / sizeof(*C_SRC))

static int exists(const char *path) {
    return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
}

/* Create every directory along a `build/...` path that does not yet exist. */
static void mkdirs(const char *path) {
    char t[1024];
    strncpy(t, path, sizeof t - 1);
    t[sizeof t - 1] = 0;
    for (char *p = t + 1; *p; ++p) {
        if (*p == '/' || *p == '\\') {
            char c = *p;
            *p = 0;
            CreateDirectoryA(t, NULL);
            *p = c;
        }
    }
    CreateDirectoryA(t, NULL);
}

/* Run a command through cmd, echoing it first. Returns its exit code. */
static int run(const char *fmt, ...) {
    char cmd[16384];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(cmd, sizeof cmd, fmt, ap);
    va_end(ap);
    printf("\n> %s\n", cmd);
    fflush(stdout);
    return system(cmd);
}

static int fail(const char *what) {
    printf("\n[!] %s failed.\n    Fix the problem above and run build.exe again.\n", what);
    fflush(stdout);
    system("pause");
    return 1;
}

/* The object file a source compiles to: build/<source>.o */
static void obj_of(const char *src, char *out, size_t n) {
    snprintf(out, n, "build/%s.o", src);
}

int main(void) {
    /* Work from the folder build.exe lives in, so a double-click (whatever the
     * shell's current directory is) still finds the sources. */
    char exe[1024];
    if (GetModuleFileNameA(NULL, exe, sizeof exe)) {
        char *slash = strrchr(exe, '\\');
        if (slash) {
            *slash = 0;
            SetCurrentDirectoryA(exe);
        }
    }

    printf("=== Lodestone C++ builder ===\n");
    if (!exists("src\\main.cpp")) {
        printf("\n[!] Run build.exe from the project folder (the one with src\\ and\n"
               "    third_party\\ in it). I do not see src\\main.cpp here.\n");
        system("pause");
        return 1;
    }

    /* 1. Toolchain. Downloaded once into .\w64devkit\. */
    if (!exists(GXX)) {
        printf("\nNo toolchain yet — fetching w64devkit " DEVKIT_VER " (about 73 MB, once).\n");
        if (!exists(DEVKIT_ZIP)) {
            if (run("powershell -NoProfile -ExecutionPolicy Bypass -Command "
                    "\"$ProgressPreference='SilentlyContinue'; "
                    "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; "
                    "Invoke-WebRequest -Uri '%s' -OutFile '%s'\"",
                    DEVKIT_URL, DEVKIT_ZIP))
                return fail("Download");
        }
        printf("\nUnpacking...\n");
        if (run("powershell -NoProfile -ExecutionPolicy Bypass -Command "
                "\"Expand-Archive -Force '%s' -DestinationPath '.'\"",
                DEVKIT_ZIP))
            return fail("Unpacking");
        if (!exists(GXX))
            return fail("Toolchain layout (w64devkit\\bin\\g++.exe not found after unpacking)");
        printf("\nToolchain ready.\n");
    } else {
        printf("\nToolchain already present — skipping download.\n");
    }

    /* 2. Compile. One object per source; the object tree mirrors the sources. */
    char obj[1024];
    char objs[12288] = "";  /* the DLL's object list, accumulated for the link */

    printf("\nCompiling the client...\n");
    for (int i = 0; i < N_CXX; ++i) {
        obj_of(CXX_SRC[i], obj, sizeof obj);
        mkdirs(obj);
        if (run("%s %s %s %s -c %s -o %s", GXX, CXXFLAGS, INCLUDES, DEFINES, CXX_SRC[i], obj))
            return fail("Compile");
        strcat(objs, obj);
        strcat(objs, " ");
    }
    for (int i = 0; i < N_C; ++i) {
        obj_of(C_SRC[i], obj, sizeof obj);
        mkdirs(obj);
        if (run("%s %s %s %s -c %s -o %s", GCC, CFLAGS, INCLUDES, DEFINES, C_SRC[i], obj))
            return fail("Compile");
        strcat(objs, obj);
        strcat(objs, " ");
    }

    printf("\nLinking lodestone_client.dll...\n");
    if (run("%s %s -o build/lodestone_client.dll "
            "-shared -static-libgcc -static-libstdc++ -Wl,--exclude-all-symbols "
            "-lopengl32 -lgdi32 -luser32 -lkernel32",
            GXX, objs))
        return fail("Linking the DLL");

    /* 3. The loader — a plain console EXE, no ImGui or MinHook. */
    printf("\nBuilding the loader...\n");
    obj_of("inject/main.cpp", obj, sizeof obj);
    mkdirs(obj);
    if (run("%s %s %s %s -c inject/main.cpp -o %s", GXX, CXXFLAGS, INCLUDES, DEFINES, obj))
        return fail("Compiling the loader");
    if (run("%s %s -o build/lodestone-inject.exe "
            "-static-libgcc -static-libstdc++ -lpsapi -luser32 -lkernel32",
            GXX, obj))
        return fail("Linking the loader");

    /* 4. Prove the DLL is injectable: it must import only libraries that are
     *    always present. A libstdc++-6.dll here means the static link slipped. */
    printf("\n--- DLL imports (must NOT list libstdc++-6.dll) ---\n");
    fflush(stdout);
    run("%s -p build/lodestone_client.dll | findstr /C:\"DLL Name\"", OBJDUMP);

    printf("\n=== Done ===\n"
           "  build\\lodestone_client.dll  - inject this\n"
           "  build\\lodestone-inject.exe  - the loader\n");
    fflush(stdout);
    system("pause");
    return 0;
}