Add XLZen category: AntiArrow, NoFireOverlay, log collector, target health HUD
Build Loader / build (push) Canceled after 0s
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
这个提交包含在:
1 父节点
0da7d49fd6
当前提交
e5973969aa
16 file changed
+881
-4
No files matched your search
@@ -18,6 +18,7 @@ import shit.zen.event.EventBus;
|
|||||||
import shit.zen.event.EventTarget;
|
import shit.zen.event.EventTarget;
|
||||||
import shit.zen.event.impl.TickEvent;
|
import shit.zen.event.impl.TickEvent;
|
||||||
import shit.zen.gui.IntroAnimation;
|
import shit.zen.gui.IntroAnimation;
|
||||||
|
import shit.zen.log.LogCollector;
|
||||||
import shit.zen.manager.CommandManager;
|
import shit.zen.manager.CommandManager;
|
||||||
import shit.zen.manager.ConfigManager;
|
import shit.zen.manager.ConfigManager;
|
||||||
import shit.zen.manager.GroupManager;
|
import shit.zen.manager.GroupManager;
|
||||||
@@ -27,6 +28,7 @@ import shit.zen.manager.ModuleManager;
|
|||||||
import shit.zen.manager.TargetManager;
|
import shit.zen.manager.TargetManager;
|
||||||
import shit.zen.patch.BlockOcclusionCachePatch;
|
import shit.zen.patch.BlockOcclusionCachePatch;
|
||||||
import shit.zen.patch.BlockPatch;
|
import shit.zen.patch.BlockPatch;
|
||||||
|
import shit.zen.patch.BufferUploaderPatch;
|
||||||
import shit.zen.patch.ChatScreenPatch;
|
import shit.zen.patch.ChatScreenPatch;
|
||||||
import shit.zen.patch.ClientLevelPatch;
|
import shit.zen.patch.ClientLevelPatch;
|
||||||
import shit.zen.patch.ConnectionPatch;
|
import shit.zen.patch.ConnectionPatch;
|
||||||
@@ -48,6 +50,7 @@ import shit.zen.patch.MinecraftPatch;
|
|||||||
import shit.zen.patch.PacketUtilsPatch;
|
import shit.zen.patch.PacketUtilsPatch;
|
||||||
import shit.zen.patch.PlayerPatch;
|
import shit.zen.patch.PlayerPatch;
|
||||||
import shit.zen.patch.PlayerTabOverlayPatch;
|
import shit.zen.patch.PlayerTabOverlayPatch;
|
||||||
|
import shit.zen.patch.ScreenEffectRendererPatch;
|
||||||
import shit.zen.asm.Bootstrap;
|
import shit.zen.asm.Bootstrap;
|
||||||
import shit.zen.utils.rotation.RotationHandler;
|
import shit.zen.utils.rotation.RotationHandler;
|
||||||
|
|
||||||
@@ -76,6 +79,7 @@ public class ZenClient extends ClientBase {
|
|||||||
private HudManager hudManager;
|
private HudManager hudManager;
|
||||||
private LagManager lagManager;
|
private LagManager lagManager;
|
||||||
private TargetManager targetManager;
|
private TargetManager targetManager;
|
||||||
|
private LogCollector logCollector;
|
||||||
private int reconnectAttempts;
|
private int reconnectAttempts;
|
||||||
|
|
||||||
public ZenClient() {
|
public ZenClient() {
|
||||||
@@ -96,6 +100,9 @@ public class ZenClient extends ClientBase {
|
|||||||
this.eventBus = new EventBus();
|
this.eventBus = new EventBus();
|
||||||
this.rotationHandler = new RotationHandler();
|
this.rotationHandler = new RotationHandler();
|
||||||
this.eventBus.register(this.rotationHandler);
|
this.eventBus.register(this.rotationHandler);
|
||||||
|
this.logCollector = new LogCollector();
|
||||||
|
this.logCollector.install();
|
||||||
|
this.eventBus.register(this.logCollector);
|
||||||
this.moduleManager = new ModuleManager();
|
this.moduleManager = new ModuleManager();
|
||||||
this.groupManager = new GroupManager();
|
this.groupManager = new GroupManager();
|
||||||
this.hudManager = new HudManager();
|
this.hudManager = new HudManager();
|
||||||
@@ -212,6 +219,8 @@ public class ZenClient extends ClientBase {
|
|||||||
PatchRegistry.register(ItemInHandLayerPatch.class);
|
PatchRegistry.register(ItemInHandLayerPatch.class);
|
||||||
PatchRegistry.register(HumanoidModelPatch.class);
|
PatchRegistry.register(HumanoidModelPatch.class);
|
||||||
PatchRegistry.register(LivingEntityRendererPatch.class);
|
PatchRegistry.register(LivingEntityRendererPatch.class);
|
||||||
|
PatchRegistry.register(BufferUploaderPatch.class);
|
||||||
|
PatchRegistry.register(ScreenEffectRendererPatch.class);
|
||||||
PatchRegistry.register(ItemPatch.class);
|
PatchRegistry.register(ItemPatch.class);
|
||||||
PatchRegistry.register(PlayerTabOverlayPatch.class);
|
PatchRegistry.register(PlayerTabOverlayPatch.class);
|
||||||
PatchRegistry.register(FriendlyByteBufPatch.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.gui.legacy.ModuleButton;
|
||||||
import shit.zen.modules.Category;
|
import shit.zen.modules.Category;
|
||||||
import shit.zen.modules.Module;
|
import shit.zen.modules.Module;
|
||||||
|
import shit.zen.render.FontPresets;
|
||||||
|
import shit.zen.render.FontRenderer;
|
||||||
import shit.zen.render.FontStore;
|
import shit.zen.render.FontStore;
|
||||||
import shit.zen.utils.render.RenderUtil;
|
import shit.zen.utils.render.RenderUtil;
|
||||||
|
|
||||||
@@ -92,10 +94,17 @@ public class CategoryPanel {
|
|||||||
case EXPLOIT -> 'e';
|
case EXPLOIT -> 'e';
|
||||||
case WORLD -> 'f';
|
case WORLD -> 'f';
|
||||||
case MISC -> 'g';
|
case MISC -> 'g';
|
||||||
|
case XLZEN -> '\uE8B8';
|
||||||
case GROUPS -> 'h';
|
case GROUPS -> 'h';
|
||||||
});
|
});
|
||||||
float iconY = (float)this.y + ((float)this.rowHeight / 2.0f - FontStore.ICON_30.getFontHeight() / 2.0f) + 3.0f;
|
if (this.category == Category.XLZEN) {
|
||||||
FontStore.ICON_30.drawStringWithShadow(guiGraphics.pose(), iconChar, this.x + 4, iconY, -1);
|
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;
|
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);
|
FontStore.OPENSANS_18.drawStringWithShadow(guiGraphics.pose(), this.category.displayName, this.x + this.rowHeight + 4, labelY, -1);
|
||||||
for (ModuleButton moduleButton : this.moduleButtons) {
|
for (ModuleButton moduleButton : this.moduleButtons) {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ extends ClientBase {
|
|||||||
CATEGORY_ICONS.put(Category.EXPLOIT, "\uE894");
|
CATEGORY_ICONS.put(Category.EXPLOIT, "\uE894");
|
||||||
CATEGORY_ICONS.put(Category.WORLD, "\uE5D3");
|
CATEGORY_ICONS.put(Category.WORLD, "\uE5D3");
|
||||||
CATEGORY_ICONS.put(Category.MISC, "\uE24D");
|
CATEGORY_ICONS.put(Category.MISC, "\uE24D");
|
||||||
|
CATEGORY_ICONS.put(Category.XLZEN, "\uE8B8");
|
||||||
CATEGORY_ICONS.put(Category.GROUPS, "\uE2C7");
|
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.BindCommand;
|
||||||
import shit.zen.command.impl.ConfigCommand;
|
import shit.zen.command.impl.ConfigCommand;
|
||||||
import shit.zen.command.impl.LanguageCommand;
|
import shit.zen.command.impl.LanguageCommand;
|
||||||
|
import shit.zen.command.impl.LogCommand;
|
||||||
import shit.zen.command.impl.ToggleCommand;
|
import shit.zen.command.impl.ToggleCommand;
|
||||||
import shit.zen.event.impl.ChatEvent;
|
import shit.zen.event.impl.ChatEvent;
|
||||||
import shit.zen.utils.misc.ChatUtil;
|
import shit.zen.utils.misc.ChatUtil;
|
||||||
@@ -24,6 +25,7 @@ public class CommandManager {
|
|||||||
this.registerCommand(new BindCommand());
|
this.registerCommand(new BindCommand());
|
||||||
this.registerCommand(new ConfigCommand());
|
this.registerCommand(new ConfigCommand());
|
||||||
this.registerCommand(new LanguageCommand());
|
this.registerCommand(new LanguageCommand());
|
||||||
|
this.registerCommand(new LogCommand());
|
||||||
this.registerCommand(new ToggleCommand());
|
this.registerCommand(new ToggleCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import shit.zen.event.impl.KeyEvent;
|
|||||||
import shit.zen.exception.ModuleNotFoundException;
|
import shit.zen.exception.ModuleNotFoundException;
|
||||||
import shit.zen.modules.Category;
|
import shit.zen.modules.Category;
|
||||||
import shit.zen.modules.Module;
|
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.AntiBots;
|
||||||
import shit.zen.modules.impl.combat.AntiFireball;
|
import shit.zen.modules.impl.combat.AntiFireball;
|
||||||
import shit.zen.modules.impl.combat.AntiKB;
|
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.ItemTags;
|
||||||
import shit.zen.modules.impl.render.NameProtect;
|
import shit.zen.modules.impl.render.NameProtect;
|
||||||
import shit.zen.modules.impl.render.NameTags;
|
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.NoHurtCam;
|
||||||
import shit.zen.modules.impl.render.OldHitting;
|
import shit.zen.modules.impl.render.OldHitting;
|
||||||
import shit.zen.modules.impl.render.Projectiles;
|
import shit.zen.modules.impl.render.Projectiles;
|
||||||
@@ -84,6 +86,7 @@ public class ModuleManager extends ClientBase {
|
|||||||
|
|
||||||
public void initModules() {
|
public void initModules() {
|
||||||
this.register(new AntiBots());
|
this.register(new AntiBots());
|
||||||
|
this.register(new AntiArrow());
|
||||||
this.register(new AntiFireball());
|
this.register(new AntiFireball());
|
||||||
this.register(new AntiKB());
|
this.register(new AntiKB());
|
||||||
this.register(new AutoOffHand());
|
this.register(new AutoOffHand());
|
||||||
@@ -139,6 +142,7 @@ public class ModuleManager extends ClientBase {
|
|||||||
this.register(new ItemTags());
|
this.register(new ItemTags());
|
||||||
this.register(new NameProtect());
|
this.register(new NameProtect());
|
||||||
this.register(new NameTags());
|
this.register(new NameTags());
|
||||||
|
this.register(new NoFireOverlay());
|
||||||
this.register(new NoHurtCam());
|
this.register(new NoHurtCam());
|
||||||
this.register(new OldHitting());
|
this.register(new OldHitting());
|
||||||
this.register(new Projectiles());
|
this.register(new Projectiles());
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public enum Category {
|
|||||||
EXPLOIT("World"),
|
EXPLOIT("World"),
|
||||||
WORLD("Misc"),
|
WORLD("Misc"),
|
||||||
MISC("Ghost"),
|
MISC("Ghost"),
|
||||||
|
XLZEN("XLZen"),
|
||||||
GROUPS("Groups");
|
GROUPS("Groups");
|
||||||
|
|
||||||
public String displayName;
|
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.minecraft.client.renderer.GameRenderer;
|
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.util.Mth;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
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.event.impl.RenderEvent;
|
||||||
import shit.zen.modules.Category;
|
import shit.zen.modules.Category;
|
||||||
import shit.zen.modules.Module;
|
import shit.zen.modules.Module;
|
||||||
|
import shit.zen.patch.BufferUploaderPatch;
|
||||||
import shit.zen.settings.impl.BooleanSetting;
|
import shit.zen.settings.impl.BooleanSetting;
|
||||||
import shit.zen.settings.impl.ModeSetting;
|
import shit.zen.settings.impl.ModeSetting;
|
||||||
|
import shit.zen.settings.impl.NumberSetting;
|
||||||
import shit.zen.utils.game.EntityUtil;
|
import shit.zen.utils.game.EntityUtil;
|
||||||
import shit.zen.utils.math.Vector2f;
|
import shit.zen.utils.math.Vector2f;
|
||||||
import shit.zen.utils.render.ColorUtil;
|
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 skeletonSetting = new BooleanSetting("Skeleton", false);
|
||||||
private final BooleanSetting playersSetting = new BooleanSetting("Players", true);
|
private final BooleanSetting playersSetting = new BooleanSetting("Players", true);
|
||||||
private final BooleanSetting mobsSetting = new BooleanSetting("Mobs", false);
|
private final BooleanSetting mobsSetting = new BooleanSetting("Mobs", false);
|
||||||
@@ -107,7 +116,12 @@ public class ESP extends Module {
|
|||||||
@EventTarget
|
@EventTarget
|
||||||
public void onRender(RenderEvent renderEvent) {
|
public void onRender(RenderEvent renderEvent) {
|
||||||
if (mc.level == null || mc.player == null) return;
|
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();
|
this.entityBoxPositions.clear();
|
||||||
return;
|
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
|
@EventTarget
|
||||||
public void onRender2D(Render2DEvent event) {
|
public void onRender2D(Render2DEvent event) {
|
||||||
if (!"Outlined 2D".equals(this.modeSetting.getValue()) || this.entityBoxPositions.isEmpty()) return;
|
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;
|
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.GlRenderEvent;
|
||||||
|
import shit.zen.event.impl.PacketEvent;
|
||||||
import shit.zen.event.impl.Render2DEvent;
|
import shit.zen.event.impl.Render2DEvent;
|
||||||
import shit.zen.hud.DynamicIsland;
|
import shit.zen.hud.DynamicIsland;
|
||||||
import shit.zen.hud.NeverloseWatermark;
|
import shit.zen.hud.NeverloseWatermark;
|
||||||
|
import shit.zen.hud.TargetHealthHud;
|
||||||
import shit.zen.modules.Category;
|
import shit.zen.modules.Category;
|
||||||
import shit.zen.modules.Module;
|
import shit.zen.modules.Module;
|
||||||
|
import shit.zen.settings.impl.BooleanSetting;
|
||||||
import shit.zen.settings.impl.ModeSetting;
|
import shit.zen.settings.impl.ModeSetting;
|
||||||
import shit.zen.event.EventTarget;
|
import shit.zen.event.EventTarget;
|
||||||
|
|
||||||
public class Watermark extends Module {
|
public class Watermark extends Module {
|
||||||
final ModeSetting styleSetting = new ModeSetting("Style", "Neverlose", "DynamicIsland").withDefault("DynamicIsland");
|
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 DynamicIsland dynamicIsland = new DynamicIsland();
|
||||||
private final NeverloseWatermark neverloseWatermark = new NeverloseWatermark();
|
private final NeverloseWatermark neverloseWatermark = new NeverloseWatermark();
|
||||||
|
private final TargetHealthHud targetHealthHud = new TargetHealthHud();
|
||||||
|
|
||||||
public Watermark() {
|
public Watermark() {
|
||||||
super("Watermark", Category.RENDER);
|
super("Watermark", Category.RENDER);
|
||||||
@@ -31,6 +40,9 @@ public class Watermark extends Module {
|
|||||||
this.dynamicIsland.onRender2D(render2DEvent);
|
this.dynamicIsland.onRender2D(render2DEvent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (this.targetHealthSetting.getValue()) {
|
||||||
|
this.targetHealthHud.render(render2DEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventTarget
|
@EventTarget
|
||||||
@@ -42,4 +54,32 @@ public class Watermark extends Module {
|
|||||||
this.neverloseWatermark.onGlRender(glRenderEvent);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ public final class Zh {
|
|||||||
put("World", "世界");
|
put("World", "世界");
|
||||||
put("Misc", "杂项");
|
put("Misc", "杂项");
|
||||||
put("Ghost", "幽灵");
|
put("Ghost", "幽灵");
|
||||||
|
put("XLZen", "XLZen");
|
||||||
|
|
||||||
// --- modules ---
|
// --- modules ---
|
||||||
put("AimAssist", "瞄准辅助");
|
put("AimAssist", "瞄准辅助");
|
||||||
@@ -258,6 +259,20 @@ public final class Zh {
|
|||||||
put("Show Artifacts", "显示神器");
|
put("Show Artifacts", "显示神器");
|
||||||
put("Show Health Bar", "显示血条");
|
put("Show Health Bar", "显示血条");
|
||||||
put("Health Bar Position", "血条位置");
|
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("More Particles", "更多粒子");
|
||||||
put("Tracers", "射线");
|
put("Tracers", "射线");
|
||||||
put("Target ESP", "目标透视");
|
put("Target ESP", "目标透视");
|
||||||
|
|||||||
Reference in new issue
屏蔽一个用户