1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/* 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;
}
/* A real zip starts with "PK" and the toolchain archive is tens of MB; anything
* smaller, or without that signature, is a truncated download or an error page
* saved under the zip's name. */
static int valid_zip(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) return 0;
unsigned char sig[2] = {0, 0};
size_t got = fread(sig, 1, 2, f);
fseek(f, 0, SEEK_END);
long size = ftell(f);
fclose(f);
return got == 2 && sig[0] == 'P' && sig[1] == 'K' && size > 20L * 1024 * 1024;
}
/* Put the toolchain's bin on PATH (absolute), so g++ can find the programs it
* shells out to — the assembler `as`, the linker `ld`. Without this g++ fails
* with "cannot execute 'as'". Children launched via system() inherit this. */
static void add_toolchain_to_path(void) {
char cwd[1024];
if (!GetCurrentDirectoryA(sizeof cwd, cwd)) return;
const char *old = getenv("PATH");
if (!old) old = "";
size_t n = strlen(cwd) + strlen(old) + 32;
char *buf = (char *)malloc(n);
if (!buf) return;
snprintf(buf, n, "%s\\w64devkit\\bin;%s", cwd, old);
SetEnvironmentVariableA("PATH", buf);
free(buf);
}
/* Create the parent directories of a file path (e.g. build/src for
* build/src/main.cpp.o). Only the directories above the final component are
* made — never the file itself, or g++ would be handed a directory to write. */
static void mkdirs(const char *file_path) {
char t[1024];
strncpy(t, file_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;
}
}
}
/* 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;
}
/* Fetch the toolchain zip. curl.exe (bundled with Windows 10 1803+) follows
* GitHub's redirect to the download CDN, and --fail refuses to save an error
* page as if it were the archive — the failure mode Invoke-WebRequest hit.
* PowerShell is the fallback for older Windows. Either way the result is
* validated, and a bad file is removed so a re-run starts clean. */
static int download_zip(void) {
if (run("curl.exe -L --fail --retry 3 --retry-delay 2 -o %s %s", DEVKIT_ZIP, DEVKIT_URL) == 0 &&
valid_zip(DEVKIT_ZIP))
return 0;
DeleteFileA(DEVKIT_ZIP);
printf("\ncurl unavailable or the download was incomplete — trying PowerShell...\n");
run("powershell -NoProfile -ExecutionPolicy Bypass -Command "
"\"$ProgressPreference='SilentlyContinue'; "
"[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; "
"try { Invoke-WebRequest -Uri '%s' -OutFile '%s' } catch { exit 1 }\"",
DEVKIT_URL, DEVKIT_ZIP);
if (valid_zip(DEVKIT_ZIP)) return 0;
DeleteFileA(DEVKIT_ZIP);
return 1;
}
/* Unpack it. tar.exe (also Windows 10 1803+) is more tolerant of large zips
* than Expand-Archive, which is the fallback. Success is judged by the
* compiler actually being there afterward, not by an exit code. */
static int extract_zip(void) {
if (run("tar -xf %s", DEVKIT_ZIP) == 0 && exists(GXX)) return 0;
run("powershell -NoProfile -ExecutionPolicy Bypass -Command "
"\"Expand-Archive -Force '%s' -DestinationPath '.'\"",
DEVKIT_ZIP);
return exists(GXX) ? 0 : 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. Fetched once into .\w64devkit\; nothing installed globally. */
if (!exists(GXX)) {
printf("\nNo toolchain yet — fetching w64devkit " DEVKIT_VER " (about 73 MB, once).\n");
/* A half-finished archive from an interrupted or blocked download would
* only fail to unpack again, so drop it and start clean. */
if (exists(DEVKIT_ZIP) && !valid_zip(DEVKIT_ZIP)) {
printf("A previous download was incomplete — re-fetching.\n");
DeleteFileA(DEVKIT_ZIP);
}
if (!exists(DEVKIT_ZIP)) {
if (download_zip()) {
printf("\n[!] Could not download the toolchain.\n"
" Check your internet connection and run build.exe again, or\n"
" download it yourself from:\n %s\n"
" save it in this folder as %s, then run build.exe again.\n",
DEVKIT_URL, DEVKIT_ZIP);
fflush(stdout);
system("pause");
return 1;
}
} else {
printf("Using the toolchain archive already here.\n");
}
printf("\nUnpacking...\n");
if (extract_zip()) {
DeleteFileA(DEVKIT_ZIP); /* force a clean re-download next run */
return fail("Unpacking (removed the archive — run build.exe again to retry)");
}
printf("\nToolchain ready.\n");
} else {
printf("\nToolchain already present — skipping download.\n");
}
add_toolchain_to_path();
/* 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;
}