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
# Lodestone client — a Windows DLL built with the mingw-w64 g++ toolchain.
#
# It cross-compiles from Linux (the primary path) and also builds natively on
# Windows; the only difference is the shell make hands recipes to, which is why
# the directory/clean commands below are chosen per OS. See BUILD.md.
#
# The C++ runtime is linked statically on purpose. A DLL that depends on
# libstdc++-6.dll cannot be injected into a game that has never heard of mingw:
# LoadLibrary fails with "a dependency is missing". Linking with g++ (rather
# than gcc) is what makes -static-libstdc++ actually take effect.
# The toolchain. On Linux these prefixed names are the cross-compiler; on
# Windows the same names ship with WinLibs/MSYS2 mingw-w64. Override on the
# command line if yours are unprefixed, e.g. `make CXX=g++ CC=gcc` — a
# command-line value wins over these. They are `:=`, not `?=`, on purpose: make
# has a built-in default for CXX/CC (g++/cc, the *host* compiler), and `?=`
# would leave that default in place, quietly building for the wrong platform.
CXX := x86_64-w64-mingw32-g++
CC := x86_64-w64-mingw32-gcc
OBJDUMP := x86_64-w64-mingw32-objdump
IMGUI := third_party/imgui
MINHOOK := third_party/minhook
BUILD := build
TARGET := $(BUILD)/lodestone_client.dll
INJECT := $(BUILD)/lodestone-inject.exe
# jni.h includes "jni_md.h" and the JDK keeps its own under include/<os>. We
# point at src/jni_md instead and never add include/<os>, so the Windows
# definitions win — otherwise jint would be a Linux `int` in a Windows DLL.
# jni.h itself still comes from a JDK: set JAVA_INC to its include directory.
# Windows example: make JAVA_INC="C:/Program Files/Java/jdk-25/include"
JAVA_INC ?= /opt/java/current/include
# Per-OS shell commands. Recipes are handed to cmd.exe on Windows and to /bin/sh
# elsewhere, and the two share almost no syntax — so make a directory, remove
# the build tree, and print the dependency check the way each shell expects.
ifeq ($(OS),Windows_NT)
# cmd's mkdir already creates intermediate directories, but errors if the
# target exists, so guard it; and it wants backslashes.
mkdir_p = @if not exist "$(subst /,\,$1)" mkdir "$(subst /,\,$1)"
RM_BUILD = if exist "$(subst /,\,$(BUILD))" rmdir /s /q "$(subst /,\,$(BUILD))"
# objdump|grep|sort is a Unix pipeline; on Windows just say how to check.
DEPREPORT = @echo Built $@. Check imports with: $(OBJDUMP) -p $@
else
mkdir_p = @mkdir -p $1
RM_BUILD = rm -rf $(BUILD)
DEPREPORT = @echo "--- dependencies (libstdc++-6.dll here means the static link failed) ---"; $(OBJDUMP) -p $@ | grep "DLL Name" | sort -u
endif
INCLUDES := -Isrc -Isrc/jni_md -I"$(JAVA_INC)" \
-I$(IMGUI) -I$(IMGUI)/backends -I$(MINHOOK)/include
DEFINES := -DWIN32_LEAN_AND_MEAN -DNOMINMAX -DUNICODE -D_UNICODE
# -MMD -MP emits a .d beside each .o listing the headers it included. Nearly
# all of this client lives in headers, so without them `make` sees only the two
# .cpp files and quietly does nothing after a header edit — leaving a stale DLL
# that looks freshly built.
CXXFLAGS := -std=c++20 -O2 -Wall -Wextra -Wno-unused-parameter -fno-rtti -MMD -MP \
$(INCLUDES) $(DEFINES)
CFLAGS := -O2 -Wall -MMD -MP $(INCLUDES) $(DEFINES)
# No winpthread: everything that needs a thread uses CreateThread, which keeps
# the static link to just libstdc++ and libgcc.
LDFLAGS := -shared -static-libgcc -static-libstdc++ \
-Wl,--exclude-all-symbols \
-lopengl32 -lgdi32 -luser32 -lkernel32
CXX_SRC := src/main.cpp src/overlay.cpp \
$(IMGUI)/imgui.cpp $(IMGUI)/imgui_draw.cpp \
$(IMGUI)/imgui_tables.cpp $(IMGUI)/imgui_widgets.cpp \
$(IMGUI)/backends/imgui_impl_opengl3.cpp
C_SRC := $(MINHOOK)/src/hook.c $(MINHOOK)/src/buffer.c \
$(MINHOOK)/src/trampoline.c $(MINHOOK)/src/hde/hde64.c
CXX_OBJ := $(patsubst %,$(BUILD)/%.o,$(CXX_SRC))
C_OBJ := $(patsubst %,$(BUILD)/%.o,$(C_SRC))
.PHONY: all clean
all: $(TARGET) $(INJECT)
# The loader is a plain console executable: no ImGui, no MinHook, just the
# process walk and the remote LoadLibraryW.
$(INJECT): $(BUILD)/inject/main.cpp.o
$(call mkdir_p,$(@D))
$(CXX) $^ -o $@ -static-libgcc -static-libstdc++ -lpsapi -luser32 -lkernel32
$(TARGET): $(CXX_OBJ) $(C_OBJ)
$(call mkdir_p,$(@D))
$(CXX) $^ -o $@ $(LDFLAGS)
$(DEPREPORT)
$(BUILD)/%.cpp.o: %.cpp
$(call mkdir_p,$(@D))
$(CXX) $(CXXFLAGS) -c $< -o $@
$(BUILD)/%.c.o: %.c
$(call mkdir_p,$(@D))
$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM_BUILD)
# Pulled in last, and with a leading '-' so a first build (no .d files yet) is
# not an error.
-include $(CXX_OBJ:.o=.d) $(C_OBJ:.o=.d) $(BUILD)/inject/main.cpp.d