Sign in Sign up
kretrod/lodestone-cpp Public
Branches
main
109 lines (92 loc) · 4.8 KB Raw
# 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 itself is vendored under third_party/jni, so no JDK is needed to build.
# jni.h includes "jni_md.h" and a JDK keeps its own machine-dependent one under
# include/<os>; we ship a Windows jni_md.h in src/jni_md and put it first, so
# the Windows definitions win — otherwise jint would be a Linux `int` in a
# Windows DLL. JAVA_INC is therefore optional; set it only to build against a
# different JDK's headers, e.g. make JAVA_INC="C:/Program Files/Java/jdk-25/include"
JAVA_INC ?=
JAVA_INC_FLAG := $(if $(JAVA_INC),-I"$(JAVA_INC)",)

# 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 -Ithird_party/jni $(JAVA_INC_FLAG) \
            -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