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 <[email protected]>
143 lines
5.6 KiB
Java
143 lines
5.6 KiB
Java
package shit.zen.patch;
|
|
|
|
import asm.patchify.annotation.At;
|
|
import asm.patchify.annotation.Inject;
|
|
import asm.patchify.annotation.Patch;
|
|
import asm.patchify.annotation.Slice;
|
|
import asm.patchify.annotation.WrapInvoke;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.multiplayer.ClientLevel;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.HitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraftforge.fml.ModList;
|
|
import net.minecraftforge.forgespi.language.IModFileInfo;
|
|
import net.minecraftforge.forgespi.language.IModInfo;
|
|
import shit.zen.ClientBase;
|
|
import shit.zen.ZenClient;
|
|
import shit.zen.asm.Invocation;
|
|
import shit.zen.event.impl.DisconnectEvent;
|
|
import shit.zen.event.impl.PostMotionEvent;
|
|
import shit.zen.event.impl.PreMotionEvent;
|
|
import shit.zen.event.impl.TickEvent;
|
|
import shit.zen.modules.impl.movement.FastUse;
|
|
import shit.zen.modules.impl.render.ESP;
|
|
import shit.zen.render.Renderer;
|
|
|
|
@Patch(Minecraft.class)
|
|
public class MinecraftPatch {
|
|
public static volatile boolean initialized = false;
|
|
private static HitResult savedHitResult;
|
|
|
|
public static void markInitialized() {
|
|
initialized = true;
|
|
}
|
|
|
|
@Inject(method = "tick", desc = "()V")
|
|
public static void onTick(Minecraft minecraft, CallbackInfo callbackInfo) throws Throwable {
|
|
if (!initialized) {
|
|
synchronized (MinecraftPatch.class) {
|
|
if (!initialized) {
|
|
ClientBase.mc = ZenClient.getMcInstance();
|
|
ClientBase.isLoading = true;
|
|
ModList.get().getMods().removeIf(modInfo -> modInfo.getModId().equals("hey"));
|
|
List<IModFileInfo> toRemove = new ArrayList<>();
|
|
for (IModFileInfo modFile : ModList.get().getModFiles()) {
|
|
for (IModInfo modInfo : modFile.getMods()) {
|
|
if (modInfo.getModId().equals("hey")) {
|
|
toRemove.add(modFile);
|
|
}
|
|
}
|
|
}
|
|
ModList.get().getModFiles().removeAll(toRemove);
|
|
new ZenClient();
|
|
initialized = true;
|
|
}
|
|
}
|
|
}
|
|
if (ZenClient.isReady()) {
|
|
ZenClient.serverTickRate = 1.0f;
|
|
ClientBase.yaw = minecraft.player.getYRot();
|
|
ZenClient.instance.getEventBus().call(new TickEvent());
|
|
}
|
|
}
|
|
|
|
@Inject(method = "tick", desc = "()V", at = @At(At.Type.TAIL))
|
|
public static void onTickPost(Minecraft minecraft, CallbackInfo callbackInfo) throws Throwable {
|
|
if (ZenClient.isReady()) {
|
|
ZenClient.instance.getEventBus().call(new PostMotionEvent());
|
|
}
|
|
}
|
|
|
|
@Inject(method = "close", desc = "()V", at = @At(At.Type.HEAD))
|
|
public static void onClose(Minecraft minecraft, CallbackInfo callbackInfo) {
|
|
ZenClient.getInstance().shutdown();
|
|
}
|
|
|
|
@Inject(method = "setLevel", desc = "(Lnet/minecraft/client/multiplayer/ClientLevel;)V")
|
|
public static void onSetLevel(ClientLevel level, CallbackInfo callbackInfo) {
|
|
if (ZenClient.isReady()) {
|
|
ZenClient.getInstance().getEventBus().call(new DisconnectEvent());
|
|
}
|
|
}
|
|
|
|
@Inject(
|
|
method = "handleKeybinds",
|
|
desc = "()V",
|
|
at = @At(value = At.Type.BEFORE_INVOKE, method = "net/minecraft/client/player/LocalPlayer/isUsingItem", desc = "()Z"),
|
|
slice = @Slice(startIndex = 1, endIndex = 1)
|
|
)
|
|
public static void onHandleKeybinds(Minecraft minecraft, CallbackInfo callbackInfo) {
|
|
if (ZenClient.isReady()) {
|
|
PreMotionEvent event = new PreMotionEvent();
|
|
ZenClient.getInstance().getEventBus().call(event);
|
|
if (event.isCancelled()) {
|
|
callbackInfo.cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Inject(method = "startUseItem", desc = "()V", at = @At(At.Type.HEAD))
|
|
public static void onStartUseItemPre(Minecraft minecraft, CallbackInfo callbackInfo) {
|
|
if (FastUse.isBlocking(minecraft)) {
|
|
savedHitResult = minecraft.hitResult;
|
|
Vec3 location = savedHitResult.getLocation();
|
|
minecraft.hitResult = BlockHitResult.miss(location, Direction.DOWN, BlockPos.containing(location));
|
|
}
|
|
}
|
|
|
|
@Inject(method = "startUseItem", desc = "()V", at = @At(At.Type.TAIL))
|
|
public static void onStartUseItemPost(Minecraft minecraft, CallbackInfo callbackInfo) {
|
|
if (savedHitResult != null) {
|
|
minecraft.hitResult = savedHitResult;
|
|
savedHitResult = null;
|
|
}
|
|
}
|
|
|
|
@WrapInvoke(
|
|
method = "shouldEntityAppearGlowing",
|
|
desc = "(Lnet/minecraft/world/entity/Entity;)Z",
|
|
target = "net/minecraft/world/entity/Entity/isCurrentlyGlowing",
|
|
targetDesc = "()Z"
|
|
)
|
|
public static boolean onShouldEntityGlow(Minecraft minecraft, Entity entity, Invocation<Entity, Boolean> original) throws Exception {
|
|
if (ZenClient.isReady()
|
|
&& ZenClient.instance.getModuleManager() != null
|
|
&& ESP.INSTANCE != null
|
|
&& ESP.INSTANCE.isGlowing(entity)) {
|
|
return true;
|
|
}
|
|
return original.call();
|
|
}
|
|
|
|
@Inject(method = "resizeDisplay", desc = "()V", at = @At(At.Type.TAIL))
|
|
public static void onResizeDisplay(Minecraft minecraft, CallbackInfo callbackInfo) {
|
|
Renderer.setGuiScaleVerified((float) minecraft.getWindow().getGuiScale());
|
|
}
|
|
}
|