Don't auto-enable in the constructor — opt-in only, matching every
other render module. Pairs with the onDisable fix from 9ad6a6b so the
module starts off and can be toggled freely.
Drop the onDisable override that immediately re-enabled the module —
left-clicking the entry in the ClickGui now actually turns it off.
The constructor still defaults the module on, so the protection is
opt-out rather than opt-in.
Reworks vertical placement in PanelClickGui / CategoryBar / ProfileWidget
/ SettingsPopup / NumberSettingRenderer so search-bar icon + placeholder,
category bar Y, role badge, dropdown header/value/arrow/items, and the
number widget label/sign/value/caret all derive their Y from CapHeight
instead of ascent+descent or hard-coded pixel offsets — fixes drift at
non-100% GUI scale.
Co-Authored-By: Koe <KoeNotPrism@proton.me>
Adds a short subsection under 后门 documenting the upstream author's
QQ-group claim that another contributor used a backdoor to remotely
read user files, with the chat screenshot as evidence.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promote the standalone mapping/BACKDOOR.md into the main README so the
backdoor discussion (screen capture, RCE / file download / file browse
packets, the upstream author's contradictory responses) is visible
without an extra click. Includes captured Trace screenshots, the
ToString-leaked CPacketSystemInfo class and the author's QQ-chat /
Bilibili rebuttals.
File moves:
- mapping/BACKDOOR.md -> inlined into README
- mapping/screenshot-*.png -> img/ (alongside the other readme assets)
- Several new images under img/ (RCE, backdoor*, CPacketSystemInfo, meme)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Forge mod entry point no longer works in this project, so the
build section was steering people toward a path that wouldn't load.
Reframe the two delivery shapes as Java Agent jar and hot-injector
EXE, with a callout that mods/ is unsupported.
Other readme drift caught in this pass:
- UPX listed as an optional common prerequisite with install
instructions (choco / upx.github.io) — the upxCompress task already
no-ops when upx isn't on PATH, this just makes the option visible.
- 使用注入器 步骤 updated for the Inject-button UI (previously
documented "double-click a row").
- 删除 stale "构建流程" 列表 in the injector section.
- 后门 段更新 + 布吉岛 段加删除线 reflecting current detection
status (user-authored edits picked up in the same commit).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Before: MSBuild defaulted to a single process per project, so each
vcxproj compiled its .cpp files sequentially. Local clean rebuild of
OpenZen.dll + OpenZenLoader.exe measured ~14.6 s.
After: same cold rebuild measures ~4.8 s — 3x faster — by combining
- `cmake --build --parallel <Runtime.runtime.availableProcessors()>`
in the buildNative task, which forwards a project-level `-m:N` to
MSBuild and gets DLL / Loader to build concurrently where the
dependency graph allows.
- `/MP` on the MSVC compile options, which is the actual win: it lets
cl.exe spawn one compiler process per available core within a
single vcxproj. The DLL+Loader together have ~13 .cpp files; this
is where the bulk of wall-time was being spent serially.
CI runners get the same speed-up for free.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three workflow ergonomics tweaks:
- Path filter now also includes build.gradle / settings.gradle /
gradle.properties / gradle/wrapper/** / gradlew(.bat) and the
workflow file itself, so a wrapper bump or build-script change
still triggers CI even though only src/ and native/ files compile
into the final artifact.
- Job-level if: skips the entire build when the push commit message
contains [SKIP CI] (Actions' contains() is case-insensitive for
string operands, so [skip ci] and [SKIP CI] both qualify).
workflow_dispatch is unaffected because head_commit is null there.
- [Release] marker detection in the PowerShell publish step now uses
StringComparison.OrdinalIgnoreCase so [release], [Release] and
[RELEASE] all cut a release; .NET's default .Contains is case-
sensitive which made the existing rule too brittle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add an opt-in publish step at the tail of the build job:
- Bump job permissions to contents: write so the workflow token can
create releases.
- Read the HEAD commit message via `git log -1` and set an
is_release output when it contains the literal substring [Release].
- When set, `gh release create build-<sha>` and attach the staged
OpenZenLoader-<sha>.exe and OpenZen-<sha>.jar. Notes are piped
through a file (--notes-file) so brackets / newlines in the commit
message can't corrupt the CLI invocation.
Pushes without the marker keep producing only the existing per-build
artifacts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cross-checked against the original obfuscated jar via Recaf. Seven PUA
codepoints in the icon string literals had been replaced with empty
strings somewhere in the deobf pipeline, which left the alert HUD with
no icons and an off-centred text layout (since titleFont.getWidth("")
returns 0, the text was being squeezed against the left edge).
Restored:
- ender_pearl glyph (U+E55E) for the projectile alert
- bolt glyph (U+EA0B) for the lightning alert + hasPathIcon
discriminator + static-block glyph lookup
- arrow_upward / arrow_downward (U+E5D8 / U+E5DB) for the off-screen
direction indicator + the rotated fallback case
Font sizes already matched the original (48 / 44 / 8 / 6); the apparent
"text too small" was an artefact of the zero-width icon collapsing the
row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cherry-picked the GUI fixes from #31 (by @Kyresn). Excluded the unrelated
.gitignore / META-INF/MANIFEST.MF / ZenClient changes from that PR:
MANIFEST.MF is generated by the jar task and must not be tracked, and
the isClientDist() guard is a no-op on a 1.20.1 client.
- Add visibility.displayable() guards on Boolean / Mode / MultiSelect
setting elements and at the ModuleElement dispatch site so hidden
rows (e.g. Eagle/Snap under Scaffold Telly Bridge) can no longer
steal clicks meant for the rows below them.
- Expand BooleanSettingElement's click hitbox to the full row, matching
OldClickGui behaviour.
- Drop the in-bounds gate on mouseReleased in ModuleElement and
CategoryPanel so a NumberSetting slider drag that ends outside the
panel still releases isDragging.
Co-Authored-By: Kyresn <179435327+Kyresn@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <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 deprecated Node 20 for JavaScript actions on 2025-09-19; v4 of
checkout / setup-java / cache / upload-artifact and ilammy/msvc-dev-cmd
still ship a Node 20 binary in action.yml. Set
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true at job scope so the runner
executes them under Node 24 now - silences the deprecation warning and
removes a 2026-06-02 surprise when the default flips.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stage build/dist/OpenZenLoader.exe and build/libs/hey-1.0.jar into
build/release as OpenZenLoader-<sha>.exe and OpenZen-<sha>.jar, then
publish each as its own upload-artifact entry whose name matches the
file. actions/upload-artifact always wraps a zip around the payload, so
this is as close as we can get to "no wrapper" without cutting a Release
- documented inline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vineflower decompiled the original into one large performInventoryAction
plus a few oddities (dead `shouldSkip` guard, dead keyJump/keyShift reads,
unused locals). The previous rewrite split the offhand handling into
several helpers, which inverted the if/else if chain and silently
changed which branch wins when multiple offhand modes match.
Re-align with the original control flow:
- onPacket: drop the `shouldSkip = false; if (shouldSkip) return;` dead
guard; collapse the ClickPacket/ClosePacket cancel under an explicit
externalContainerOpen short-circuit so external chests/furnaces fall
through untouched.
- onMotion: early-return for the no-pending-packets branch and strip the
side-effect-free keyJump/keyShift reads.
- performInventoryAction: re-merge the split handle* helpers into the
flat if / else if chain the original jar uses.
- isUsefulItem: flatten back into the per-item return ladder.
- Drop unused imports (Screen, Iterator, ChestMenu) and add the missing
ChatUtil import that the packet trace lines need.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous run still rebuilt Qt from source on the second push.
vcpkg's x-gha binary cache backend relies on the v1 GitHub Actions
cache API and the ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN
secrets exported via actions/github-script. On the current
windows-2022 image the v1 API is being phased out (the new
ACTIONS_RESULTS_URL is what's reachable), so x-gha quietly missed
on every run.
Switch to a path-based actions/cache@v4 entry:
- path: vcpkg\installed + vcpkg-archives
- key: vcpkg-qt-${runner.os}-${hash(native/vcpkg.json)}
- restore-keys: vcpkg-qt-${runner.os}- (warm start on a vcpkg.json bump)
vcpkg.installed is the deployed Qt tree CMake's manifest mode
inspects; if it already contains qtbase[widgets] the first vcpkg
install call is a no-op and CMake configure finishes in seconds.
vcpkg-archives gets fed via the new
VCPKG_BINARY_SOURCES=clear;files,...\vcpkg-archives,readwrite
binary source so transitive package binaries are also persisted
across runs - useful when vcpkg.json changes and only some libs
need rebuilding.
Also drop the now-unused "Export GitHub Actions cache tokens"
step (no x-gha backend, no tokens needed).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The deobfuscated NoSlow kept the original jar's pay-role gate
around its Grim V3 mode:
hasGrimRole() -> false stub (no auth bridge in this fork)
checkAndFallbackMode() -> if !hasGrimRole && mode == "Grim V3":
mode = "NoSlow"
That made every "Grim V3" selection snap back to "NoSlow" the
next tick - reported as NoSlow Mode grimv3 cannot be enabled
in #27.
We are open source; the paywall is gone. Delete the whole gate:
- hasGrimRole / isGrimMode / isGrimModeActive /
checkAndFallbackMode / createModeSetting are removed.
- The three checkAndFallbackMode call sites (ctor / onEnable /
onTick) are removed.
- `mode` is initialized inline with the two-option ModeSetting.
- isGrimSlowMode / isNoSlowMode collapse to mode.is(...) one-
liners so the rest of NoSlow still picks the right slowdown
branch.
Closes#27
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
windows-2022 runners with VS Enterprise installed pre-set a
system-wide VCPKG_ROOT pointing at
"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\vcpkg".
That value wins over the job-level `env: VCPKG_ROOT:` in PowerShell
expansions, so the previous run tried to git-clone into that
read-only path and failed with "destination path ... already exists
and is not an empty directory".
Drop the job-level VCPKG_ROOT and instead resolve the path to
"$GITHUB_WORKSPACE\vcpkg" inside the clone step, then write it
back via GITHUB_ENV so every later step (including Gradle's
findVcpkg / configureNative) picks up our copy. The build step
also dumps VCPKG_ROOT / JAVA_HOME / OPENZEN_BUILD_REVISION up
front for quick diagnosis if the override ever drifts again.
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>
Telly Bridge Scaffold (and other paths that lean on
ReflectionUtil.setJumpDelay / setRightClickDelay) silently broke
under DLL injection because the SRG name we were trying on the
production runtime was wrong:
noJumpDelay was f_20889_ -> f_20954_ (LivingEntity)
rightClickDelay was f_91076_ -> f_91011_ (Minecraft)
findField walks the superclass chain and tries every candidate
name, so in a dev mojmap environment the mojmap names match first
and we never noticed. In a SRG production runtime both candidates
miss, findField throws ReflectionException, the surrounding try
swallows it as "Failed to set ... field", and Telly Bridge's
jump-delay reset never lands - the scaffold then mis-times the
next placement.
The other ReflectionUtil entries (yRot/xRot/depthBufferId/
brewingStand/missTime) use SRG names that were already correct
for 1.20.1; left untouched.
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>
- Title no longer carries the [WIP] marker now that the DLL injection
and module restoration work has stabilised.
- Minor wording tweak in the early-build disclaimer.
- New "常见问题" section noting that Bujidao currently does not
detect the project (verified 2026-05-23) since its anti-cheat is
class-name blacklist based; suggest renaming classes at build time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Module keybinds used to fire while ChatScreen, PauseScreen or any
other UI was open, so typing a chat message or being in the pause
menu would silently toggle modules whose bind happened to match a
key in the text.
Skip the keybind scan in onKey() unless mc.screen is null, matching
vanilla's "no GUI" gating used elsewhere.
Fixes#19
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- README: announce QQ group 523522206 for project discussion.
- ZenClient: remove bootstrapForDll(). The DLL path went from
"GameLoaderBridge -> DllBootstrap.start -> new ZenClient" to just
"DllBootstrap.start -> registerPatches + installPatchesAndRetransform"
a few commits ago, so MinecraftPatch.onTick's existing lazy-init
now constructs the singleton on the next tick and no external
caller needs this helper anymore.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KillAura
- Field set realigned with the obfuscated jar: drop rotationsMode,
attackMode, targetHud, noUseItem, aboveTarget; keep 12 boolean /
7 number / 3 mode + sprintCounter.
- Settings renamed per the user's config sheet: Infinity Switch,
Keep Sprint, Max APS, Min APS, Switch Delay (Attack Times), FoV,
Priority. Defaults: attackMobs/multiAttack/keepSprint=true,
aimRange=4, maxAps=12, minAps=9, Priority=FoV.
- New Test/More Particles/Ignore skip ticks/Fake AutoBlock/Delay
Mode settings now drive real behaviour:
* test -> "attack player above" branch in isValidTarget
* ignoreSkipTicks -> attack pacing guard in onPreMotion
* morePart -> magicCrit+crit in attackEntity
* delayMode "1.8" smooths attacks across ticks; "1.9" uses
sprintCounter + attackStrengthScale gating
* fakeAutoBlock surfaces to OldHitting (see below)
- Target ESP turned into a 4-mode ModeSetting (None/Spiral/Box/Tab)
with the original hurt-tinted box and tab renderers wired through
RenderUtil + EntityUtil.
- attackEntity bridged back through ForgeHooksClient.onMouseButton*
so Forge mouse listeners still see the synthesised swings.
- doAttack now bails on isWebPlacing first and surfaces an
AntiBots-skip notice via ChatUtil.print.
- All settings + internal flags moved to inline field initializers;
constructor only does super(...) and INSTANCE = this.
NoSlow (was FastUse)
- Rename module, file, INSTANCE type, mods.toml-facing display name,
and all call sites (ModuleManager, MinecraftPatch).
- Mode option list now ("Grim V3", "NoSlow") with Grim V3 default;
isGrimMode probes the new label.
- User-facing settings renamed (Bow / Crossbow / Food / Potion drop
the NoSlow suffix). Bow/Crossbow defaults flipped to false.
- Settings + state moved to inline initializers; constructor only
does super(...), INSTANCE = this, checkAndFallbackMode().
OldHitting
- isKillAuraAttacking now gates on the restored fakeAutoBlock flag,
matching the obfuscated jar's animation trigger.
ReflectionUtil-side knock-on cleanup (the SRG/mojmap remap helpers
introduced earlier) is what made these field-name swaps safe at
runtime in both dev and DLL-injected environments.
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>
Add YAML issue forms under .github/ISSUE_TEMPLATE/ with required
fields for bug reports (behavior, repro, expected fix), crashes
(log, repro) and suggestions. Disable blank issues via config.yml
to force template usage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the entity-state force-write in RotationHandler.onHeadTurn with
a render-side override that fires RotationAnimationEvent from inside
LivingEntityRenderer.render's Mth.rotLerp / Mth.lerp call sites — the
local entity's yBodyRot/yHeadRot/xRot keep doing Mojang's natural body-
follows-head lerp.
Wrap slices account for PatchApplier's loose (owner || name) && desc
matcher: head yaw is rotLerp #2, head pitch is lerp #4 (after the three
Mth.rotLerp matches that the owner-match alone pulls in).
Also surface PatchTransformer behavior so future broken targets are
visible: log every applied handler at INFO and warn whenever a wrap /
invoke / TAIL inject / ModifyLocals anchor finds no site.
Drop the runtime Port / Open Browser settings — the original Zen WebUI
hard-codes :8089, and the in-game settings round-trip added no value.
Replace the placeholder panel with the deobfuscated Tailwind-based
Web Click GUI shipped by Zen.
Recreate the shit.zen.network.webui handlers (categories, modules,
toggle, get/set setting, static files) from Recaf and wire them into
the WebUI module so it serves a control panel from a configurable
local port. Ships a Tailwind-based panel under resources/webui/.
- Remove ZenClient.isOwner(String) -- it has been hardcoded to return
true since the Encryption / owner-check teardown, so every caller
was effectively a no-op. Drop the method and the only remaining
caller in NameProtect (which was filtering out the local player
twice via name equality, then again via isOwner, with the same
always-true result).
- KeyBindsHud: write the keyboard / power-settings glyphs as Java
unicode escapes ('\uE1C6' / '\uE9F6') instead of inline PUA bytes,
so the source stays diff-friendly when editors strip non-ASCII.
- OpalNameTag: drop the 'decoded name suffix' branch (the helper it
called is gone, leaving the suffix permanently empty), and restore
the missing health icon glyph ('\uE87D') that had been stripped to
an empty string -- the name-box width math no longer reserves space
for the absent decoded suffix either.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Several Recaf-side behaviors were missing or wrong in the current
KillAura, which is why the module felt 'broken':
- onTick short-circuit was too aggressive: any non-null mc.screen
killed everything. Recaf only bails for AbstractContainerScreen and
for the rotation-owning helpers (Helper / AntiWeb / AntiTNT /
MidPearl / Stuck / AutoWebPlace), plus ItemUtil.hasServerItem().
Pure UI screens (chat, F3, vanilla menus that aren't containers) now
no longer wipe the target.
- Switch trigger used the wrong counter: it compared the float
'attacks' accumulator against switchAttackTimes, but Recaf has a
separate per-attack counter (attackTimes) compared against
switchDelay. Added the missing field and swapped the predicate so
Switch Delay actually controls how long we stay on a target.
- Attack pacing was reduced to a flat 'attacks += aps/20'. Recaf only
feeds the accumulator in Smooth mode and uses
MathUtil.randomDouble(switchAttackTimes, aps) / 20.0 to jitter the
rate; Snap mode instead waits for getAttackStrengthScale >= 0.9F
and respects a sprintCounter cooldown set from
getCurrentItemAttackStrengthDelay(). AntiKB.NoXZMode and sprintSync
scale the rate too.
- attackEntity() now bumps attackTimes, gates the actual attack +
swing on (sprintTickCounter % 2 == 0) when sprintSync is on, runs
the magicCrit + crit pair when targetHud is on, and seeds
sprintCounter for Snap mode.
- doAttack() / multiAttack now use RotationUtil.getHitDistance along
the active rotation rather than just closestPoint distance, which
is what Recaf does. Dropped a defensive 'attack target if not
hovering anyone' fallback that was not in Recaf.
- updateTargets() guards against the null getTargets() can return so
targetList is always a usable list.
- onDisable now also clears sprintCounter and attackTimes alongside
sprintTickCounter so re-enabling the module starts from a clean
state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The header keyboard icon next to 'Hotkeys' and the toggle icon used
when a module has no bound key were both left as empty string literals
in the deobfuscated source. Recaf has them as:
initSettings(): getStringWidth("\uE1C6", bindFont)
getStringWidth("\uE9F6", bindFont)
renderRows(): drawTextWithShadow("\uE1C6", ..., bindFont, ...) (both right- and left-aligned branches)
drawTextWithShadow("\uE9F6", ..., bindFont, ...) (no-keyName branch)
\uE1C6 is the Material Icons "keyboard" glyph and \uE9F6 is
"power_settings_new". The bindFont uses materialIcons(18.0f) so the
codepoints just need to be put back as Java unicode escapes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The panel and search-bar backgrounds were calling TextGlow.drawBackground
only, which paints two translucent rounded rects (a dark base + a thin
white overlay). The original visual is supposed to be a frosted-glass
blur of the gameplay behind it -- the blur shader was loaded and
wired up via RenderUtil.drawBlurredRect (blits the main render target
into a TextureTarget, then samples it through the 'blur' shader from
ShaderSource), but nothing was actually invoking it.
Call RenderUtil.drawBlurredRect right before TextGlow.drawBackground
in both drawPanelGlow and drawSearchBar, using the existing radius
and effective alpha so the blur fades in alongside the panel open
animation. The shader assets (blur.fsh / blur.vsh / common.glsl) are
embedded in ShaderSource enum, so no extra resource files are needed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per request: drop framework-level comparison, drop arguments based on
field/class/method names (those are renamed during deobfuscation and
do not prove plagiarism by themselves). Keep only the 14 modules
listed by the user plus the utility classes, and back every claim
with actual code blocks placed side-by-side so a reader can see the
two snippets are the same thing.
Modules included: AntiBots, CrystalAura, AntiFireball, Scaffold,
FastWeb, Stuck, AutoMLG, SafeWalk, Disabler, AutoTools, ChestStealer,
InventoryManager, ChestESP, Compass, Projectiles, plus a utility-
class section (ChunkUtil, RotationUtil, BlockUtil, MovementUtil,
RayTraceUtil).
The evidence now hinges on three categories of artefacts that
deobfuscation cannot fabricate: string literals ("Fake Staff Detected!
(", "Stream limit didn't work.", "点击使用", "Normal"/"Telly Bridge"
/"Keep Y", etc.), magic constants (+1337, (0.88,1.88,0.88), > 2 &&
< 0.0001, i*i+1, 72000, 0.6/0.2/1.2, +1.62, RGB (173,12,255)...),
and signature import/dead-code fingerprints (antlr-runtime's
OrderedHashSet used in a rotation util; MovementUtil.getDirectionAngle
copied verbatim despite being a private unused method).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Populate paste/README.md with a per-module / per-subsystem comparison
between OpenZen (this repo, deobfuscated from Zen client) and
Naven-Modern (github.com/Margele/Naven-Modern). Findings collected by
running parallel agents against both source trees:
- Functional modules: 30+ Zen modules trace back to a 1:1 Naven
counterpart, either same-named or renamed (e.g. KillAura<-Aura,
CrystalAura<-AttackCrystal, ESP<-Glow, InventoryManager<-
InventoryCleaner using Naven's @ModuleInfo internal name). Setting
strings, default values, ranges, RGB constants, and even Chinese
literals like "点击使用" survive verbatim.
- Framework: EventBus/EventTarget/EventPriority are the renamed twins
of Naven's EventManager/EventTarget/Priority (same DarkMagician6
EventAPI shape, identical default byte priority 2). Module,
ModuleManager, Setting/Value, CommandManager, util classes (
RotationUtil keeps Naven's odd antlr OrderedHashSet import,
ChunkUtil keeps "Stream limit didn't work.") all line up.
- Fingerprints that can't be hand-waved away: +1337 magic offset in
Stuck; the typo 'Recorvey' carried over from rewrites; the
Projectiles RGB triplets (173,12,255)/(255,238,154); identical
sensitivity-GCD formula scaled*scaled*scaled*1.2F.
The doc also tabulates module-level severity ("line-by-line" / same-
name+rewrite / not plagiarized) so readers can skim before diving in.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
README: clarify that the deobfuscated jar is the latest one as of
2026-05-21 (rather than an old build, as some users seem to assume),
collapse a stray hard wrap, and link to a new paste/ subtree that
will track which modules were lifted from the Naven client.
Also rename KillAura.aimRange to attackRange to match the user-facing
'Attack Range' label and how the field is actually used in the in-FOV
check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The earlier change had Renderer.render seed DrawContext with
GuiGraphics.pose() so PanelClickGui's pushPose+scale(0.98+0.02*eased)
would also drive the inner DrawContext draws. It did not visibly fix
the search bar — icon/query/placeholder still rendered too high
relative to the search background — and quietly mutates the shared
matrix stack while CustomFont is busy pushing its own glyph
transforms on top of it.
Restore the Recaf-equivalent behavior: Renderer.render builds a
DrawContext with its own fresh PoseStack. The PanelClickGui body
wrapping with Renderer.render is kept since that is what actually
gets ModuleListPanel's scissor to take effect in Screen-mode renders.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous attempt only made Renderer.render share GuiGraphics.pose()
with DrawContext when guiGraphics was non-null. But every sub-panel
call (drawSearchBar, drawToasts, ModuleListPanel, SettingsPanel,
CategoryBar, ProfileWidget, SettingsPopup, KeybindOverlay,
ScaleSwitchOverlay) used Renderer.renderConsumer, which falls back
to Renderer.render(null, ...) when currentCanvas == null — that's
exactly the Screen.render path. With guiGraphics == null the
DrawContext still got a private new PoseStack(), so any element
rendered through DrawContext was identity-posed while the search
background drawn via guiGraphics.pose() rode along with
pushPose + scale(0.98+0.02*eased), and the two drifted apart.
Wrap the entire PanelClickGui body inside Renderer.render(
guiGraphics, ...). That sets currentCanvas to a DrawContext whose
PoseStack is GuiGraphics.pose(), so every nested renderConsumer
short-circuits into the same DrawContext and shares the same
matrix. Search icon, placeholder, query text and the blinking
cursor now sit on the same scaled pose as the search background.
Toasts get the fix for free.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related render bugs in PanelClickGui (Screen context):
1) Module list overflow when a category has more entries than fit:
ModuleListPanel.render called Renderer.renderConsumer, which in
the Screen context (currentCanvas == null) falls through to
Renderer.render(null, ...). That built a DrawContext(null), and
DrawContext.clipRect short-circuits when guiGraphics is null, so
enableScissor was never issued. The inner clip in renderModuleList
was also skipped, so rows past the panel bottom were drawn freely.
Switch ModuleListPanel.render and renderSearchResults to
Renderer.render(guiGraphics, ...). The DrawContext now has the
live GuiGraphics, so both the outer clip and the renderModuleList
inner clip actually call enableScissor and the list stays inside
its panel.
2) Search bar (and other PanelClickGui inner draw lambdas) drifted
relative to their backgrounds. Renderer.render created a
DrawContext with a fresh "new PoseStack()" regardless of the
GuiGraphics that was passed in. PanelClickGui wraps its render in
pushPose + scale(0.98+0.02*eased), so primitives drawn through
guiGraphics.pose() (search background, cursor rect, toasts) got
the scaled pose while primitives drawn through the DrawContext
(search icon, query text, placeholder) used identity — they
visually drifted apart whenever scaleFactor != 1.
Construct DrawContext with the GuiGraphics's PoseStack instead of
a private one, so every path shares the same matrix stack and
the search bar and toasts render coherently with the rest of
PanelClickGui.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related rendering regressions:
1) OldClickGui category headers — the switch in legacy CategoryPanel
only mapped COMBAT..EXPLOIT to icon glyphs 'a'..'e' and fell back
to '?'. The Category enum has WORLD ("Misc") and MISC ("Ghost")
too, so those two headers rendered as a question mark. Extend the
switch with WORLD -> 'f' and MISC -> 'g'.
2) PanelClickGui rendering — several Material Icon PUA codepoints
had been silently stripped to empty strings during earlier source
passes, leaving the panel without bind icon, profile-popup logo /
close button / dropdown arrow, and the number-setting edit icon.
Restore them per Recaf:
- ModuleListPanel bind icon \uE312 (2 sites)
- SettingsPopup logo \uE8B8
- SettingsPopup close button \uE5CD
- SettingsPopup dropdown arrow \uE313
- NumberSettingRenderer edit pen \uE3C9
CategoryBar's 7 category glyphs and PanelClickGui's search icon
already had the correct PUA bytes on disk; they were just
invisible in tooling, so no change there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hand-written PlayerEntry never initialized widthAnim, so the
render path read 0 for entry width and drew nothing visible. Other
gaps:
- nameWidth was never measured, so item layouts collapsed.
- displayName bypassed NameProtect.replacePlayerName, leaking real
IGNs through the streamer/anti-doxx filter.
- startRemove() flipped a flag but never triggered the slide-out or
height-collapse animations, so isRemoveDone() (which used a custom
alpha decay) effectively never returned true.
- tick() mutated alpha / currentY by hand instead of just ticking
the animation timers the render code already consults.
Rewrite per Recaf's PlayerListHud$PlayerEntry:
- Constructor seeds itemStacks/cheatItems from initialItems, calls
updateItems(...) to set displayName, nameWidth, totalWidth, then
primes slide/height/alpha/width anims (width starts at totalWidth
so the row is visible from frame 1, then animates if it grows).
- updateItems() recomputes displayName via NameProtect, the cached
nameWidth via headerFont, the totalWidth from padding/head/items,
and animates widthAnim to the new total.
- startRemove() actually slides the entry out and animates height
to 0; isRemoveDone() defers to heightAnim.isDone().
- Drop the dead alpha / targetY / currentY / parent fields.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous EffectEntry had two render-killing inversions: show()
animated heightAnim to the target row height and widthAnim to 100,
but the render loop uses heightAnim as the 0..1 alpha factor
(multiplied by 80/140/160/185) and widthAnim as the actual row
height. That made every entry render with massively saturated alpha
and a 100-pixel-tall pill. getTotalWidth() also returned the height
animation value, so the sort order was wrong too.
Rewrite to mirror Recaf's PotionEffectsHud$EffectEntry:
- show(h): heightAnim -> 1.0 (alpha), widthAnim -> h (row height).
- startRemove() animates both back to 0; isRemoveDone() checks anim
isDone() instead of a hand-rolled alpha field.
- tick() drops the custom alpha decay and keeps the duration text
fresh for non-instantaneous effects.
- getTotalWidth() actually measures the text (icon + name + duration
+ padding) so sort-by-width works.
- updateEffect() bumps originalDuration when the new instance has a
longer remaining duration (stacked refresh).
- refreshDisplayText() delegates to the outer HUD for the infinity
symbol and roman-numeral amplifier formatting.
- Rename instance -> effectInstance and update render-site reads.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>