Add XLZen category: AntiArrow, NoFireOverlay, log collector, target health HUD
Build Loader / build (push) Canceled after 0s

- ESP: add Chams mode rendering the full entity model through walls
  (BufferUploaderPatch drops the depth test at the draw funnel, so all
  layers - sheep fur, armor, held items - stay identical to the real entity)
- AntiArrow (XLZen): predict arrow trajectories and avoid them via
  position spoof on outgoing move packets or real dodge impulses
- NoFireOverlay (XLZen): remove the first-person burning screen effect
- LogCollector + .log command: capture all log4j output, server chat,
  damage/disconnect events and session info; export as zip together with
  latest.log, native injection logs and recent crash reports
- Watermark: target health panel for the player currently in combat
- New XLZen module category wired into both click GUIs
这个提交包含在:
Administrator committed 2026-09-15 05:34:26 +08:00
1 父节点 0da7d49fd6
当前提交 e5973969aa
16 file changed
+881 -4

No files matched your search

+9
查看文件
@@ -18,6 +18,7 @@ import shit.zen.event.EventBus;
import shit.zen.event.EventTarget;
import shit.zen.event.impl.TickEvent;
import shit.zen.gui.IntroAnimation;
import shit.zen.log.LogCollector;
import shit.zen.manager.CommandManager;
import shit.zen.manager.ConfigManager;
import shit.zen.manager.GroupManager;
@@ -27,6 +28,7 @@ import shit.zen.manager.ModuleManager;
import shit.zen.manager.TargetManager;
import shit.zen.patch.BlockOcclusionCachePatch;
import shit.zen.patch.BlockPatch;
import shit.zen.patch.BufferUploaderPatch;
import shit.zen.patch.ChatScreenPatch;
import shit.zen.patch.ClientLevelPatch;
import shit.zen.patch.ConnectionPatch;
@@ -48,6 +50,7 @@ import shit.zen.patch.MinecraftPatch;
import shit.zen.patch.PacketUtilsPatch;
import shit.zen.patch.PlayerPatch;
import shit.zen.patch.PlayerTabOverlayPatch;
import shit.zen.patch.ScreenEffectRendererPatch;
import shit.zen.asm.Bootstrap;
import shit.zen.utils.rotation.RotationHandler;
@@ -76,6 +79,7 @@ public class ZenClient extends ClientBase {
private HudManager hudManager;
private LagManager lagManager;
private TargetManager targetManager;
private LogCollector logCollector;
private int reconnectAttempts;
public ZenClient() {
@@ -96,6 +100,9 @@ public class ZenClient extends ClientBase {
this.eventBus = new EventBus();
this.rotationHandler = new RotationHandler();
this.eventBus.register(this.rotationHandler);
this.logCollector = new LogCollector();
this.logCollector.install();
this.eventBus.register(this.logCollector);
this.moduleManager = new ModuleManager();
this.groupManager = new GroupManager();
this.hudManager = new HudManager();
@@ -212,6 +219,8 @@ public class ZenClient extends ClientBase {
PatchRegistry.register(ItemInHandLayerPatch.class);
PatchRegistry.register(HumanoidModelPatch.class);
PatchRegistry.register(LivingEntityRendererPatch.class);
PatchRegistry.register(BufferUploaderPatch.class);
PatchRegistry.register(ScreenEffectRendererPatch.class);
PatchRegistry.register(ItemPatch.class);
PatchRegistry.register(PlayerTabOverlayPatch.class);
PatchRegistry.register(FriendlyByteBufPatch.class);
@@ -0,0 +1,51 @@
package shit.zen.command.impl;
import java.io.File;
import shit.zen.ClientBase;
import shit.zen.command.Command;
import shit.zen.log.LogCollector;
import shit.zen.utils.misc.ChatUtil;
public class LogCommand extends Command {
public LogCommand() {
super("log", new String[]{"logs"});
}
@Override
public void onCommand(String[] args) {
if (args.length != 1) {
ChatUtil.print("Usage: log export/clear/count/folder");
return;
}
switch (args[0].toLowerCase()) {
case "export" -> {
if (ClientBase.mc != null) {
// FileDialogUtil blocks with a modal dialog; keep it off the netty thread.
ClientBase.mc.execute(() -> LogCollector.INSTANCE.exportAndReport());
}
}
case "clear" -> {
LogCollector.INSTANCE.clear();
ChatUtil.print("\u00a7a\u5df2\u6e05\u7a7a\u5df2\u6536\u96c6\u7684\u65e5\u5fd7");
}
case "count" ->
ChatUtil.print("\u5df2\u6536\u96c6 " + LogCollector.INSTANCE.getCount() + " \u6761\u65e5\u5fd7");
case "folder" -> {
try {
Runtime.getRuntime().exec("explorer "
+ new File(ClientBase.mc.gameDirectory, "logs").getAbsolutePath());
} catch (Exception ignored) {
}
}
default -> ChatUtil.print("Usage: log export/clear/count/folder");
}
}
@Override
public String[] onTab(String[] args) {
if (args.length == 0) {
return new String[]{"export", "clear", "count", "folder"};
}
return new String[0];
}
}
@@ -8,6 +8,8 @@ import shit.zen.ZenClient;
import shit.zen.gui.legacy.ModuleButton;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
import shit.zen.render.FontPresets;
import shit.zen.render.FontRenderer;
import shit.zen.render.FontStore;
import shit.zen.utils.render.RenderUtil;
@@ -92,10 +94,17 @@ public class CategoryPanel {
case EXPLOIT -> 'e';
case WORLD -> 'f';
case MISC -> 'g';
case XLZEN -> '\uE8B8';
case GROUPS -> 'h';
});
float iconY = (float)this.y + ((float)this.rowHeight / 2.0f - FontStore.ICON_30.getFontHeight() / 2.0f) + 3.0f;
FontStore.ICON_30.drawStringWithShadow(guiGraphics.pose(), iconChar, this.x + 4, iconY, -1);
if (this.category == Category.XLZEN) {
FontRenderer materialFont = FontPresets.materialIcons(30.0f);
float iconY = (float)this.y + ((float)this.rowHeight / 2.0f - materialFont.getFont().getFontHeight() / 2.0f) + 3.0f;
materialFont.getFont().drawStringWithShadow(guiGraphics.pose(), iconChar, this.x + 4, iconY, -1);
} else {
float iconY = (float)this.y + ((float)this.rowHeight / 2.0f - FontStore.ICON_30.getFontHeight() / 2.0f) + 3.0f;
FontStore.ICON_30.drawStringWithShadow(guiGraphics.pose(), iconChar, this.x + 4, iconY, -1);
}
float labelY = (float)this.y + ((float)this.rowHeight / 2.0f - FontStore.OPENSANS_18.getFontHeight() / 2.0f) - 0.5f;
FontStore.OPENSANS_18.drawStringWithShadow(guiGraphics.pose(), this.category.displayName, this.x + this.rowHeight + 4, labelY, -1);
for (ModuleButton moduleButton : this.moduleButtons) {
@@ -157,6 +157,7 @@ extends ClientBase {
CATEGORY_ICONS.put(Category.EXPLOIT, "\uE894");
CATEGORY_ICONS.put(Category.WORLD, "\uE5D3");
CATEGORY_ICONS.put(Category.MISC, "\uE24D");
CATEGORY_ICONS.put(Category.XLZEN, "\uE8B8");
CATEGORY_ICONS.put(Category.GROUPS, "\uE2C7");
}
}
@@ -0,0 +1,125 @@
package shit.zen.hud;
import java.awt.Color;
import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import shit.zen.ClientBase;
import shit.zen.event.impl.Render2DEvent;
import shit.zen.modules.impl.combat.KillAura;
import shit.zen.render.DrawContext;
import shit.zen.render.FontPresets;
import shit.zen.render.FontRenderer;
import shit.zen.render.Paint;
import shit.zen.render.Renderer;
import shit.zen.render.RoundedRectangle;
/**
* Watermark companion: shows the health of the player currently in combat
* with the user - either the one the user is attacking (KillAura target or
* the victim of the user's hits) or the one attacking the user. Combat
* partners are learned from ClientboundDamageEventPacket broadcasts, so it
* works in multiplayer for both directions.
*/
public class TargetHealthHud extends ClientBase {
private static final long COMBAT_TIMEOUT = 5000L;
private static final long FADE_MS = 800L;
private static final float PAD = 7.0f;
private static final float BAR_HEIGHT = 3.0f;
private final FontRenderer nameFont = FontPresets.poppinsMedium(12.0f);
private final FontRenderer hpFont = FontPresets.poppinsMedium(10.0f);
private Player target;
private long lastCombat;
public void onDamagePacket(ClientboundDamageEventPacket packet) {
if (mc == null || mc.level == null || mc.player == null) {
return;
}
int victimId = packet.entityId();
int causeId = packet.sourceCauseId();
if (victimId == mc.player.getId()) {
Entity attacker = mc.level.getEntity(causeId);
if (attacker instanceof Player player && player != mc.player) {
this.note(player);
}
} else if (causeId == mc.player.getId()) {
Entity victim = mc.level.getEntity(victimId);
if (victim instanceof Player player && victim != mc.player) {
this.note(player);
}
}
}
public void note(Entity entity) {
if (entity instanceof Player player) {
this.target = player;
this.lastCombat = System.currentTimeMillis();
}
}
public void render(Render2DEvent event) {
if (mc == null || mc.player == null || mc.level == null) {
return;
}
if (KillAura.aimingTarget instanceof Player auraTarget && auraTarget != mc.player) {
this.note(auraTarget);
}
Player target = this.target;
if (target == null) {
return;
}
long since = System.currentTimeMillis() - this.lastCombat;
if (since > COMBAT_TIMEOUT || !target.isAlive() || target.isRemoved()
|| mc.player.distanceToSqr(target) > 6400.0) {
this.target = null;
return;
}
float alpha = since > COMBAT_TIMEOUT - FADE_MS ? (COMBAT_TIMEOUT - since) / (float) FADE_MS : 1.0f;
float hp = Mth.clamp(target.getHealth(), 0.0f, target.getMaxHealth());
float max = Math.max(1.0f, target.getMaxHealth());
float ratio = Mth.clamp(hp / max, 0.0f, 1.0f);
String name = target.getName().getString();
String hpText = String.format("%.1f / %.0f", hp, max);
float nameWidth = this.nameFont.getWidth(name);
float hpWidth = this.hpFont.getWidth(hpText);
float lineHeight = this.nameFont.getMetrics().capHeight();
float width = PAD * 2.0f + nameWidth + 8.0f + hpWidth;
float height = PAD + lineHeight + 5.0f + BAR_HEIGHT + PAD;
float x = mc.getWindow().getGuiScaledWidth() - width - 8.0f;
float y = 8.0f;
float barY = y + PAD + lineHeight + 5.0f;
float barWidth = width - PAD * 2.0f;
Color fill = Color.getHSBColor(Math.max(0.0f, ratio) / 3.0f, 0.9f, 1.0f);
int bgColor = new Color(0, 0, 0, (int) (140 * alpha)).getRGB();
int textColor = new Color(255, 255, 255, (int) (255 * alpha)).getRGB();
int shadowColor = new Color(0, 0, 0, (int) (120 * alpha)).getRGB();
int barBgColor = new Color(60, 60, 60, (int) (200 * alpha)).getRGB();
int barColor = new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), (int) (255 * alpha)).getRGB();
float hpX = x + width - PAD - hpWidth;
float drawX = x;
float drawY = y + PAD;
float textY = drawY;
float barFillWidth = barWidth * ratio;
Renderer.renderConsumer(drawContext -> {
try (Paint paint = new Paint()) {
paint.setColor(bgColor);
drawContext.drawRoundedRect(RoundedRectangle.ofXYWHR(drawX, drawY - PAD + 2.0f, width, height, 4.0f), paint);
paint.setColor(barBgColor);
drawContext.drawRoundedRect(RoundedRectangle.ofXYWHR(drawX + PAD, barY, barWidth, BAR_HEIGHT, BAR_HEIGHT / 2.0f), paint);
if (barFillWidth > 0.5f) {
paint.setColor(barColor);
drawContext.drawRoundedRect(RoundedRectangle.ofXYWHR(drawX + PAD, barY, barFillWidth, BAR_HEIGHT, BAR_HEIGHT / 2.0f), paint);
}
paint.setColor(shadowColor);
drawContext.drawString(name, drawX + PAD + 0.5f, textY + 0.5f, this.nameFont, paint);
drawContext.drawString(hpText, hpX + 0.5f, textY + 1.0f, this.hpFont, paint);
paint.setColor(textColor);
drawContext.drawString(name, drawX + PAD, textY, this.nameFont, paint);
drawContext.drawString(hpText, hpX, textY + 0.5f, this.hpFont, paint);
}
});
}
}
@@ -0,0 +1,356 @@
package shit.zen.log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundDisconnectPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Property;
import shit.zen.ClientBase;
import shit.zen.ZenClient;
import shit.zen.event.EventTarget;
import shit.zen.event.impl.PacketEvent;
import shit.zen.event.impl.TickEvent;
import shit.zen.event.impl.WorldChangeEvent;
import shit.zen.utils.misc.ChatUtil;
import shit.zen.utils.misc.FileDialogUtil;
/**
* Collects every log stream the client can reach and bundles them into one
* exportable archive:
*
* - ALL log4j2 output in real time (Minecraft, Forge, every mod, and the
* client's own "Client"/shit.zen loggers) via a root-logger appender,
* - inbound server traffic that has no log4j footprint: player chat,
* system broadcasts (join/leave/deaths), disconnect reasons,
* - session/environment info,
* and at export time additionally sweeps the on-disk artifacts:
* latest.log, the native injection logs (%TEMP%/openzen-*.log) and recent
* crash reports.
*/
public class LogCollector {
public static LogCollector INSTANCE;
private static final String APPENDER_NAME = "OpenZenLogCollector";
private static final int MAX_ENTRIES = 20000;
private static final int MAX_CRASH_REPORTS = 5;
private static final int MAX_STACK_CHARS = 4096;
private static final Object LOCK = new Object();
// re-entrancy guard: anything logged while capturing must not re-enter
private static final ThreadLocal<Boolean> CAPTURING = ThreadLocal.withInitial(() -> Boolean.FALSE);
private final ArrayDeque<Line> lines = new ArrayDeque<>();
private volatile boolean installed;
private long sessionStart;
private boolean environmentLogged;
public LogCollector() {
INSTANCE = this;
}
private static final class Line {
final long time;
final String source;
final String message;
Line(long time, String source, String message) {
this.time = time;
this.source = source;
this.message = message;
}
}
public void install() {
if (this.installed) {
return;
}
this.sessionStart = System.currentTimeMillis();
try {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration configuration = context.getConfiguration();
if (configuration.getAppender(APPENDER_NAME) == null) {
AbstractAppender appender = new AbstractAppender(APPENDER_NAME, null, null, true, Property.EMPTY_ARRAY) {
@Override
public void append(LogEvent event) {
LogCollector.this.captureLog(event);
}
};
appender.start();
configuration.addAppender(appender);
configuration.getRootLogger().addAppender(appender, Level.ALL, null);
context.updateLoggers();
}
this.installed = true;
} catch (Throwable ignored) {
// never re-log here or the appender would feed back into itself
}
this.record("COLLECTOR", "log collector installed, capturing all log4j output");
}
private void captureLog(LogEvent event) {
if (CAPTURING.get()) {
return;
}
CAPTURING.set(Boolean.TRUE);
try {
StringBuilder builder = new StringBuilder(128);
builder.append('[').append(event.getLevel()).append("] ");
String loggerName = event.getLoggerName();
builder.append(loggerName != null ? loggerName : "?").append(": ");
org.apache.logging.log4j.message.Message message = event.getMessage();
builder.append(message != null ? message.getFormattedMessage() : "");
Throwable thrown = event.getThrown();
if (thrown != null) {
java.io.StringWriter writer = new java.io.StringWriter();
thrown.printStackTrace(new java.io.PrintWriter(writer));
String stack = writer.toString();
if (stack.length() > MAX_STACK_CHARS) {
stack = stack.substring(0, MAX_STACK_CHARS) + "\n... (truncated)";
}
builder.append('\n').append(stack);
}
this.record("GAME", builder.toString());
} catch (Throwable ignored) {
} finally {
CAPTURING.set(Boolean.FALSE);
}
}
private void record(String source, String message) {
synchronized (LOCK) {
if (this.lines.size() >= MAX_ENTRIES) {
this.lines.pollFirst();
}
this.lines.addLast(new Line(System.currentTimeMillis(), source, message));
}
}
public int getCount() {
synchronized (LOCK) {
return this.lines.size();
}
}
public void clear() {
synchronized (LOCK) {
this.lines.clear();
}
}
@EventTarget
public void onPacket(PacketEvent event) {
if (!event.isIncoming()) {
return;
}
try {
Packet<?> packet = event.getPacket();
if (packet instanceof ClientboundPlayerChatPacket chat) {
Component unsigned = chat.unsignedContent();
String text = unsigned != null ? unsigned.getString() : chat.body().content();
this.record("CHAT", this.resolveName(chat.sender()) + ": " + text);
} else if (packet instanceof ClientboundSystemChatPacket system) {
String text = system.content().getString();
if (!text.isEmpty()) {
this.record("SERVER", text);
}
} else if (packet instanceof ClientboundDisconnectPacket disconnect) {
this.record("NETWORK", "disconnected: " + disconnect.getReason().getString());
}
} catch (Throwable ignored) {
}
}
@EventTarget
public void onWorldChange(WorldChangeEvent event) {
this.record("SESSION", "=== world/server changed ===");
this.environmentLogged = false;
}
@EventTarget
public void onTick(TickEvent event) {
if (this.environmentLogged || !ZenClient.isReady()) {
return;
}
this.environmentLogged = true;
this.record("SESSION", "=== joined world ===");
this.recordEnvironment();
}
private void recordEnvironment() {
try {
this.record("SESSION", "client: " + ZenClient.CLIENT_NAME + " v" + ZenClient.VERSION
+ ", user: " + ZenClient.username);
this.record("SESSION", "minecraft: " + ClientBase.mc.getLaunchedVersion()
+ ", os: " + System.getProperty("os.name"));
this.record("SESSION", "game dir: " + ClientBase.mc.gameDirectory.getAbsolutePath());
ServerData server = ClientBase.mc.getCurrentServer();
if (server != null) {
this.record("SESSION", "server: " + server.name + " (" + server.ip + ")");
} else {
this.record("SESSION", "server: (singleplayer)");
}
} catch (Throwable ignored) {
}
}
private String resolveName(UUID sender) {
try {
ClientPacketListener connection = ClientBase.mc.getConnection();
if (connection != null) {
PlayerInfo info = connection.getPlayerInfo(sender);
if (info != null && info.getProfile() != null && info.getProfile().getName() != null) {
return info.getProfile().getName();
}
}
} catch (Throwable ignored) {
}
return sender.toString();
}
/**
* Opens a save dialog and writes the archive; falls back to an automatic
* path under gameDir/logs when the dialog is unavailable or cancelled.
*/
public void exportAndReport() {
String stamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String defaultName = "zen-logs-" + stamp + ".zip";
File target = FileDialogUtil.saveZip("\u5bfc\u51fa\u65e5\u5fd7", defaultName);
if (target == null) {
target = new File(new File(ClientBase.mc.gameDirectory, "logs"), defaultName);
}
try {
this.export(target);
ChatUtil.print("\u00a7a\u65e5\u5fd7\u5df2\u5bfc\u51fa: " + target.getAbsolutePath());
} catch (Throwable throwable) {
ChatUtil.print("\u00a7c\u65e5\u5fd7\u5bfc\u51fa\u5931\u8d25: " + throwable);
}
}
void export(File target) throws IOException {
File parent = target.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw new IOException("cannot create " + parent);
}
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(target))) {
this.writeCollected(zip);
this.writeSessionInfo(zip);
this.writeExternalFile(zip, new File(ClientBase.mc.gameDirectory, "logs/latest.log"), "latest.log");
for (File nativeLog : this.listNativeLogs()) {
this.writeExternalFile(zip, nativeLog, "native/" + nativeLog.getName());
}
for (File crashReport : this.listCrashReports()) {
this.writeExternalFile(zip, crashReport, "crash-reports/" + crashReport.getName());
}
}
}
private void writeCollected(ZipOutputStream zip) throws IOException {
List<Line> snapshot;
synchronized (LOCK) {
snapshot = new ArrayList<>(this.lines);
}
SimpleDateFormat full = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
StringBuilder builder = new StringBuilder(64 + snapshot.size() * 48);
builder.append("# Zen log collector - ").append(snapshot.size()).append(" entries\n");
for (Line line : snapshot) {
builder.append('[').append(full.format(new Date(line.time))).append("] [")
.append(line.source).append("] ").append(line.message).append('\n');
}
zip.putNextEntry(new ZipEntry("zen-collected.log"));
zip.write(builder.toString().getBytes(StandardCharsets.UTF_8));
zip.closeEntry();
}
private void writeSessionInfo(ZipOutputStream zip) throws IOException {
StringBuilder builder = new StringBuilder(512);
builder.append("client: ").append(ZenClient.CLIENT_NAME).append(" v").append(ZenClient.VERSION).append('\n');
builder.append("user: ").append(ZenClient.username).append('\n');
try {
builder.append("minecraft: ").append(ClientBase.mc.getLaunchedVersion()).append('\n');
builder.append("game dir: ").append(ClientBase.mc.gameDirectory.getAbsolutePath()).append('\n');
ServerData server = ClientBase.mc.getCurrentServer();
builder.append("server: ")
.append(server != null ? server.name + " (" + server.ip + ")" : "(singleplayer)").append('\n');
} catch (Throwable ignored) {
}
builder.append("os: ").append(System.getProperty("os.name")).append('\n');
builder.append("java: ").append(System.getProperty("java.version")).append('\n');
builder.append("session start: ").append(new Date(this.sessionStart)).append('\n');
builder.append("exported at: ").append(new Date()).append('\n');
builder.append("collected entries: ").append(this.getCount()).append('\n');
zip.putNextEntry(new ZipEntry("session-info.txt"));
zip.write(builder.toString().getBytes(StandardCharsets.UTF_8));
zip.closeEntry();
}
private List<File> listNativeLogs() {
List<File> result = new ArrayList<>();
try {
File tmp = new File(System.getProperty("java.io.tmpdir", "."));
File[] files = tmp.listFiles((dir, name) -> name.startsWith("openzen-") && name.endsWith(".log"));
if (files != null) {
result.addAll(Arrays.asList(files));
}
} catch (Throwable ignored) {
}
return result;
}
private List<File> listCrashReports() {
List<File> result = new ArrayList<>();
try {
File dir = new File(ClientBase.mc.gameDirectory, "crash-reports");
File[] files = dir.listFiles((dir1, name) -> name.endsWith(".txt"));
if (files != null) {
result.addAll(Arrays.asList(files));
result.sort(Comparator.comparingLong(File::lastModified).reversed());
while (result.size() > MAX_CRASH_REPORTS) {
result.remove(result.size() - 1);
}
}
} catch (Throwable ignored) {
}
return result;
}
private void writeExternalFile(ZipOutputStream zip, File file, String entryName) throws IOException {
if (file == null || !file.isFile()) {
return;
}
byte[] buffer = new byte[8192];
zip.putNextEntry(new ZipEntry(entryName));
try (InputStream input = new FileInputStream(file)) {
int read;
while ((read = input.read(buffer)) != -1) {
zip.write(buffer, 0, read);
}
} catch (Throwable ignored) {
}
zip.closeEntry();
}
}
@@ -7,6 +7,7 @@ import shit.zen.command.Command;
import shit.zen.command.impl.BindCommand;
import shit.zen.command.impl.ConfigCommand;
import shit.zen.command.impl.LanguageCommand;
import shit.zen.command.impl.LogCommand;
import shit.zen.command.impl.ToggleCommand;
import shit.zen.event.impl.ChatEvent;
import shit.zen.utils.misc.ChatUtil;
@@ -24,6 +25,7 @@ public class CommandManager {
this.registerCommand(new BindCommand());
this.registerCommand(new ConfigCommand());
this.registerCommand(new LanguageCommand());
this.registerCommand(new LogCommand());
this.registerCommand(new ToggleCommand());
}
@@ -11,6 +11,7 @@ import shit.zen.event.impl.KeyEvent;
import shit.zen.exception.ModuleNotFoundException;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
import shit.zen.modules.impl.combat.AntiArrow;
import shit.zen.modules.impl.combat.AntiBots;
import shit.zen.modules.impl.combat.AntiFireball;
import shit.zen.modules.impl.combat.AntiKB;
@@ -62,6 +63,7 @@ import shit.zen.modules.impl.render.Interface;
import shit.zen.modules.impl.render.ItemTags;
import shit.zen.modules.impl.render.NameProtect;
import shit.zen.modules.impl.render.NameTags;
import shit.zen.modules.impl.render.NoFireOverlay;
import shit.zen.modules.impl.render.NoHurtCam;
import shit.zen.modules.impl.render.OldHitting;
import shit.zen.modules.impl.render.Projectiles;
@@ -84,6 +86,7 @@ public class ModuleManager extends ClientBase {
public void initModules() {
this.register(new AntiBots());
this.register(new AntiArrow());
this.register(new AntiFireball());
this.register(new AntiKB());
this.register(new AutoOffHand());
@@ -139,6 +142,7 @@ public class ModuleManager extends ClientBase {
this.register(new ItemTags());
this.register(new NameProtect());
this.register(new NameTags());
this.register(new NoFireOverlay());
this.register(new NoHurtCam());
this.register(new OldHitting());
this.register(new Projectiles());
@@ -8,6 +8,7 @@ public enum Category {
EXPLOIT("World"),
WORLD("Misc"),
MISC("Ghost"),
XLZEN("XLZen"),
GROUPS("Groups");
public String displayName;
@@ -0,0 +1,139 @@
package shit.zen.modules.impl.combat;
import java.util.Optional;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import shit.zen.event.impl.MotionEvent;
import shit.zen.event.EventTarget;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
import shit.zen.settings.impl.BooleanSetting;
import shit.zen.settings.impl.ModeSetting;
import shit.zen.settings.impl.NumberSetting;
/**
* Prevents bow arrows from hitting the local player.
*
* The arrow/entity collision itself is simulated server-side (AbstractArrow
* sweeps its per-tick movement segment against the server-side player
* bounding box), so a client cannot cancel a hit directly. What the client
* DOES control is where the server thinks the player is: the player hitbox
* position is taken verbatim from our ServerboundMovePlayerPackets.
*
* Two strategies:
* - Spoof: while a simulated arrow trajectory would intersect the player,
* offset the reported x/z sideways (perpendicular to the arrow's path)
* inside the MotionEvent that LocalPlayerPatch routes into sendPosition.
* The local player never moves; the server-side hitbox does.
* - Dodge: apply a real horizontal velocity impulse perpendicular to the
* arrow's path so the player physically steps out of the way.
*
* Trajectory simulation mirrors vanilla AbstractArrow.tick(): per tick
* pos += vel, then vel *= 0.99 (air drag) and vel.y -= 0.05 (gravity), with
* block collisions truncating the segment, and entity hits tested against
* the player box inflated by 0.3 (ProjectileUtil.getEntityHitResult margin).
*/
public class AntiArrow extends Module {
public static AntiArrow INSTANCE;
private final ModeSetting modeSetting = new ModeSetting("Mode", "Spoof", "Dodge").withDefault("Spoof");
private final NumberSetting rangeSetting = new NumberSetting("Range", 32.0, 8.0, 64.0, 1.0);
private final NumberSetting lookaheadSetting = new NumberSetting("Lookahead", 20.0, 5.0, 60.0, 1.0);
private final NumberSetting offsetSetting = new NumberSetting("Offset", 0.9, 0.5, 3.0, 0.1,
() -> this.modeSetting.is("Spoof"));
private final NumberSetting speedSetting = new NumberSetting("Speed", 0.3, 0.1, 1.0, 0.05,
() -> this.modeSetting.is("Dodge"));
private final BooleanSetting ownArrowsSetting = new BooleanSetting("Own Arrows", false);
public AntiArrow() {
super("AntiArrow", Category.XLZEN);
INSTANCE = this;
}
private record Threat(AbstractArrow arrow, int impactTicks, Vec3 dodgeDir) {
}
@EventTarget
public void onMotion(MotionEvent event) {
// The first dispatch (isPost() == true) happens at the head of
// LocalPlayer.sendPosition, before the movement packets are built;
// mutated x/z here flow straight into the outgoing packets.
if (event.isPre()) return;
if (mc.player == null || mc.level == null) return;
Threat threat = this.findThreat();
if (threat == null) return;
if (this.modeSetting.is("Dodge")) {
double speed = this.speedSetting.getValue().doubleValue();
Vec3 motion = mc.player.getDeltaMovement();
mc.player.setDeltaMovement(threat.dodgeDir().x * speed, motion.y, threat.dodgeDir().z * speed);
} else {
double offset = this.offsetSetting.getValue().doubleValue();
event.setX(event.getX() + threat.dodgeDir().x * offset);
event.setZ(event.getZ() + threat.dodgeDir().z * offset);
}
}
/**
* Simulates every nearby arrow's flight and returns the one that will hit
* the player soonest (within the lookahead window), or null if none.
*/
private Threat findThreat() {
double rangeSq = this.rangeSetting.getValue().doubleValue();
rangeSq *= rangeSq;
int lookahead = this.lookaheadSetting.getValue().intValue();
Threat best = null;
for (Entity entity : mc.level.entitiesForRendering()) {
if (!(entity instanceof AbstractArrow arrow)) continue;
if (!this.ownArrowsSetting.getValue() && arrow.getOwner() == mc.player) continue;
Vec3 vel = arrow.getDeltaMovement();
if (vel.lengthSqr() < 0.02) continue;
if (mc.player.distanceToSqr(arrow) > rangeSq) continue;
Vec3 pos = arrow.position();
AABB playerBox = mc.player.getBoundingBox().inflate(0.3);
Vec3 v = vel;
for (int t = 1; t <= lookahead; t++) {
Vec3 next = pos.add(v);
BlockHitResult blockHit = mc.level.clip(new ClipContext(pos, next,
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, arrow));
Vec3 end = blockHit.getType() != HitResult.Type.MISS ? blockHit.getLocation() : next;
if (playerBox.clip(pos, end).isPresent()) {
if (best == null || t < best.impactTicks()) {
best = new Threat(arrow, t, this.perpDodgeDir(pos, v));
}
break;
}
if (blockHit.getType() != HitResult.Type.MISS) break;
pos = next;
v = v.scale(0.99);
v = new Vec3(v.x, v.y - 0.05, v.z);
}
}
return best;
}
/**
* Dodge direction: perpendicular to the arrow's horizontal velocity, on
* whichever side of the flight line the player already is (shortest way
* out of the path).
*/
private Vec3 perpDodgeDir(Vec3 arrowPos, Vec3 vel) {
double dx = vel.x;
double dz = vel.z;
double len = Math.sqrt(dx * dx + dz * dz);
if (len < 1.0E-4) return new Vec3(1.0, 0.0, 0.0);
double px = -dz / len;
double pz = dx / len;
double side = (mc.player.getX() - arrowPos.x) * px + (mc.player.getZ() - arrowPos.z) * pz;
if (side < 0.0) {
px = -px;
pz = -pz;
}
return new Vec3(px, 0.0, pz);
}
}
@@ -13,6 +13,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
@@ -29,8 +32,10 @@ import shit.zen.event.impl.Render2DEvent;
import shit.zen.event.impl.RenderEvent;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
import shit.zen.patch.BufferUploaderPatch;
import shit.zen.settings.impl.BooleanSetting;
import shit.zen.settings.impl.ModeSetting;
import shit.zen.settings.impl.NumberSetting;
import shit.zen.utils.game.EntityUtil;
import shit.zen.utils.math.Vector2f;
import shit.zen.utils.render.ColorUtil;
@@ -48,7 +53,11 @@ public class ESP extends Module {
}
}
private final ModeSetting modeSetting = new ModeSetting("Mode", "Glow", "Outlined 2D").withDefault("Outlined 2D");
private final ModeSetting modeSetting = new ModeSetting("Mode", "Glow", "Outlined 2D", "Chams").withDefault("Chams");
private final ModeSetting chamsStyleSetting = new ModeSetting("Chams Style", "Texture", "Color").withDefault("Texture")
.withVisibility(() -> this.modeSetting.is("Chams"));
private final NumberSetting chamsOpacitySetting = new NumberSetting("Chams Opacity", 180.0, 30.0, 255.0, 1.0,
() -> this.modeSetting.is("Chams"));
private final BooleanSetting skeletonSetting = new BooleanSetting("Skeleton", false);
private final BooleanSetting playersSetting = new BooleanSetting("Players", true);
private final BooleanSetting mobsSetting = new BooleanSetting("Mobs", false);
@@ -107,7 +116,12 @@ public class ESP extends Module {
@EventTarget
public void onRender(RenderEvent renderEvent) {
if (mc.level == null || mc.player == null) return;
if (!"Outlined 2D".equals(this.modeSetting.getValue())) {
if (this.modeSetting.is("Chams")) {
this.entityBoxPositions.clear();
this.renderChams(renderEvent);
return;
}
if (!this.modeSetting.is("Outlined 2D")) {
this.entityBoxPositions.clear();
return;
}
@@ -152,6 +166,45 @@ public class ESP extends Module {
}
}
// Private immediate source for the chams pass only, so endBatch() here
// never flushes vanilla's pending geometry with depth disabled.
private final MultiBufferSource.BufferSource chamsBufferSource = MultiBufferSource.immediate(new BufferBuilder(256));
@SuppressWarnings({"rawtypes", "unchecked"})
private void renderChams(RenderEvent renderEvent) {
float partial = renderEvent.partialTick();
Vec3 camera = mc.gameRenderer.getMainCamera().getPosition();
PoseStack poseStack = renderEvent.poseStack();
float alpha = Mth.clamp(this.chamsOpacitySetting.getValue().floatValue() / 255.0f, 0.05f, 1.0f);
boolean colorStyle = this.chamsStyleSetting.is("Color");
for (Entity entity : mc.level.entitiesForRendering()) {
if (!(entity instanceof LivingEntity living)) continue;
if (!this.shouldShowEntity(entity) || !this.isInRange(entity)) continue;
EntityRenderer renderer = mc.getEntityRenderDispatcher().getRenderer(living);
Color tint = colorStyle && entity instanceof Player p ? ColorUtil.getPlayerColor(p) : Color.WHITE;
poseStack.pushPose();
poseStack.translate(living.getX() - camera.x, living.getY() - camera.y, living.getZ() - camera.z);
BufferUploaderPatch.chamsPass.set(Boolean.TRUE);
RenderSystem.setShaderColor(tint.getRed() / 255.0f, tint.getGreen() / 255.0f,
tint.getBlue() / 255.0f, alpha);
try {
// Full renderer path (model + every LayerRenderer: sheep fur,
// armor, held items, ...), so the ghost is identical to the
// real entity. BufferUploaderPatch drops the depth test for
// the flushed batches, making the whole pass visible through
// walls.
renderer.render(living, living.getViewYRot(partial), partial, poseStack, this.chamsBufferSource,
LevelRenderer.getLightColor(mc.level, living.blockPosition()));
this.chamsBufferSource.endBatch();
} finally {
BufferUploaderPatch.chamsPass.set(Boolean.FALSE);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
RenderSystem.enableDepthTest();
poseStack.popPose();
}
}
}
@EventTarget
public void onRender2D(Render2DEvent event) {
if (!"Outlined 2D".equals(this.modeSetting.getValue()) || this.entityBoxPositions.isEmpty()) return;
@@ -0,0 +1,13 @@
package shit.zen.modules.impl.render;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
public class NoFireOverlay extends Module {
public static NoFireOverlay INSTANCE;
public NoFireOverlay() {
super("NoFireOverlay", Category.XLZEN);
INSTANCE = this;
}
}
@@ -1,18 +1,27 @@
package shit.zen.modules.impl.render;
import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
import shit.zen.event.impl.EntityHurtEvent;
import shit.zen.event.impl.GlRenderEvent;
import shit.zen.event.impl.PacketEvent;
import shit.zen.event.impl.Render2DEvent;
import shit.zen.hud.DynamicIsland;
import shit.zen.hud.NeverloseWatermark;
import shit.zen.hud.TargetHealthHud;
import shit.zen.modules.Category;
import shit.zen.modules.Module;
import shit.zen.settings.impl.BooleanSetting;
import shit.zen.settings.impl.ModeSetting;
import shit.zen.event.EventTarget;
public class Watermark extends Module {
final ModeSetting styleSetting = new ModeSetting("Style", "Neverlose", "DynamicIsland").withDefault("DynamicIsland");
private final BooleanSetting targetHealthSetting = new BooleanSetting("Target Health", true);
private final DynamicIsland dynamicIsland = new DynamicIsland();
private final NeverloseWatermark neverloseWatermark = new NeverloseWatermark();
private final TargetHealthHud targetHealthHud = new TargetHealthHud();
public Watermark() {
super("Watermark", Category.RENDER);
@@ -31,6 +40,9 @@ public class Watermark extends Module {
this.dynamicIsland.onRender2D(render2DEvent);
break;
}
if (this.targetHealthSetting.getValue()) {
this.targetHealthHud.render(render2DEvent);
}
}
@EventTarget
@@ -42,4 +54,32 @@ public class Watermark extends Module {
this.neverloseWatermark.onGlRender(glRenderEvent);
}
}
@EventTarget
public void onPacket(PacketEvent event) {
if (!this.isEnabled() || !event.isIncoming()) {
return;
}
if (event.getPacket() instanceof ClientboundDamageEventPacket packet) {
this.targetHealthHud.onDamagePacket(packet);
}
}
@EventTarget
public void onEntityHurt(EntityHurtEvent event) {
if (!this.isEnabled()) {
return;
}
DamageSource source = event.damageSource();
if (source == null) {
return;
}
if (event.entity() == mc.player) {
if (source.getEntity() instanceof Player attacker && attacker != mc.player) {
this.targetHealthHud.note(attacker);
}
} else if (source.getEntity() == mc.player && event.entity() instanceof Player) {
this.targetHealthHud.note(event.entity());
}
}
}
@@ -0,0 +1,37 @@
package shit.zen.patch;
import asm.patchify.annotation.At;
import asm.patchify.annotation.Inject;
import asm.patchify.annotation.Patch;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
@Patch(BufferUploader.class)
public class BufferUploaderPatch {
// Set only around ZenClient's own through-wall (chams) pass, so the depth
// overrides below apply exclusively to the batches flushed by that pass.
public static final ThreadLocal<Boolean> chamsPass = ThreadLocal.withInitial(() -> Boolean.FALSE);
// drawWithShader is the single funnel every buffered draw goes through and
// runs AFTER RenderType.setupRenderState() / BEFORE the GL draw call, so an
// override here wins over whatever depth state the render type's shards
// just set (a DepthTestStateShard with GL_ALWAYS is a no-op in 1.20.1, and
// any per-call depthFunc tweak before the flush would be clobbered).
@Inject(method = "drawWithShader", desc = "(Lcom/mojang/blaze3d/vertex/BufferBuilder$RenderedBuffer;)V", at = @At(At.Type.HEAD))
public static void onDrawWithShaderPre(BufferBuilder.RenderedBuffer renderedBuffer, CallbackInfo callbackInfo) {
if (chamsPass.get()) {
RenderSystem.disableDepthTest();
GlStateManager._depthMask(false);
}
}
@Inject(method = "drawWithShader", desc = "(Lcom/mojang/blaze3d/vertex/BufferBuilder$RenderedBuffer;)V", at = @At(At.Type.TAIL))
public static void onDrawWithShaderPost(BufferBuilder.RenderedBuffer renderedBuffer, CallbackInfo callbackInfo) {
if (chamsPass.get()) {
GlStateManager._depthMask(true);
RenderSystem.enableDepthTest();
}
}
}
@@ -0,0 +1,22 @@
package shit.zen.patch;
import asm.patchify.annotation.Inject;
import asm.patchify.annotation.Patch;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ScreenEffectRenderer;
import shit.zen.modules.impl.render.NoFireOverlay;
@Patch(ScreenEffectRenderer.class)
public class ScreenEffectRendererPatch {
// renderFire draws the first-person burning flames over the screen
// (the animated block fire texture). Cancelling it only removes that
// overlay - the underwater / powder-snow passes in the same renderer
// and the actual fire status are untouched.
@Inject(method = "renderFire", desc = "(Lnet/minecraft/client/Minecraft;Lcom/mojang/blaze3d/vertex/PoseStack;)V")
public static void onRenderFire(Minecraft minecraft, PoseStack poseStack, CallbackInfo callbackInfo) {
if (NoFireOverlay.INSTANCE != null && NoFireOverlay.INSTANCE.isEnabled()) {
callbackInfo.cancel();
}
}
}
+15
查看文件
@@ -63,6 +63,7 @@ public final class Zh {
put("World", "世界");
put("Misc", "杂项");
put("Ghost", "幽灵");
put("XLZen", "XLZen");
// --- modules ---
put("AimAssist", "瞄准辅助");
@@ -258,6 +259,20 @@ public final class Zh {
put("Show Artifacts", "显示神器");
put("Show Health Bar", "显示血条");
put("Health Bar Position", "血条位置");
put("Glow", "发光");
put("Outlined 2D", "2D方框");
put("Chams", "穿墙模型");
put("Chams Style", "透视样式");
put("Chams Opacity", "透视不透明度");
put("Texture", "原材质");
put("Color", "纯色");
put("AntiArrow", "防箭");
put("Spoof", "位置偏移");
put("Dodge", "移动躲避");
put("Offset", "偏移距离");
put("Lookahead", "预判时长");
put("Own Arrows", "躲避自己的箭");
put("Target Health", "目标血量");
put("More Particles", "更多粒子");
put("Tracers", "射线");
put("Target ESP", "目标透视");