Anti-fingerprint hardening for the loader window:
- The OS-level window title (read by GetWindowTextW / window scanners) is
now a fresh random alphanumeric string on every launch instead of the
fixed "OpenZen Loader" text. The visible custom title bar keeps showing
the branded name, so the UI is unchanged.
- The window size gets a per-launch random jitter of +/-10px on each axis
(base 760x500), with the minimum size lowered by the jitter so a negative
jitter isn't clamped.
Note: the Win32 *class* name is intentionally not randomized here — a Qt6
top-level window's class atom is fixed at CreateWindow time and Qt 6.10
exposes no hook to customize it (SetClassLongPtr cannot rename a class),
so randomizing it would require replacing the Qt window with a hand-rolled
native window. Title + size are the practical fingerprint vectors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the standard QMainWindow chrome and QTableWidget with a
purpose-built Qt UI built around hand-rolled widgets and animations:
- SplashScreen: frameless translucent splash with a wordmark fade/scale,
a sweeping scan line and a pulsing accent glow. ~950ms total cold-start
reveal that fades out into the main window.
- TitleBar: custom title bar replaces the OS frame, owns drag-to-move,
minimize/close buttons, and a breathing scan-status dot.
- InstanceList + InstanceRow: scrollable column of self-painted rows
(PID, title, Inject button). Rows are not selectable, double-click
has no special meaning - injection happens only through the button.
Hover animates each row's tint; new pids play a slide-in/opacity
entrance.
- InjectionOverlay: modal-style frameless overlay shown while inject()
runs in a worker thread. Spinner ring + progress bar advance during
injection; completion swaps to a checkmark / X, holds briefly, then
fades out. After it closes, the main window fades and the loader
self-quits.
- MainWindow: frameless + translucent panel painted by hand; entrance
fade/slide on launch; uniform playExitThenQuit() shared by the close
button and the post-injection path so every exit path gets a fade-out
instead of a hard vanish.
Also enables Win11 rounded corners via DwmSetWindowAttribute on show.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GitHub Actions (.github/workflows/build-loader.yml)
- Runs on every push to master and on workflow_dispatch.
- windows-2022 runner: setup-java 17 (Temurin), ilammy/msvc-dev-cmd
for MSVC x64, clones + bootstraps microsoft/vcpkg into the
workspace, installs UPX via choco, caches Gradle, then runs
`gradlew --no-daemon clean dll upxCompress` with VCPKG_ROOT and
OPENZEN_BUILD_REVISION exported.
- vcpkg's x-gha binary cache is wired in via VCPKG_BINARY_SOURCES +
ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN, so the first run pays
the Qt static-build cost (~30 min - 2 h) and every push after
that pulls cached qtbase artifacts.
- Publishes build/dist/OpenZenLoader.exe as the
OpenZenLoader-<sha> artifact (30 day retention).
Git revision in window title
- build.gradle: gitShortRevision() prefers OPENZEN_BUILD_REVISION
from the environment (set by CI) and falls back to
`git rev-parse --short=7 HEAD` for local builds.
- configureNative passes -DOPENZEN_BUILD_REVISION=<sha> to CMake
and logs the resolved value.
- native/loader/CMakeLists.txt forwards it as a compile definition.
- MainWindow.buildUi sets the window title to
"OpenZen Loader (build abc1234)" when the macro is defined, plain
"OpenZen Loader" otherwise.
UPX compression
- New upxCompress Gradle task: locates upx on PATH, runs
`upx --best --lzma` on build/dist/OpenZenLoader.exe and logs the
before/after size. Skips with a warning (not an error) when upx
is missing so local builds keep working without it. CI's
`gradlew clean dll upxCompress` always shrinks the artifact.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
build.gradle
- Rewrite packageDist: drop the Copy task type and use a plain
task with `copy { from src; into ... }` inside doLast. The
Copy task snapshots its source set at configuration time, so
a first `./gradlew clean dll` saw the source path before
buildNative produced OpenZenLoader.exe and ended the task as
NO-SOURCE. Doing the copy in doLast defers source resolution
to after buildNative, which is when the EXE actually exists.
- Bail with a clear GradleException if the EXE is still missing
after buildNative instead of silently leaving build/dist
empty, and log the packaged size on success.
native/loader/src/MainWindow.cpp
- Loosen the in-game window filter to: title startsWith
"Minecraft" OR window class equals "GLFW30" (case-insensitive).
GLFW30 is the LWJGL3 window class Minecraft uses before the
title is set, so this catches the early-startup window too.
Drop the "Launcher" blacklist - launchers are no longer
matched by either branch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Loader UI
- Replace the Win32 ListView loader window with a Qt6 Widgets
one. main.cpp + new MainWindow.h/cpp: QTableWidget showing
PID / Window Title / Window Class, QTimer auto-refresh every
1 s, double-click a row to inject. Title filter is built in
(startsWith "Minecraft" and not containing "Launcher"), so
HMCL / MultiMC / generic Java apps never show up.
- Dark Fusion theme + inline QSS so the single EXE looks the
same on any Windows version.
- native/vcpkg.json declares qtbase[widgets] and build.gradle's
new findVcpkg() detects VCPKG_ROOT / C:/vcpkg / D:/vcpkg /
~/vcpkg / D:/vcpkg-<version> and passes
-DCMAKE_TOOLCHAIN_FILE + -DVCPKG_TARGET_TRIPLET=
x64-windows-static so Qt is fully statically linked. Final
OpenZenLoader.exe stays single-file (~30 MB) with no Qt6*.dll
or vcruntime/msvcp DLL imports beyond Windows system DLLs.
- Drop the old ui.cpp and the now-unused run_ui() entry on
loader.h.
Patch transformer
- asm.patchify.loader.PatchTransformer.wrapInvoke now tries a
strict owner+name+desc match first, falling back to the
historical (owner || name) matcher only if strict turns up
nothing. The old loose matcher counted every Mth.*(FFF)F call
against a wrap aimed at Mth.lerp(FFF)F, so a same-method
sibling wrap that deleted its own site shifted later slice
indices and made onRenderPitchLerp miss.
LivingEntityRendererPatch
- With the strict matcher in place, onRenderPitchLerp's slice
drops from (4,4) to (1,1) - there is exactly one
Mth.lerp(FFF)F call site in LivingEntityRenderer.render
(pitch lerp), as verified via javap on the runtime srg jar.
onRenderHeadYawLerp keeps slice (2,2) (2nd of three
Mth.rotLerp calls = head yaw). No more "no call site of
m_14179_" warning at boot.
Scaffold
- Rename the BooleanSetting field from advancedBlockSearch to
sneak so the Java name matches the user-facing label ("Sneak").
README
- Add the vcpkg / Qt prerequisites and update the injector
usage section to describe the auto-refreshing UI + double-
click flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Loader no longer touches disk: drop the LoadLibraryW path that
extracted OpenZen.dll to %TEMP%\OpenZenLoader and replace it with
an in-process PE loader that maps the embedded DLL straight into
the target Java process.
native/loader (new manual_map.cpp/.h):
- VirtualAllocEx in the remote process for SizeOfImage,
locally apply relocations + IAT patching (kernel32 etc. are
KnownDLLs so local GetProcAddress addresses are valid in the
remote), WriteProcessMemory the finished image once,
VirtualProtectEx to match section characteristics, then run a
minimal x64 trampoline shellcode that calls DllMain with
DLL_PROCESS_ATTACH using the proper ABI (shadow space,
16-byte stack alignment).
- embedded_dll.cpp now hands back a pointer into the .rsrc
section instead of writing the DLL out. injector.cpp shrinks
to a 14-line shim around inject_in_memory.
native (top-level CMakeLists.txt):
- Force /MT (CMAKE_MSVC_RUNTIME_LIBRARY = MultiThreaded) for
both the DLL and the loader EXE. Without this the manual
mapper's local-address import resolution trips over
vcruntime140 / ucrtbase, which are not KnownDLLs and may have
different bases in the target process - leading to NULL
derefs inside msvcp140 after injection. With /MT the only
import surface left is KERNEL32.
native/dll (jar_extract.cpp):
- Replace FindResource/LoadResource/SizeofResource with a
hand-rolled PE resource directory walker. FindResource paths
through LdrFindResource_U, which depends on the PEB.Ldr table
that manual-mapped modules are not part of, so the standard
API returns NULL for the embedded zen.jar after injection.
native/loader UI:
- Drop the now-pointless DLL Path edit + Browse button.
- Add per-process "Window Class" column harvested via
GetClassNameW alongside the existing window title.
- Add a Minecraft Only checkbox (default on): show only rows
whose window title startsWith "Minecraft" and does not
contain "Launcher", filtering HMCL / MultiMC / generic Java
apps out of the picker.
build.gradle:
- Add cleanNative (Delete) hooked into clean so wiping the
project also removes native/build/ and the staged
native/zen.jar (CMake would otherwise hold onto the previous
configure).
gradle.properties:
- Fill in the real mod metadata (id=hey, name=OpenZen,
authors=Shirona1337, group=io.github.openzen, description).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OpenZenLoader.exe now ships with a requireAdministrator manifest
instead of asInvoker. The Windows loader prompts for UAC consent
once at launch, which:
- guarantees OpenProcess / VirtualAllocEx / CreateRemoteThread
succeed against javaw.exe when the Minecraft launcher (HMCL,
MultiMC) was itself started elevated,
- avoids the silent injection failure observed on locked-down
standard user accounts.
The EXE also picks up the UAC shield overlay on its file icon.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ship a self-contained OpenZenLoader.exe (OpenZen.dll embedded as
RCDATA) that injects into a running Minecraft 1.20.1 Forge
javaw.exe and brings up the Zen client without -javaagent or a
mods/ entry. The existing Forge mod path stays intact.
Native (native/):
- DLL: JNI_GetCreatedJavaVMs + JVMTI late-attach via the JDK's
instrument.dll Agent_OnAttach to obtain a real Instrumentation;
URLClassLoader on the game loader loads a single bridge class.
- Loader EXE: Win32 listview of javaw.exe/java.exe processes
with window titles, CreateRemoteThread+LoadLibraryW injection,
embedded DLL extracted to %TEMP%\OpenZenLoader at runtime.
Java:
- shit.zen.dll.GameLoaderBridge re-defines every jar class onto
the Forge GameClassLoader (fixed-point retry for super-class
deps) and extracts non-class resources to
%TEMP%\openzen-resources-<pid>, exposed via the
openzen.resources system property.
- shit.zen.dll.DllBootstrap only runs Bootstrap.init +
registerPatches + installPatchesAndRetransform; ZenClient
construction stays on MinecraftPatch.onTick lazy-init.
- shit.zen.asm.Bootstrap parses mapping.srg, detects SRG vs
mojmap runtime via Minecraft.tick reflection, exposes
remapMethod/remapField used at 5 PatchTransformer match
points and from ReflectionUtil.
- ReflectionUtil.resolveField walks the superclass chain trying
SRG then mojmap on each level so e.g. activeEffects on
LocalPlayer resolves on LivingEntity.
- shit.zen.utils.misc.Assets unifies resource lookup with a
fallback to openzen.resources; fonts, cloud assets and WebUI
static files route through it.
- PatchAgent.installPatchesAndRetransform is now idempotent so
DllBootstrap and ZenClient.init can both call it safely.
Build:
- ./gradlew jar forge mod jar (unchanged)
- ./gradlew dll single-file Loader EXE in build/dist
- CMake auto-located via vswhere when VS 2022 ships it.
- README documents both paths and required toolchain.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>