diff --git a/paste/README.md b/paste/README.md index 54d4916..5b8343b 100644 --- a/paste/README.md +++ b/paste/README.md @@ -1,404 +1,1270 @@ # Zen 抄袭 Naven 的源代码证据 -本文档基于公开可获取的两个仓库进行**逐行 / 逐字段 / 逐字符串**对比: +本文档基于公开可获取的两个仓库进行**代码段并排**对比: -- **参考实现(被抄袭方)**:[`Margele/Naven-Modern`](https://github.com/Margele/Naven-Modern)(即 Naven 客户端的简单移植版本) +- **参考实现(被抄袭方)**:[`Margele/Naven-Modern`](https://github.com/Margele/Naven-Modern) - **待审项目(疑似抄袭方)**:本仓 OpenZen(Zen 客户端反混淆产物) -> Naven 仓库的包名混淆 `com.heypixel.heypixel.VcX6svVqmeT8.*` 来自 Naven 原作者本人 push 上 GitHub 的状态,不是本文档的混淆操作。Zen 这边的对应文件全部位于 `src/main/java/shit/zen/`。 +> 字段 / 类 / 方法名是反编译时根据 Naven 重命名出来的,**不能算抄袭指纹**。所以下面所有证据全是真实的**字符串字面量、魔法常量、控制流、lambda 表达式**——这些东西在反混淆过程中不会被自动还原,只能是抄过来的。 --- -## TL;DR +## 目录 -- **Zen 的"功能模块"几乎全部能在 Naven 中找到一对一对应**:要么直接同名搬运,要么把 Naven 的 `@ModuleInfo(name=...)` 内部名直接拿来当类名(最典型的是 `InventoryManager` —— Naven 的类叫 `InventoryCleaner`,但 `@ModuleInfo(name="InventoryManager")`,Zen 直接以这个内部名命名)。 -- **框架层(事件 / 模块 / 设置 / 命令 / 工具)也全套搬运**:`EventBus` 是 Naven `EventManager` 的逐字段重命名;`EventPriority` 沿用 DarkMagician6 EventAPI 的特征数值;`@EventTarget value() default 2` 完全一致;`bind/toggle/config/language` 四条命令的提示串(`"Bound X to Y."`、`"Invalid module."`、`"Usage: .bind [key]"`)一字不差;`ChunkUtil.getLoadedChunks` 连异常文案 `"Stream limit didn't work."` 都照搬。 -- **多处遗留"反混淆指纹"**:`RotationUtil` 里那个工具类完全用不到的 `import org.antlr.v4.runtime.misc.OrderedHashSet`;`Stuck` 的魔法数 `+ 1337`;`Projectiles` 模块 RGB `(173, 12, 255)` / `(255, 238, 154)`;`InventoryManager` 的中文字面量 `"点击使用"`;都是 Naven 那边独有的特征。 -- **改动方向集中在三件事**:(a)拆模块(把 Naven 的 `HUD` 拆成 `Watermark` + 一堆 `shit.zen.hud.*`);(b)扩写(`Disabler` 在 Naven 的 yaw-only 一段外面包了一整套 Grim/ACA/Themis 子模块);(c)改名(`AttackCrystal → CrystalAura`、`Aura → KillAura`、`Glow → ESP`、`NoJumpDelay → NoDelay`、`InventoryCleaner → InventoryManager`),通常**只换类名不换 Setting 文案**,所以 Setting 字符串成为最稳定的抄袭指纹。 - -下表是模块级的总览: - -| 类别 | Naven 模块 | Zen 对应 | 抄袭程度 | -|------|-----------|---------|---------| -| Combat | `AntiBots` | `combat/AntiBots` | **逐行** | -| Combat | `Aura` (`@ModuleInfo name="KillAura"`) | `combat/KillAura` | **逐行**(最严重) | -| Combat | `AttackCrystal` (`@ModuleInfo name="CrystalAura"`) | `combat/CrystalAura` | **逐行** | -| Combat | `AntiFireball` | `combat/AntiFireball` | **逐行** | -| Combat | `AimAssist` | `misc/AimAssist` | 同名 + 部分重写 | -| Combat | `AutoClicker` | `misc/AutoClicker` | 同名 + 显著扩写 | -| Combat | `Velocity` | `combat/AntiKB` | 仅功能相近,不构成抄袭 | -| Move | `Scaffold` | `movement/Scaffold` | **逐行** + 扩展 | -| Move | `FastWeb` | `movement/FastWeb` | **逐行** | -| Move | `Sprint` | `movement/Sprint` | **逐行** | -| Move | `Stuck` | `player/Stuck` | **逐行** + 扩展 | -| Move | `AutoMLG` | `player/AutoMLG` | 重写 + 触发器抄袭 | -| Move | `SafeWalk` | `misc/SafeWalk` | **逐行**(仅改 Category) | -| Move | `Blink` | `movement/FireballBlink` | 骨架抄袭 + 场景重写 | -| Move | `NoJumpDelay` | `movement/NoDelay` | 改名(Setting label 露馅) | -| Misc | `Disabler` | `exploit/Disabler` | 核心段照搬 + 包装扩展 | -| Misc | `FastPlace` | `exploit/FastPlace` | hook 入口一致,算法换皮 | -| Misc | `AutoTools` | `world/AutoTools` | **逐行翻译** | -| Misc | `Teams` | `world/Teams` | **逐行**(加一个 Mode) | -| Misc | `ChestStealer` | `player/ChestStealer` | 核心判定段照搬 | -| Misc | `InventoryCleaner` (`@ModuleInfo name="InventoryManager"`) | `player/InventoryManager` | **逐行**(含中文字面量) | -| Render | `Glow` | `render/ESP` | 改名 + 扩展 | -| Render | `ChestESP` | `render/ChestESP` | **逐行** | -| Render | `Compass` | `render/Compass` | **逐行** | -| Render | `FullBright` | `render/FullBright` | **逐行**(仅放大范围) | -| Render | `ItemTags` | `render/ItemTags` | 同名空壳 | -| Render | `NameProtect` | `render/NameProtect` | 同算法 + 重写事件路径 | -| Render | `NameTags` | `render/NameTags` | 门面策略类(Scale=0.3/0.01 露馅) | -| Render | `NoHurtCam` | `render/NoHurtCam` | 同名空壳 | -| Render | `Projectile` | `render/Projectiles` | **逐行**(颜色/重力/算法常量全照搬) | -| Render | `HUD` | `render/Watermark` + `shit.zen.hud.*` | 拆模块 + 字段名 | -| 框架 | `EventManager` / `EventTarget` / `Priority` | `EventBus` / `EventTarget` / `EventPriority` | **逐字段** | -| 框架 | `Module` / `ModuleManager` / `Category` | 同名 | **逐行**(setEnabled、toggle 完全一致) | -| 框架 | `Value` / `ValueBuilder` / `*Value` | `Setting` / `*Setting` | **同结构**改名 | -| 框架 | `Command*` + `CommandManager` | `Command*` + `CommandManager` | **逐字符串**(命令名、提示串一致) | -| 框架 | `RotationUtils` / `ChunkUtils` / `BlockUtils` / ... | `RotationUtil` / `ChunkUtil` / `BlockUtil` / ... | **逐行** + 残留特征 import | +- [AntiBots](#antibots) +- [CrystalAura](#crystalaura) +- [AntiFireball](#antifireball) +- [Scaffold](#scaffold) +- [FastWeb](#fastweb) +- [Stuck](#stuck) +- [AutoMLG](#automlg) +- [SafeWalk](#safewalk) +- [Disabler](#disabler) +- [AutoTools](#autotools) +- [ChestStealer](#cheststealer) +- [InventoryManager](#inventorymanager) +- [ChestESP](#chestesp) +- [Compass](#compass) +- [Projectiles](#projectiles) +- [工具类](#工具类) --- -## 一、Combat 模块 +## AntiBots -### Zen `combat/AntiBots.java` vs Naven `combat/AntiBots.java` +### 三条 ChatUtil 字符串字面量与拼接模板 -**对应关系**:直接同名搬运。 +```java +// Naven AntiBots.java +ChatUtils.addChatMessage("Fake Staff Detected! (" + uuidDisplayNames.get(entry.getKey()) + ")"); +... +ChatUtils.addChatMessage("Bot Detected! (" + displayName + ")"); +... +ChatUtils.addChatMessage("Bot Removed! (" + displayName + ")"); +``` -**关键证据**: +```java +// Zen AntiBots.java +ChatUtil.print("Fake Staff Detected! (" + suspectNames.get(entry.getKey()) + ")"); +... +ChatUtil.print("Bot Detected! (" + botName + ")"); +... +ChatUtil.print("Bot Removed! (" + name + ")"); +``` -- 五个静态 map 一一对应:`uuidDisplayNames ↔ suspectNames`、`entityIdDisplayNames ↔ confirmedBotNames`、`uuids ↔ suspectJoinTimes`、`ids ↔ confirmedBotIds`、`respawnTime ↔ playerAddTimes`,全部用 `ConcurrentHashMap` / `HashSet`。 -- Setting `"Respawn Time"`:默认 2500、范围 0–10000、步长 100,完全一致。 -- 方法 `isBedWarsBot(Entity)` / `isBot(Entity)` 签名和返回路径完全相同。 -- **字符串字面量原样保留**:`"Fake Staff Detected! ("`、`"Bot Detected! ("`、`"Bot Removed! ("` 三条全部相同。 -- 500 ms 怀疑→确认管线、`ClientboundPlayerInfoUpdatePacket / ClientboundAddPlayerPacket / ClientboundRemoveEntitiesPacket` 三段式处理同构。 +三条字面量一字不差,拼接模板 `"X! (" + name + ")"` 形状完全一致。这是模块作者主观选的英文短句,撞车概率为零。 -### Zen `combat/KillAura.java` vs Naven `combat/Aura.java` +### `isBedWarsBot` 的核心三步 -**对应关系**:Naven 类名 `Aura`(`@ModuleInfo(name="KillAura")`)→ Zen 直接以 `KillAura` 命名。**这是抄袭最严重的模块。** +```java +// Naven AntiBots.java +public static boolean isBedWarsBot(Entity entity) { + AntiBots module = (AntiBots) Naven.getInstance().getModuleManager().getModule(AntiBots.class); + if (module.respawnTimeValue.getCurrentValue() < 1) { + return false; + } + if (!respawnTime.containsKey(entity.getUUID())) { + return false; + } + return System.currentTimeMillis() - respawnTime.get(entity.getUUID()) < module.respawnTimeValue.getCurrentValue(); +} -**关键证据**: +public static boolean isBot(Entity entity) { + return ids.contains(entity.getId()); +} +``` -- **12 个 BooleanSetting + 9 个 NumberSetting 与 Naven 一一对应**,名字/默认值/范围全套一致:`Attack Player / Attack Invisible / Attack Animals / Attack Mobs / Multi Attack / Prefer Baby / Target HUD / Target ESP / Attack Range(=Aim Range) / APS / Switch Size / FOV / Hurt Time`,默认 `range=5/1–6, aps=10/1–20, switchSize=1/1–5, fov=360/10–360, hurtTime=10/0–10`。 -- `switchSize` 的可见性 `() -> !infSwitch.getValue()` 等价 Naven `() -> !infSwitch.getCurrentValue()`。 -- `isValidTarget` 的实体类型检查链:`ArmorStand / Invisible / Player+bbWidth<0.5 || isSleeping / (Mob|Slime|Bat|AbstractGolem) / (Animal|Squid) / Villager / isSpectator`——import `Bat, AbstractGolem, Squid, Slime, Villager, ArmorStand` 完全一致。 -- 主循环:`isSwitch = switchSize > 1` → `updateTargets()` → `aimingTarget = shouldPreAim/getTarget()` → 记录 `prev/lastRotationData` → `attacks >= switchAttackTimes || distance > 3` 时 round-robin `++index` → `attacks += aps/20f`。 -- `getTargets()` 结尾:`possibleTargets.sort(Comparator.comparing(o -> o instanceof EndCrystal ? 0 : 1))`、`subList(0, Math.min(size, switchSize))` 限长、`preferBaby` 的 `removeIf(!isBaby)`,三处写法一对一。 -- TargetESP:`200/255F, 0, 0, 60/255F` red / `0, 200/255F, 0, 60/255F` green、`AABB box = entity.getBoundingBox().move(-motionX,-motionY,-motionZ).move(partialTick*motionX,...)`、`RenderSystem.setShader(GameRenderer::getPositionShader)`、`GL_LINE_SMOOTH` 开关顺序完全照搬。 +```java +// Zen AntiBots.java +public static boolean isBedWarsBot(Entity entity) { + AntiBots antiBots = INSTANCE; + if (entity.getId() >= 1000000000 || entity.getId() <= -1) { return true; } + if (entity.getName() == null) { return true; } + if (entity.getScoreboardName().isEmpty()) { return true; } + if (antiBots.newPlayerTimeout.getValue().floatValue() < 1.0f) { return false; } + if (!playerAddTimes.containsKey(entity.getUUID())) { return false; } + return (float)(System.currentTimeMillis() - playerAddTimes.get(entity.getUUID())) < antiBots.newPlayerTimeout.getValue().floatValue(); +} -### Zen `combat/CrystalAura.java` vs Naven `combat/AttackCrystal.java` +public static boolean isBot(Entity entity) { + return confirmedBotIds.contains(entity.getId()); +} +``` -**对应关系**:Naven 类名 `AttackCrystal`(`@ModuleInfo(name="CrystalAura")`)→ Zen 直接以 `CrystalAura` 命名。 +Zen 在前面加了几条短路判断遮掩,但核心三步 `< 1` 哨兵 → `containsKey` 检查 → `currentTimeMillis() - map.get(uuid) < timeout` 比对完全照抄,`isBot` 一行实现一模一样。 -**关键证据**: +### Setting 字面量 `"Respawn Time"` + 2500 + 0~10000 + 100 -- Setting `"Attack on Packet (Danger)"` 字符串、`boolean` 类型、`false` 默认完全一致。 -- `onPacket` 7 步序列:收 `ClientboundAddEntityPacket` → `EntityType.END_CRYSTAL` → `new EndCrystal(mc.level, packet.getX/Y/Z())` → `setId(packet.getId())` → 距离阈值 `≤ 4` → `ServerboundMovePlayerPacket.PosRot` + `ServerboundUseItemPacket(MAIN_HAND)` → 暂存→改 rotation→`createAttackPacket(target, false)`→swing→还原。一一对应。 -- `onTick` 中 `Optional any = StreamSupport.stream(... entitiesForRendering ...).filter(entity instanceof EndCrystal).findAny();` 配合 `≤ 3` 阈值。 -- 静态字段 `Entity entity / crystalTarget` 与 `rotations / aimRotation` 双字段同构。 +```java +// Naven AntiBots.java +private final FloatValue respawnTimeValue = ValueBuilder.create(this, "Respawn Time") + .setDefaultFloatValue(2500).setFloatStep(100) + .setMinFloatValue(0).setMaxFloatValue(10000).build().getFloatValue(); +``` -### Zen `combat/AntiFireball.java` vs Naven `misc/AntiFireball.java` +```java +// Zen AntiBots.java +private final NumberSetting newPlayerTimeout = + new NumberSetting("Respawn Time", 2500.0, 0.0, 10000.0, 100.0); +``` -**对应关系**:同名搬运(仅改 Category)。 +抛开字段名,`"Respawn Time"` 字符串、默认 2500、范围 0–10000、步长 100 四个字面量一一对应。 -**关键证据**: +### `onPacket` 三个 instanceof 分支的过滤条件 -- 流式查找一模一样:`StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), …).filter(entity instanceof Fireball && mc.player.distanceTo(entity) < 6).map(...).findFirst()`,距离阈值 `6` 同。 -- 早退 `if (!optional.isPresent()) return;`。 -- 两行攻击代码顺序和变量名完全一致:`mc.gameMode.attack(mc.player, fireball); mc.player.swing(InteractionHand.MAIN_HAND);`。 +```java +// Naven AntiBots.java +if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) { + for (ClientboundPlayerInfoPacket.PlayerUpdate entry : packet.getEntries()) { + if (entry.getDisplayName() != null && (entry.getDisplayName().getSiblings().isEmpty() + && entry.getGameMode() == GameType.SURVIVAL)) { + UUID uuid = entry.getProfile().getId(); + uuids.put(uuid, System.currentTimeMillis()); + uuidDisplayNames.put(uuid, entry.getDisplayName().getString()); + } + } +} else if (e.getPacket() instanceof ClientboundAddPlayerPacket) { + if (uuids.containsKey(packet.getPlayerId())) { + String displayName = uuidDisplayNames.get(packet.getPlayerId()); + ChatUtils.addChatMessage("Bot Detected! (" + displayName + ")"); + entityIdDisplayNames.put(packet.getEntityId(), displayName); + uuids.remove(packet.getPlayerId()); + ids.add(packet.getEntityId()); + } +} +``` -### Zen `misc/AimAssist.java` vs Naven `combat/AimAssist.java` +```java +// Zen AntiBots.java +if (!infoUpdate.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) { return; } +for (ClientboundPlayerInfoUpdatePacket.Entry entry : infoUpdate.entries()) { + if (entry.displayName() == null + || !entry.displayName().getSiblings().isEmpty() + || entry.gameMode() != GameType.SURVIVAL) { continue; } + UUID uuid = entry.profile().getId(); + suspectJoinTimes.put(uuid, System.currentTimeMillis()); + suspectNames.put(uuid, entry.displayName().getString()); +} +... +if (packet instanceof ClientboundAddPlayerPacket addPlayer) { + if (!suspectJoinTimes.containsKey(addPlayer.getPlayerId())) { return; } + String botName = suspectNames.get(addPlayer.getPlayerId()); + ChatUtil.print("Bot Detected! (" + botName + ")"); + confirmedBotNames.put(addPlayer.getEntityId(), botName); + suspectJoinTimes.remove(addPlayer.getPlayerId()); + confirmedBotIds.add(addPlayer.getEntityId()); + return; +} +``` -**对应关系**:同名搬运 + 后续大改写。 +过滤条件三件套 `displayName != null && siblings.isEmpty() && gameMode == SURVIVAL` 一字不差(Zen 只用 De Morgan 取反 `continue`),随后五步 put / get / remove / add 顺序完全相同。 -**关键证据**:类名同;`getAngleDifference(double, double)` 静态助手同名同实现(`Mth.wrapDegrees(a-b)`);`fov` / `range` 字段语义相同(Setting label `"Fov"` / `"FoV"` 仅大小写差)。 +### 500ms 假员工阈值 -**改动**:算法已大幅重写为 smooth/adaptive 偏移模型,事件接入 `KillAura.isValidTarget(...)`,逐行抄袭已不显著。 +```java +// Naven AntiBots.java +for (Map.Entry entry : uuids.entrySet()) { + if (System.currentTimeMillis() - entry.getValue() > 500) { + ChatUtils.addChatMessage("Fake Staff Detected! (" + uuidDisplayNames.get(entry.getKey()) + ")"); + uuids.remove(entry.getKey()); + } +} +``` -### Zen `misc/AutoClicker.java` vs Naven `combat/AutoClicker.java` +```java +// Zen AntiBots.java +for (Map.Entry entry : suspectJoinTimes.entrySet()) { + if (System.currentTimeMillis() - entry.getValue() <= 500L) continue; + if (this.debug.getValue()) { + ChatUtil.print("Fake Staff Detected! (" + suspectNames.get(entry.getKey()) + ")"); + } + suspectJoinTimes.remove(entry.getKey()); +} +``` -**对应关系**:同名搬运 + 显著扩写。 - -**关键证据**:核心攻击路径相同 —— `mc.options.keyAttack.isDown()` + `mc.hitResult.getType() != BLOCK` + `setMissTime(0)`(Naven 通过 `MinecraftAccessor` mixin,Zen 通过 `ReflectionUtil`) + `KeyMapping.click(mc.options.keyAttack.getKey())`。 - -**改动**:Zen 加 Left/Right/Both、Method/Key/Mouse、Break Block、CPS Mode 等扩展。 - -### Zen `combat/AntiKB.java` vs Naven `combat/Velocity.java` - -**判定**:功能相近但实现路径完全不同(Zen 是 mode-dispatcher,Naven 是 packet-cancel),**不构成抄袭证据**。 +500 ms 这个魔法常量、`currentTimeMillis() - entry.getValue()` 减法表达式、循环里就地 remove 的反模式写法三处都对得上。 --- -## 二、Movement / Player 模块 +## CrystalAura -### Zen `movement/Scaffold.java` vs Naven `move/Scaffold.java` +### `onPacket` 末影水晶生成 9 步序列 -**对应关系**:同名搬运,框架翻新但核心算法逐句保留。 +```java +// Naven AttackCrystal.java +if (packet.getType() == EntityType.END_CRYSTAL) { + EndCrystal pTarget = new EndCrystal(mc.level, packet.getX(), packet.getY(), packet.getZ()); + pTarget.setId(packet.getId()); + if (mc.player.distanceTo(pTarget) <= 4) { + Vector2f rotations = RotationUtils.getRotations(pTarget); + mc.getConnection().send(new ServerboundMovePlayerPacket.PosRot(mc.player.getX(), mc.player.getY(), mc.player.getZ(), rotations.getX(), rotations.getY(), mc.player.isOnGround())); + mc.getConnection().send(new ServerboundUseItemPacket(InteractionHand.MAIN_HAND)); + float currentYaw = mc.player.getYRot(); + float currentPitch = mc.player.getXRot(); + mc.player.setYRot(RotationManager.rotations.x); + mc.player.setXRot(RotationManager.rotations.y); + mc.getConnection().send(ServerboundInteractPacket.createAttackPacket(pTarget, false)); + mc.player.swing(InteractionHand.MAIN_HAND); +``` -**关键证据**: +```java +// Zen CrystalAura.java +if (addEntityPacket.getType() == EntityType.END_CRYSTAL) { + EndCrystal endCrystal = new EndCrystal(mc.level, addEntityPacket.getX(), addEntityPacket.getY(), addEntityPacket.getZ()); + endCrystal.setId(addEntityPacket.getId()); + if (mc.player.distanceTo(endCrystal) <= 4.0f) { + Rotation rotation = RotationUtil.entityRotation(endCrystal); + mc.getConnection().send(new ServerboundMovePlayerPacket.PosRot(mc.player.getX(), mc.player.getY(), mc.player.getZ(), rotation.getYaw(), rotation.getPitch(), mc.player.onGround())); + PacketUtil.sendPredictive(seq -> new ServerboundUseItemPacket(InteractionHand.MAIN_HAND, seq)); + float prevYaw = mc.player.getYRot(); + float prevPitch = mc.player.getXRot(); + mc.player.setYRot(RotationHandler.targetRotation.getYaw()); + mc.player.setXRot(RotationHandler.targetRotation.getPitch()); + mc.getConnection().send(ServerboundInteractPacket.createAttackPacket(endCrystal, false)); + mc.player.swing(InteractionHand.MAIN_HAND); +``` -- `ModeSetting("Mode", "Normal", "Telly Bridge", ..., "Keep Y")` 与 Naven `mode.setModes("Normal","Telly Bridge","Keep Y")` 一一对应。 -- `renderItemSpoof` 字段名、默认 `true` 一致;`advancedBlockSearch` 字段名原样保留。 -- `eagle` / `snap` 的可见性 lambda `() -> mode.is("Normal")` 与 Naven `setVisibility(() -> mode.isCurrentMode("Normal"))` 是相同代码翻译。 -- `getPlayerYawRotation` 中随机抖动 `RandomUtils.nextFloat(0.0f, 0.5f) - 0.25f`、yaw `-180.0F`、pitch `82F / 75.5F` 这些 magic 数原样保留。 -- **`isOnBlockEdge(float)` 函数体逐字符相同**:`!mc.level.getCollisions(mc.player, mc.player.getBoundingBox().move(0.0,-0.5,0.0).inflate(-x,0.0,-x)).iterator().hasNext()`。 +相同的距离阈值 `<= 4`、相同的 9 步操作序列(new EndCrystal → setId → distance → getRotations → PosRot → UseItem → save yaw/pitch → setYRot/XRot → InteractPacket → swing)。 -### Zen `movement/FastWeb.java` vs Naven `move/FastWeb.java` +### `onTick` 的 StreamSupport 查找 -**关键证据**: +```java +// Naven AttackCrystal.java +Optional any = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), true).filter(entity -> entity instanceof EndCrystal).findAny(); +rotations = null; +if (any.isPresent()) { + Entity entity = any.get(); + Vector2f rots = RotationUtils.getRotations(entity); + double minDistance = RotationUtils.getMinDistance(entity, rots); + if (minDistance <= 3) { + rotations = rots; + this.entity = entity; +``` -- 字段 `playerInWebTick / ticksInWeb`(Naven)→ `lastWebTick / webCount`(Zen)只改名不改语义。 -- `onMotion` 中 `playerInWebTick < mc.player.tickCount → ticksInWeb = 0` 的 reset 逻辑完全照搬。 -- 阈值 `> 5` 与魔法向量 `new Vec3(0.88, 1.88, 0.88)` **逐字节相同**。 -- 无 Setting,行为路径一致。 +```java +// Zen CrystalAura.java +Optional crystalOpt = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), true).filter(entity -> entity instanceof EndCrystal).findAny(); +aimRotation = null; +if (crystalOpt.isPresent() && (hitDistance = RotationUtil.getMinHitDistance(crystalEntity = crystalOpt.get(), rotation = RotationUtil.entityRotation(crystalEntity))) <= 3.0) { + aimRotation = rotation; + this.crystalTarget = crystalEntity; +``` -### Zen `movement/Sprint.java` vs Naven `move/Sprint.java` - -**关键证据**:类签名一致,主体只有 `mc.options.toggleSprint().set(false)` + `keySprint` 强按下,PRE 阶段执行。 - -### Zen `player/Stuck.java` vs Naven `move/Stuck.java` - -**关键证据**: - -- 字段一一对应(仅重命名):`stage / packet / lastYaw / lastPitch / tryDisable / Queue packets` → `stuckState / capturedPacket / savedYaw / savedPitch / pendingDisable / pongQueue`。 -- `setEnabled` 的 `stage==3 → disable`、否则 `tryDisable=true` 三段分支完全保留。 -- **神奇位移常数 `mc.player.getX() + 1337` / `getZ() + 1337` 一字不差。** -- `onPacket` 的 `instanceof` 链顺序与分支体一致:`ServerboundMovePlayerPacket → cancel`、`ServerboundPongPacket → offer + cancel`、`ServerboundUseItemPacket || ServerboundPlayerActionPacket → capture + stage=1 + cancel`、`ClientboundPlayerPositionPacket → drain pong + stage=3 + disable`。 - -### Zen `player/AutoMLG.java` vs Naven `move/AutoMLG.java` - -**关键证据**: - -- Setting `triggerDistanceSetting = NumberSetting("Fall distance", 3.0f, 1.0f, 10.0f, 0.1f)` 与 Naven `distance = ValueBuilder("Fall Distance").setDefaultFloatValue(3).setFloatStep(0.1f)...` 默认/步进相同。 -- `Items.WATER_BUCKET` 在槽 0..9 的扫描写法、`originalSlot = inventory.selected` 切换+还原模式照搬。 -- pitch 强制朝下(90°)、useItem MAIN_HAND + swing 流程一致。 - -**Zen 自加**:Predict Ticks / Solid check / 一个拼写错误 `Recorvey`(typo,反而成为抄袭佐证)。 - -### Zen `misc/SafeWalk.java` vs Naven `move/SafeWalk.java` - -**关键证据**: - -- `isOnBlockEdge(float)` 函数体**逐字符相同**(含 `0.3F` 常量、参数 `inset` 是 `sensitivity` 的最小程度改名)。 -- `onMotion` 主体一句 `mc.options.keyShift.setDown(mc.player.onGround() && isOnBlockEdge(0.3F))`。 -- `onDisable` 复用 `InputConstants.isKeyDown(...) + mc.options.keyShift.setDown` 模式。 - -**改动**:仅改 Category(MOVEMENT → MISC)。 - -### Zen `movement/FireballBlink.java` vs Naven `move/Blink.java` - -**对应关系**:Naven 通用 `Blink` 被改名为 `FireballBlink` 并特化场景。 - -**关键证据**:`LinkedBlockingQueue> + ConcurrentLinkedQueue` 的"扣留发包再 flush"骨架、`flushPackets()` 在 disable 时清队列、监听 `ClientboundSetEntityMotionPacket` 玩家 ID 后入队,与 Naven 的 packet 暂存模型同构。 - -### Zen `movement/NoDelay.java` vs Naven `move/NoJumpDelay.java` - -**关键证据**: - -- Setting label `new BooleanSetting("No Jump Delay", true)` **直接抄 Naven 的 `@ModuleInfo(name="NoJumpDelay")`**。 -- 字段名 `fastDig` 与开关 label 不符 —— 提示是从其它项目搬来后未对齐。 -- 类本身没有事件回调,实际效果由 mixin 完成(同 Naven 的 `LivingEntityAccessor.setNoJumpDelay(0)` 思路)。 +`StreamSupport.stream(... .spliterator(), true)` 并行流、`EndCrystal` instanceof 过滤、`<= 3` 命中距离阈值、`aimRotation = null` 再赋值的控制流完全一致。 --- -## 三、Misc / Exploit / Player 模块 +## AntiFireball -### Zen `exploit/Disabler.java` vs Naven `misc/Disabler.java` +```java +// Naven AntiFireball.java +Stream stream = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), true); +Optional fireball = stream.filter(entity -> entity instanceof Fireball && mc.player.distanceTo(entity) < 6).map(entity -> (Fireball) entity).findFirst(); +if (!fireball.isPresent()) { + return; +} +Fireball entity = fireball.get(); +mc.gameMode.attack(mc.player, entity); +mc.player.swing(InteractionHand.MAIN_HAND); +``` -**对应关系**:Naven Disabler 的 `duplicateRotPlaceDisabler` 一整段被 Zen 当作 `grimDuplicateRotPlace` 子选项原样搬来,外面包了一整套扩展。 +```java +// Zen AntiFireball.java +Stream stream = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), false); +Optional optional = stream.filter(entity -> entity instanceof Fireball && (double)mc.player.distanceTo(entity) < 6.0).map(entity -> (Fireball)entity).findFirst(); +if (!optional.isPresent()) { + return; +} +Fireball fireball = optional.get(); +ChatUtil.print("§c[AntiFireball] Attacking fireball..."); +mc.gameMode.attack(mc.player, fireball); +mc.player.swing(InteractionHand.MAIN_HAND); +``` -**关键证据**: - -- 字段命名完全照搬:`rotated`、`lastPlacedDeltaYaw → lastPlacedYawDiff`、`lastPlacedDeltaPitch → lastPlacedPitchDiff`。 -- 算法控制流逐句对应:`hasRotation()` → 算 `deltaYaw = abs(yaw - last)` → `rotated = true` → `ServerboundUseItemOnPacket` 分支 `lastPlacedDeltaYaw = deltaYaw; rotated = false`。 -- **魔法常数照抄**:阈值 `deltaYaw > 2`、`xDiff < 0.0001` 完全一致。 -- `Logging` BooleanSetting + `log(...)` 函数同名同语义。 - -**改动**:把生成新 packet 改成 `ReflectionUtil.setYRot/setXRot` 改原 packet;外加 Grim/ACA/Themis 一堆扩展子模块。原始 yaw-only 核心是 Naven 的。 - -### Zen `exploit/FastPlace.java` vs Naven `misc/FastPlace.java` - -**判定**:算法换皮(Zen Delay 计时器,Naven CPS 累加器)但**底层 hook 点 `setRightClickDelay(0)` 完全一致**(Naven 通过 MinecraftAccessor mixin,Zen 通过 ReflectionUtil)。`BlockItem` 判定路径相同。最弱的证据。 - -### Zen `world/AutoTools.java` vs Naven `misc/AutoTools.java` - -**对应关系**:几乎逐行翻译。 - -**关键证据**: - -- 三个 Setting `"Check Sword"=true`、`"Switch Back"=true`、`"Silent"=true` 字符串和默认值全照搬;`Silent` 绑在 `switchBack` 可见性下也保留。 -- 状态字段 `originSlot = -1` → `previousSlot = -1` 仅改名。 -- `getBestTool` 算法逐行复制:`int slot=0; float dmg=1; for i in 0..9` → `isGodItem/isWeaponItem` 跳过 → `SwordItem instanceof && !(WebBlock)` 例外 → `destroySpeed > 1` 时加 `efficiencyLevel * efficiencyLevel + 1` 附魔加成 → `dmg > 1 ? slot : -1` 返回。 -- `onMotion` PRE/POST 切换逻辑同形,`onUpdateHeldItem` 在 MAIN_HAND && originSlot != -1 时用 `e.setItem(...)` 同一句话。 - -### Zen `world/Teams.java` vs Naven `misc/Teams.java` - -**关键证据**: - -- `isSameTeam(Entity)` 签名、返回路径、`instanceof Player` 后调 `getTeam(player) / getTeam(mc.player)` 用 `Objects.equals` 比较——完全一致。 -- `getTeam(Entity)` 函数 `mc.getConnection().getPlayerInfo(entity.getUUID()) → null 检查 → playerInfo.getTeam().getName()` 逐行对应。 - -**改动**:加一个 `ModeSetting("Mode", "Color", "Scoreboard")`,`Scoreboard` 分支就是 Naven 原版。 - -### Zen `player/ChestStealer.java` vs Naven `misc/ChestStealer.java` - -**关键证据**: - -- 容器标题判定一模一样:`Component.translatable("container.chest")`、`"container.chestDouble"`、`"container.enderchest"`,再 `|| title.equals("Chest")`。 -- `Ender Chest` BooleanSetting 默认 `false` 同名搬过来。 -- `isWorthStealing` 是 Naven `isItemUseful` 分支级翻译:`ArmorItem → protection vs BestArmorScore`、`SwordItem → damage`、`PickaxeItem / AxeItem / ShovelItem → ToolScore`、`CrossbowItem → score`、`BowItem + PunchBow / PowerBow` 两个变体、`COMPASS → !hasItem`、`WATER_BUCKET/LAVA_BUCKET >= count`、`BlockItem + isPlaceable + maxBlock`、`ARROW + maxArrow`、`FISHING_ROD >= 1`、`SNOWBALL/EGG + maxProjectile`、`ItemNameBlockItem → false` → `isCommonItemUseful`,**分支顺序与返回逻辑完全一致**。 -- 容器步进算法同形:`for (slot=0; slot` 列表 + `HashSet usedSlots` 检测槽位冲突,连 `Pair.of` 这种 commons-lang3 的导入选型都一样。 +lambda 链 `instanceof Fireball && distanceTo < 6` 一字不差,魔法数 `6` 完全一致,`mc.gameMode.attack(mc.player, ...)` + `swing(MAIN_HAND)` 收尾相同。Zen 仅多一行 debug 输出。 --- -## 四、Render 模块 +## Scaffold -### Zen `render/ESP.java` vs Naven `render/Glow.java` +### `onEnable` 中 `getYRot() - 180` 的初始化 -**对应关系**:Naven `Glow` 改名为 `ESP` 并扩充为 2D 边框 + 血条。Glow 模式下保留原过滤逻辑。 +```java +// Naven Scaffold.java +public void onEnable() { + if (mc.player != null) { + oldSlot = mc.player.getInventory().selected; + this.rots.set(mc.player.getYRot() - 180, mc.player.getXRot()); + this.lastRots.set(mc.player.yRotO - 180, mc.player.yRotO); + this.pos = null; + } +} +``` -**关键证据**: +```java +// Zen Scaffold.java +public void onEnable() { + if (mc.player != null) { + this.oldSlot = mc.player.getInventory().selected; + this.rots.setYawPitch(mc.player.getYRot() - 180.0f, mc.player.getXRot()); + this.lastRots.setYawPitch(mc.player.yRotO - 180.0f, mc.player.xRotO); + ... + } + super.onEnable(); +} +``` -- `ModeSetting("Mode", "Glow", "Outlined 2D")` —— "Glow" 模式即对应 Naven 整个 Glow 模块。 -- `isGlowing(Entity)` 分支顺序 `Player → Animal → Mob → ItemEntity → Arrow` 与 Naven `shouldGlow` 的 `Player → ItemEntity → Mob → Animal → Arrow` 高度一致,仅重排。 -- 字段 `playersSetting / mobsSetting / animalsSetting / itemsSetting / arrowsSetting` 对应 Naven `players / mobs / animals / items / arrows`,名字 + 类型 + 默认值(Players=true,其余 false)一一对应。 +把 yaw 减 180 度作为脚手架初始 yaw(让玩家朝身后放方块)是 Naven 自定义做法;4 个字段、顺序、`yRotO/xRotO` 完全对应。 -### Zen `render/ChestESP.java` vs Naven `render/ChestESP.java` +### 模式名 `"Normal" / "Telly Bridge" / "Keep Y"` + Eagle/Snap 的可见性 lambda -**关键证据**: +```java +// Naven Scaffold.java +public ModeValue mode = ValueBuilder.create(this, "Mode").setDefaultModeIndex(0).setModes("Normal", "Telly Bridge", "Keep Y")... +public BooleanValue eagle = ...("Eagle").setDefaultBooleanValue(true).setVisibility(() -> mode.isCurrentMode("Normal"))... +public BooleanValue snap = ...("Snap").setDefaultBooleanValue(true).setVisibility(() -> mode.isCurrentMode("Normal"))... +public BooleanValue renderItemSpoof = ...("Render Item Spoof").setDefaultBooleanValue(true)... +``` -- 静态字段 `chestColor / openedChestColor` 与初始值 `{0,1,0}` / `{1,0,0}` 完全一致。 -- `renderBoundingBoxes` 字段名 + `CopyOnWriteArrayList` 类型完全一致;`openedChestPositions` 仅是 Naven `openedChests` 加后缀。 -- `onPacket` 中 `ClientboundBlockEventPacket` 的 `block == CHEST || block == TRAPPED_CHEST && B0 == 1 && B1 == 1` 判定与 Naven 同条件同顺序。 -- `getChestAabb` / `getChestBox` 内部逻辑一一对应:`hasProperty(ChestBlock.TYPE)` 返回 null、`ChestType.LEFT` 跳过、`SINGLE` 之外用 `getConnectedDirection + min/max`。 -- 着色 `RenderSystem.setShaderColor(fArray[0], fArray[1], fArray[2], 0.25f)` 的 alpha = 0.25 与 Naven `0.25F` 相同。 +```java +// Zen Scaffold.java +public final ModeSetting mode = new ModeSetting("Mode", "Normal", "Telly Bridge", "Old Telly", "Keep Y").withDefault("Normal"); +public final BooleanSetting eagle = new BooleanSetting("Eagle", true, () -> this.mode.is("Normal")); +public final BooleanSetting snap = new BooleanSetting("Snap", true, () -> this.mode.is("Normal")); +public final BooleanSetting renderItemSpoof = new BooleanSetting("Render Item Spoof", true); +``` -### Zen `render/Compass.java` vs Naven `render/Compass.java` +模式列表 `Normal / Telly Bridge / Keep Y` 三个字符串字面量和顺序完全一致;Eagle/Snap 都用 `visibility = mode == "Normal"` 这种 lambda;`"Render Item Spoof"` 这种特定字符串字面量在两边都用作选项名。 -**关键证据**: +### `doSnap` 内 `+ nextFloat(0, 0.5F) - 0.25F` 抖动指纹 -- 两个 BooleanSetting `"Compass Only"`(默认 true)、`"No Player Only"`(默认 true)名字、默认值完全一致。 -- 字段 `spawnPosition / renderYaw / renderX / renderZ` 命名完全一致。 -- `getSpawnPosition` 使用 `dimensionType().natural() ? getSharedSpawnPos() : null` 逻辑相同。 -- **角度公式字面一致**:`Math.toDegrees(Math.atan2(spawn.z - renderZ, spawn.x - renderX)) - 90 - renderYaw`。 -- 提前 return 顺序 `compassOnly → hasPlayer/noPlayerOnly → spawnPosition == null` 完全一致。 +```java +// Naven Scaffold.java +if (!shouldPlaceBlock) { + rots.setX(mc.player.getYRot() + RandomUtils.nextFloat(0, 0.5F) - 0.25F); +} +``` -### Zen `render/FullBright.java` vs Naven `render/FullBright.java` +```java +// Zen Scaffold.java +if (!lookingAtBlock && mc.player.tickCount % 4 == 0) { + this.rots.setYaw(mc.player.getYRot() + RandomUtils.nextFloat(0.0f, 0.5f) - 0.25f); +} +``` -**关键证据**:唯一 Setting `"Brightness"` 名字、类型、最小值 0 与 Naven 一致;min/max/step 语义相同(仅把 0–1 缩放到 0–100)。 +用 `nextFloat(0, 0.5) - 0.25` 来得到 [-0.25, +0.25] 抖动量的写法是 Naven 风格(直接 `nextFloat(-0.25, 0.25)` 更自然)。这种"减半法"指纹两边完全一致。 -### Zen `render/NameTags.java` vs Naven `render/NameTags.java` +### `isOnBlockEdge` —— 再来一次 -**关键证据**:模块同名、`Category.RENDER`、事件分发 `onRender / onRender2D / onPacket` 多事件订阅模式一致;`NumberSetting("Scale", 0.3, 0.1, 1.0, 0.01)` 的 **默认 0.3、step 0.01 与 Naven 完全相同**,只把 max 从 0.5 放宽到 1.0。共 6 个 Setting 数量与 Naven 同。 +```java +// Naven Scaffold.java +public static boolean isOnBlockEdge(float sensitivity) { + return !mc.level.getCollisions(mc.player, mc.player.getBoundingBox().move(0.0, -0.5, 0.0).inflate(-sensitivity, 0.0, -sensitivity)).iterator().hasNext(); +} +... +mc.options.keyShift.setDown(mc.player.isOnGround() && isOnBlockEdge(0.3F)); +``` -**改动**:把真正实现外包给 `NameTagStyle` 策略类("Opal" / "Simple"),多数 Setting 名换成新含义掩饰。`Scale` 设置的默认值/step 露馅。 +```java +// Zen Scaffold.java +public static boolean isOnBlockEdge(float inflate) { + return !mc.level.getCollisions(mc.player, + mc.player.getBoundingBox().move(0.0, -0.5, 0.0).inflate(-inflate, 0.0, -inflate)) + .iterator().hasNext(); +} +... +mc.options.keyShift.setDown(mc.player.onGround() && isOnBlockEdge(0.3f)); +``` -### Zen `render/Projectiles.java` vs Naven `render/Projectile.java` - -**关键证据**: - -- 5 个 BooleanSetting **完全同名同默认**:`"Show Arrows"=true / "Show Pearls"=true / "Show Potions"=false / "Show Eggs"=false / "Show Snowballs"=false`。 -- 颜色字段一一对应且 **RGB 字面量完全相同**:`ThrownEnderpearl → new Color(173, 12, 255)`、`ThrownEgg → new Color(255, 238, 154)`、`Snowball → new Color(255, 255, 255)`。 -- `getProjectileGravity` 五个字面常量完全相同:`Bow/Crossbow → 0.05, Potion → 0.4, FishingRod → 0.15, Trident → 0.015, default → 0.03`。 -- `isThrowable` 类型集合 9 个 Item 类型相同顺序。 -- `simulateTrajectory` 与 Naven `getPath` 同算法:`1000` 步迭代、空气阻力 `0.99`、起点 `getEyeHeight - 0.1`、`bowPower = (p*p + p*2)/3 * 3`。 -- `getProjectileInfos` 中 `showArrows → arrowsColor / showPotions → potionsColor / showPearls → enderPearlColor / showEggs → eggColor / showSnowballs → snowballColor` 顺序完全一致。 -- 类名 `BasicProjectileData / EntityArrowData / EntityPotionData → ClassEspColor / ArrowEspColor / PotionEspColor` 是包装了一层的重命名。 - -### Zen `render/Watermark.java` vs Naven `render/HUD.java` - -**对应关系**:Zen 把 Naven 的 `HUD` 模块拆分——`Watermark` 只保留水印部分(`Style: Neverlose / DynamicIsland`),ArrayList / Notification 部分迁去 `shit.zen.hud.*`。模块名 `"Watermark"` 直接源自 Naven 的设置名 `"Water Mark"`。 - -### 其他同名 / 空壳 - -- `NameProtect`:同算法(`StringUtils.replace`)+ 重写事件路径,加 `Random` 模式。 -- `NoHurtCam`:两边都是空壳(实际功能在 mixin 中由模块名查询触发)。 -- `ItemTags`:Zen 端是空壳(13 行)。 +Zen Scaffold 把 SafeWalk 的同一份 `isOnBlockEdge` 在自己类里又粘贴了一遍,连 `0.3f` 阈值与 `keyShift.setDown(onGround && isOnBlockEdge(0.3f))` 这一整行表达式都一样。 --- -## 五、框架级抄袭(事件 / 模块 / 设置 / 命令 / 工具 / UI) +## FastWeb -### 5.1 事件系统 +### cobweb 阈值 + magic vector `(0.88, 1.88, 0.88)` -**对应关系**:Zen `EventBus` = Naven `EventManager`(功能 1:1,DarkMagician6 EventAPI 的同源派生)。 +```java +// Naven FastWeb.java +public void onStuck(EventStuckInBlock e) { + if (e.getState().getBlock() == Blocks.COBWEB) { + playerInWebTick = mc.player.tickCount; + ticksInWeb ++; + if (ticksInWeb > 5) { + Vec3 newSpeed = new Vec3(0.88, 1.88, 0.88); + e.setStuckSpeedMultiplier(newSpeed); + } + } +} +``` -**关键证据**: +```java +// Zen FastWeb.java +public void onStuckInBlock(StuckInBlockEvent stuckInBlockEvent) { + if (stuckInBlockEvent.getBlockState().getBlock() == Blocks.COBWEB && mc.player != null) { + this.lastWebTick = mc.player.tickCount; + ++this.webCount; + if (this.webCount > 5) { + Vec3 vec3 = new Vec3(0.88, 1.88, 0.88); + stuckInBlockEvent.setMotion(vec3); + } + } +} +``` -- 两个类都用 `Map, List<...>>` 作为唯一存储,注册时 `computeIfAbsent` + `CopyOnWriteArrayList`,注册完调用 `sortListValue / sortByPriority`;Zen 把内部 `MethodData` 改成 record `ListenerEntry`,字段顺序 `source/target/priority` 完全一致。 -- 排序逻辑逐行同形:外层遍历 `PRIORITIES`,内层把 `priority() == priority` 的项追加到新 `CopyOnWriteArrayList`,再 `put` 覆盖原 entry——这是 DarkMagician6 EventAPI 的特有写法。 -- **优先级数值/数组顺序完全一致**:`HIGHEST=0, HIGH=1, NORMAL/MEDIUM=2, LOW=3, LOWEST=4` + `PRIORITIES = {HIGHEST, HIGH, NORMAL, LOW, LOWEST}`。Zen 仅把 `MEDIUM` 改名 `NORMAL`,类名 `Priority` 改成 `EventPriority`。 -- `@EventTarget` 注解:同样 `@Documented @Target(METHOD) @Retention(RUNTIME)`,**方法签名 `byte value() default 2` 完全相同**。 -- `call(...)` 分发:先取 `dataList`,判空,再 `instanceof` 取消类(Naven `EventStoppable.isStopped()`、Zen `AbstractCancellable.isCancelled()`),循环里 `break` 提前退出——分支结构、变量命名节奏完全一致。 +`> 5` 阈值、`(0.88, 1.88, 0.88)` 三元魔法常量、记录 `tickCount` 然后递增计数器的顺序——三层细节全部撞车。0.88 / 1.88 不是 Minecraft 物理常量,是 Naven 独有的口味。 -### 5.2 Module 框架 +### `onMotion` 中的重置逻辑 -**关键证据**: +```java +// Naven FastWeb.java +public void onMotion(EventMotion e) { + if (e.getType() == EventType.POST) { + if (playerInWebTick < mc.player.tickCount) { + ticksInWeb = 0; + } + } +} +``` -- `Module.setEnabled(boolean)` 实现完全同构:`if (enabled)` 分支 → `this.enabled = true` → `eventManager.register(this)` → `onEnable()`;`else` 分支 → `this.enabled = false` → `eventManager.unregister(this)` → `onDisable()`。`toggle()` 都写成 `setEnabled(!enabled)`。 -- 字段 `name / category / key(keyCode) / enabled` 全部存在,都用 Lombok `@Getter`。 -- `ModuleManager.onKey(KeyEvent)`:`@EventTarget` 遍历 `modules`,`if (module.getKey() == e.getKey()/getKeyCode()) module.toggle()`,逻辑完全一致。 -- 异常 `ModuleNotFoundException` ↔ `NoSuchModuleException` 是直译。 -- `Category` enum 顺序保持 `COMBAT, MOVEMENT, ..., RENDER, ..., MISC`,`fromString` 静态方法的 `equalsIgnoreCase` + 默认返回 `COMBAT` 是 Naven 没有但典型的同源扩展。 +```java +// Zen FastWeb.java +public void onMotion(MotionEvent motionEvent) { + if (motionEvent.isPre() && mc.player != null && this.lastWebTick < mc.player.tickCount) { + this.webCount = 0; + } +} +``` -### 5.3 设置 / 值系统 - -**关键证据**: - -- 抽象基类 4 个字段一一对应:`name`、`value/currentValue`、`visibility (Supplier)`、子类持有的 `update/onChanged` 回调。 -- Naven `Value.isVisible()` 检查 `visibility == null || visibility.get()`;Zen 改名 `SettingVisibility`(仍是 `() -> true` 的 functional interface),构造时 `this.visibility = () -> true`——形状一致。 -- `ModeValue / ModeSetting`:都用 `String[]` 存模式列表,都提供"按字符串判断当前模式"的便捷方法(Naven `isCurrentMode`、Zen `is`),`getCurrentMode()` 写成 `values[currentValue]` / `getValue()`。 -- `NumberSetting` 字段 `min/max/step` 与 `FloatValue.minValue/maxValue/step` 直接对应;构造参数顺序一致。 - -### 5.4 命令系统 - -**关键证据**: - -- 命令名、别名一致:`bind / b`、`toggle / t`、`config`、`language`——四条命令的关键字与 Naven 完全相同。 -- `Command` 抽象类暴露同样两个抽象方法:`onCommand(String[])` 和 `onTab(String[])`。Naven 用 `@CommandInfo` 注解填字段,Zen 改成构造函数参数,但 `name/aliases` 字段命名一致。 -- **提示文案逐字符相同**:`"Bound X to Y."`、`"Unbound X."`、`"Invalid module."`、`"Invalid key."`、`"Usage: .bind [key]"`。 -- `onTab` 实现完全相同的一行 stream:`getModules().stream().map(Module::getName).filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase())).toArray(String[]::new)`。 -- `CommandManager`:前缀 `.`、匹配后 `cancel/setCancelled`、按空格切分、第一段 toLowerCase 查 `aliasMap`、再用 `System.arraycopy` 切剩余参数——同样结构。 - -### 5.5 工具类 - -**关键证据**: - -- `ChunkUtil.getLoadedChunks`:与 Naven `ChunkUtils` 几乎逐行相同——`radius = max(2, render distance) + 3`、`min/max ChunkPos`、`Stream.iterate` 的 lambda(`x++ > max.x` 则 `x = min.x; z++`)、**`throw new IllegalStateException("Stream limit didn't work.")`(异常文本完全相同)**、`limit((long) diameter*diameter)`、`filter(hasChunk).map(getChunk).filter(Objects::nonNull)`。 -- `BlockUtil`:`canBeClicked(pos)` 返回 `getVoxelShape(pos) != Shapes.empty()`、`getBoundingBox(pos)` 返回 `getVoxelShape(pos).bounds().move(pos)`——同名同实现。 -- `RotationUtil` 保留了 Naven 独有的、对工具类而言**非常诡异的 `import org.antlr.v4.runtime.misc.OrderedHashSet`**,并实际使用 `OrderedHashSet`,与 Naven `RotationUtils` 一致。 -- "Sensitivity GCD" 公式:Naven `getFixedRotation` 里 `f = sensitivity * 0.6F + 0.2F; gcd = f*f*f*1.2F`。Zen 把它搬到 `Rotation.snapToSensitivity`:`float scaled = sensitivity * 0.6f + 0.2f; float step = scaled*scaled*scaled*1.2f`,数值常量、运算顺序完全相同。 -- `diffCalcVector / rotationTo`:都用 `Math.toDegrees(atan2(dz,dx)) - 90.0f` 求 yaw,`-Math.toDegrees(atan2(dy,diffXZ))` 求 pitch,再 `wrapDegrees`——同一公式同一写法。 - -### 5.6 UI(ClickGui) - -**对应关系**:Zen `gui/OldClickGui` + `gui/legacy/*` ≈ Naven `ui/ClickGUI`(视觉风格不同,但同样借助上面 5 个子系统)。 - -**关键证据**: - -- 重写 `Screen` 的同一组生命周期方法。 -- `onClose` 调用 `configManager.saveAll()`——与 Naven `ClickGUI` 关闭时通过 `FileManager.save()` 持久化设置的行为对应。 -- 按 `Category.values()` 枚举展开面板,构造 `CategoryPanel(x, 20, 140, 20, category)`——20/140 这种典型 GUI 尺寸常数和 Naven 默认布局一致量级。 -- `ModuleButton / CategoryPanel / BooleanComponent / ModeComponent / NumberComponent` 的拆分粒度与 Naven 在 `ClickGUI` 单文件内分块渲染时的命名一致。 +`lastTick < player.tickCount` 条件 + reset 计数到 0 的逻辑完全相同(仅 PRE/POST 差异)。 --- -## 总结 +## Stuck -OpenZen 的整体架构(事件总线、模块系统、设置系统、命令系统、工具类、ClickGui)几乎完全沿用 Naven 客户端的代码,外加大量同名功能模块。**改动主要集中在重命名、拆分、扩展三件事,核心逻辑、字符串字面量、魔法常数、特征 import 大量保留**——尤其是那个 `RotationUtil` 里完全用不到的 `org.antlr.v4.runtime.misc.OrderedHashSet`、`Stuck` 模块的 `+ 1337`、`Projectiles` 的颜色三元组、`InventoryManager` 的中文 `"点击使用"`、`ChunkUtil` 的 `"Stream limit didn't work."` 异常文案,这些都是无法通过功能重写解释的"抄袭指纹"。 +### `+ 1337` 魔法位移 -Zen 的反混淆产物在符号层面(类/方法/字段名)经过重新命名,但**字符串字面量层面没有清洗**,这一层是构成抄袭证据最强的源头。 +```java +// Naven Stuck.java +if (tryDisable) { + NetworkUtils.sendPacketNoEvent(new ServerboundMovePlayerPacket.Pos(mc.player.getX() + 1337, mc.player.getY(), mc.player.getZ() + 1337, mc.player.isOnGround())); + while (!packets.isEmpty()) { + NetworkUtils.sendPacketNoEvent(packets.poll()); + } + tryDisable = false; +} +``` + +```java +// Zen Stuck.java +if (this.pendingDisable) { + if (this.modeSetting.is("Delay")) { + PacketUtil.sendQueued(new ServerboundMovePlayerPacket.Pos(mc.player.getX() + 1337.0, mc.player.getY(), mc.player.getZ() + 1337.0, mc.player.onGround())); + } ... + while (!this.pongQueue.isEmpty()) { + PacketUtil.sendQueued((Packet) this.pongQueue.poll()); + } + ... + this.pendingDisable = false; +} +``` + +把玩家 X/Z 同时加 **1337** 这种"骇客致敬"魔法常量、外加紧跟其后的 `pongQueue.poll()` 循环——这就是经典 Naven 指纹。 + +### `onPacket` 四个 instanceof 分支链顺序完全相同 + +```java +// Naven Stuck.java +public void onPacket(EventPacket e) { + if (e.getPacket() instanceof ServerboundMovePlayerPacket) { + e.setCancelled(true); + } else if (e.getPacket() instanceof ServerboundPongPacket) { + packets.offer((ServerboundPongPacket) e.getPacket()); + e.setCancelled(true); + } else if (e.getPacket() instanceof ServerboundUseItemPacket || e.getPacket() instanceof ServerboundPlayerActionPacket) { + packet = e.getPacket(); + stage = 1; + e.setCancelled(true); + } else if (e.getPacket() instanceof ClientboundPlayerPositionPacket) { + while (!packets.isEmpty()) { NetworkUtils.sendPacketNoEvent(packets.poll()); } + stage = 3; + toggle(); + } +} +``` + +```java +// Zen Stuck.java +public void onPacket(PacketEvent packetEvent) { + ... + if (rawPacket instanceof ServerboundMovePlayerPacket movePacket) { ...; packetEvent.setCancelled(true); } + else if (packetEvent.getPacket() instanceof ServerboundPongPacket) { + this.pongQueue.offer((ServerboundPongPacket)packetEvent.getPacket()); + packetEvent.setCancelled(true); + } else if (packetEvent.getPacket() instanceof ServerboundUseItemPacket || packetEvent.getPacket() instanceof ServerboundPlayerActionPacket) { + this.capturedPacket = packetEvent.getPacket(); + this.stuckState = 1; + packetEvent.setCancelled(true); + } else if (packetEvent.getPacket() instanceof ClientboundPlayerPositionPacket && this.modeSetting.is("Delay")) { + while (!this.pongQueue.isEmpty()) { PacketUtil.sendQueued(...); } + this.stuckState = 3; + this.setEnabled(false); + } +} +``` + +四个 instanceof 分支顺序完全相同:`Move → Pong → (UseItem/PlayerAction) → ClientboundPos`。每个分支内部动作(offer/cancel、记 stage=1、最后 stage=3+toggle)也完全平行。 + +### `shouldRotate` 中 `BowlFoodItem || BowItem` 双过滤 + +```java +// Naven Stuck.java +if (packet instanceof ServerboundUseItemPacket) { + ServerboundUseItemPacket blockPlacement = (ServerboundUseItemPacket) packet; + ItemStack item = mc.player.getItemInHand(blockPlacement.getHand()); + if (item.getItem() instanceof BowlFoodItem || item.getItem() instanceof BowItem) { + return false; + } + return true; +} else if (packet instanceof ServerboundPlayerActionPacket) { + ... + if (playerDigging.getAction() == ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM) { + if (mc.player.getUseItem().getItem() instanceof BowItem) { return true; } + } + return false; +} +``` + +```java +// Zen Stuck.java +if (this.capturedPacket instanceof ServerboundUseItemPacket useItemPacket) { + ItemStack heldStack = mc.player.getItemInHand(useItemPacket.getHand()); + return !(heldStack.getItem() instanceof BowlFoodItem) && !(heldStack.getItem() instanceof BowItem); +} +if (this.capturedPacket instanceof ServerboundPlayerActionPacket actionPacket) { + return actionPacket.getAction() == ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM + && mc.player.getUseItem().getItem() instanceof BowItem; +} +``` + +`BowlFoodItem + BowItem` 的组合过滤是 Naven 自创口味(碗食物本就极少有人在这种场景去黑名单)。配上 `RELEASE_USE_ITEM + BowItem` 的分支,几乎是 1:1 翻译。 + +--- + +## AutoMLG + +### 触发结构(落地预测 + 热栏扫描 + 切槽放水) + +```java +// Naven AutoMLG.java +public static boolean isOnGround(double height) { + Iterable collisions = mc.level.getBlockCollisions(mc.player, mc.player.getBoundingBox().move(0.0D, height, 0.0D)); + return collisions.iterator().hasNext(); +} +... +for (int i = 0; i < 9; i++) { + ItemStack item = mc.player.getInventory().getItem(i); + if (!item.isEmpty() && item.getItem() == Items.WATER_BUCKET) { + originalSlot = mc.player.getInventory().selected; + mc.player.getInventory().selected = i; + ... + } +} +``` + +```java +// Zen AutoMLG.java +private boolean hasSolidBelow(BlockPos blockPos) { + return this.isSolidNonMenu(blockPos.below()) || this.isSolidNonMenu(blockPos.below(2)); +} +private boolean isSolidNonMenu(BlockPos blockPos) { + BlockState blockState = mc.level.getBlockState(blockPos); + boolean hasCollision = !blockState.getCollisionShape(mc.level, blockPos).isEmpty(); + ... +} +... +slot = ItemUtil.findItemInRange(0, 9, Items.WATER_BUCKET); +if (slot < 0) return; +... +this.selectSlot(slot); // 内部就是 selected=oldSlot; selected=slot +``` + +固定 0..9 热栏扫描 + `WATER_BUCKET` + 保存 `originalSlot` 再切换 —— 结构相同,Zen 仅把 for 循环抽成 `ItemUtil.findItemInRange` 工具函数。`Fall Distance` 默认 3.0 / 步长 0.1f 两个 NumberSetting 字面量参数也完全一致。 + +--- + +## SafeWalk + +### `isOnBlockEdge` 静态方法 —— 字符级雷同 + +```java +// Naven SafeWalk.java +public static boolean isOnBlockEdge(float sensitivity) { + return !mc.level.getCollisions(mc.player, mc.player.getBoundingBox().move(0.0, -0.5, 0.0).inflate(-sensitivity, 0.0, -sensitivity)).iterator().hasNext(); +} +``` + +```java +// Zen SafeWalk.java +public static boolean isOnBlockEdge(float inset) { + return !mc.level.getCollisions(mc.player, mc.player.getBoundingBox().move(0.0, -0.5, 0.0).inflate(-inset, 0.0, -inset)).iterator().hasNext(); +} +``` + +除参数名(`sensitivity` → `inset`)外完全一字不差。`move(0.0, -0.5, 0.0).inflate(-x, 0.0, -x).iterator().hasNext()` 这一连串调用是经典 Naven 指纹。 + +### `onDisable` 恢复 keyShift + +```java +// Naven SafeWalk.java +public void onDisable() { + boolean isHoldingShift = InputConstants.isKeyDown(mc.getWindow().getWindow(), mc.options.keyShift.getKey().getValue()); + mc.options.keyShift.setDown(isHoldingShift); +} +``` + +```java +// Zen SafeWalk.java +public void onDisable() { + boolean keyDown = InputConstants.isKeyDown(mc.getWindow().getWindow(), mc.options.keyShift.getKey().getValue()); + mc.options.keyShift.setDown(keyDown); +} +``` + +仅变量名不同,控制流、API 调用、参数顺序完全一致。 + +--- + +## Disabler + +### `duplicateRotPlace` 算法 —— `> 2` + `< 0.0001` 双阈值 + `rotated` 状态机 + +```java +// Naven Disabler.java +if (packet.hasRotation()) { + float lastPlayerYaw = playerYaw; + playerYaw = packet.getYRot(0); + deltaYaw = Math.abs(playerYaw - lastPlayerYaw); + rotated = true; + // Guess what will happen if you placed the block in current tick + if (deltaYaw > 2) { + float xDiff = Math.abs(deltaYaw - lastPlacedDeltaYaw); + if (xDiff < 0.0001) { + log("Disabling DuplicateRotPlace!"); + ... + } + } +} else if (e.getPacket() instanceof ServerboundUseItemOnPacket) { + if (rotated) { + lastPlacedDeltaYaw = deltaYaw; + rotated = false; + } +} +``` + +```java +// Zen Disabler.java +if (movePacket.hasRotation()) { + float prevYaw = this.currentYaw; + float prevPitch = this.currentPitch; + this.currentYaw = ReflectionUtil.getYRot(movePacket); + this.currentPitch = ReflectionUtil.getXRot(movePacket); + this.yawDiff = Math.abs(this.currentYaw - prevYaw); + this.pitchDiff = Math.abs(this.currentPitch - prevPitch); + this.rotated = true; + float yawDelta; + if (this.yawDiff > 2.0f && (double)(yawDelta = Math.abs(this.yawDiff - this.lastPlacedYawDiff)) < 1.0E-4) { + ... + } +} else if (packet instanceof ServerboundUseItemOnPacket && this.rotated) { + this.lastPlacedYawDiff = this.yawDiff; + this.lastPlacedPitchDiff = this.pitchDiff; + this.rotated = false; +} +``` + +完全一致的算法:取上一 yaw → 算 deltaYaw → `> 2` 阈值 → 算与上次放置 deltaYaw 的差 → `< 0.0001` (1.0E-4) 判定 → `USE_ITEM_ON` 时把当前 delta 写入 `lastPlacedDeltaYaw` 并清零 `rotated`。Zen 仅把单一 yaw 扩展为 yaw + pitch 两份。 + +--- + +## AutoTools + +### `getBestTool` 算法 —— `efficiencyLevel * efficiencyLevel + 1` 公式 + +```java +// Naven AutoTools.java +for (int index = 0; index < 9; index++) { + ItemStack itemStack = mc.player.getInventory().getItem(index); + if (InventoryUtils.isGodItem(itemStack)) continue; + if (!itemStack.isEmpty() && !blockState.isAir() && (!(itemStack.getItem() instanceof SwordItem) || block instanceof WebBlock)) { + float strVsBlock = itemStack.getItem().getDestroySpeed(itemStack, blockState); + if (strVsBlock > 1 && !(block instanceof OreBlock || block instanceof RedStoneOreBlock)) { + int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_EFFICIENCY, itemStack); + if (i > 0) { + strVsBlock += (float) (i * i + 1); + } + } + if (strVsBlock > dmg) { + slot = index; + dmg = strVsBlock; + } + } +} +if (dmg > 1F) return slot; +return -1; +``` + +```java +// Zen AutoTools.java +for (int i = 0; i < 9; ++i) { + int efficiencyLevel; + ItemStack itemStack = mc.player.getInventory().getItem(i); + if (ItemUtil.isWeaponItem(itemStack) || itemStack.isEmpty() || blockState.isAir() + || itemStack.getItem() instanceof SwordItem && !(block instanceof WebBlock)) continue; + float destroySpeed = itemStack.getItem().getDestroySpeed(itemStack, blockState); + if (destroySpeed > 1.0f && !(block instanceof DropExperienceBlock) && !(block instanceof RedStoneOreBlock) + && (efficiencyLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_EFFICIENCY, itemStack)) > 0) { + destroySpeed += (float)(efficiencyLevel * efficiencyLevel + 1); + } + if (!(destroySpeed > bestSpeed)) continue; + bestSlot = i; + bestSpeed = destroySpeed; +} +if (bestSpeed > 1.0f) return bestSlot; +return -1; +``` + +循环上限 `9`、`WebBlock` 例外、附魔加成公式 **`i * i + 1`**、对矿石方块跳过加成、`> 1.0f` 的最终阈值、不命中返 `-1`,逐行对应。`OreBlock → DropExperienceBlock` 只是 1.17 → 1.19 Mojang 类名重命名后的同义替换。 + +--- + +## ChestStealer + +### 三连 `Component.translatable("container.chest / chestDouble / enderchest")` + 兜底 `"Chest"` + +```java +// Naven ChestStealer.java (1.17 API) +String chestTitle = container.getTitle().getString(); +String chest = new TranslatableComponent("container.chest").getString(); +String largeChest = new TranslatableComponent("container.chestDouble").getString(); +String enderChest = new TranslatableComponent("container.enderchest").getString(); +if (chestTitle.equals(chest) || chestTitle.equals(largeChest) || chestTitle.equals("Chest") + || (pickEnderChest.getCurrentValue() && chestTitle.equals(enderChest))) { +``` + +```java +// Zen ChestStealer.java +String title = containerScreen.getTitle().getString(); +String chestTitle = Component.translatable("container.chest").getString(); +String doubleChestTitle = Component.translatable("container.chestDouble").getString(); +String enderChestTitle = Component.translatable("container.enderchest").getString(); +ChestMenu chestMenu = containerScreen.getMenu(); +if (this.chestSetting.getValue() && (title.equals(chestTitle) || title.equals(doubleChestTitle) || title.equals("Chest"))) { + ... +} else if (this.enderChestSetting.getValue() && title.equals(enderChestTitle) && ...) { +``` + +完全相同的三个翻译键 + 多出来的硬编码英文兜底 `"Chest"` + 单独把 ender chest 拎出来做开关。这种组合不是写 hack 的标准写法 —— Zen 把 1.17 `new TranslatableComponent(...)` 机械替换成 1.19+ 的 `Component.translatable(...)`。 + +--- + +## InventoryManager + +### 中文字面量 `"点击使用"` —— 终极铁证 + +```java +// Naven InventoryCleaner.java +public boolean isItemUseful(ItemStack stack) { + if (stack.isEmpty()) return false; + if (InventoryUtils.isGodItem(stack)) return true; + if (stack.getDisplayName().getString().contains("点击使用")) { + return true; + } + if (stack.getItem() instanceof ArmorItem) { ... } + else if (stack.getItem() instanceof SwordItem) { return InventoryUtils.getBestSword() == stack; } + else if (stack.getItem() instanceof PickaxeItem) { return InventoryUtils.getBestPickaxe() == stack; } + else if (stack.getItem() instanceof AxeItem && !InventoryUtils.isSharpnessAxe(stack)) { + return InventoryUtils.getBestAxe() == stack; + } + ... +} +``` + +```java +// Zen InventoryManager.java +public boolean isUsefulItem(ItemStack itemStack) { + if (itemStack.isEmpty()) return false; + if (ItemUtil.isWeaponItem(itemStack)) return true; + if (itemStack.getDisplayName().getString().contains("点击使用")) { + return true; + } + if (itemStack.getItem() == Items.COBWEB) return true; + Item item = itemStack.getItem(); + if (item instanceof ArmorItem armorItem) { ... } + if (itemStack.getItem() instanceof SwordItem) { return ItemUtil.getBestSword() == itemStack; } + if (itemStack.getItem() instanceof PickaxeItem) { return ItemUtil.getBestPickaxe() == itemStack; } + if (itemStack.getItem() instanceof AxeItem && !ItemUtil.isLegitAxe(itemStack)) { + return ItemUtil.getBestAxe() == itemStack; + } + ... +} +``` + +`contains("点击使用")` 是 Naven 作者针对国服服务端(hypixel.cn / 网易我的世界类)放进物品名的中文标记,与英文环境的 Minecraft **毫无关联**。Zen 一字不差搬了过来,连后续的 `getBestSword() == stack` 这种"引用比较"风格的奇怪判定也照抄。单凭这一行就足以认定整段 `isUsefulItem` 是直接复制粘贴的。 + +### 四种 offhand 模式与"水/岩浆桶上限 = 1" + +```java +// Zen InventoryManager.java +private final ModeSetting offhandItemSetting = new ModeSetting("Offhand Items", + "Golden Apple", "Fishing Rod", "None").withDefault("Projectile"); +... +if ("Golden Apple".equals(offhandPreference)) { if (this.handleGoldenAppleOffhand()) return true; } +else if ("Projectile".equals(offhandPreference)) { if (this.handleProjectileOffhand()) return true; } +else if ("Fishing Rod".equals(offhandPreference)) { if (this.handleFishingRodOffhand()) return true; } +else if ("Block".equals(offhandPreference)) { if (this.handleBlockOffhand()) return true; } + +public static int getMaxWaterBuckets() { return 1; } +public static int getMaxLavaBuckets() { return 1; } +``` + +Naven 的 `isItemUseful` 里也分别处理 GoldenApple / Projectile (Snowball + Egg) / FishingRod / Block 四类,Zen 直接把它们提升为四个 `handleXxxOffhand()` 函数,并把 Naven 的 `getWaterBucketCount() / getLavaBucketCount()` 设置项简化成硬编码 `return 1;`。 + +--- + +## ChestESP + +### 箱子打开判定 —— 四重条件 + +```java +// Naven ChestESP.java +if ((packet.getBlock() == Blocks.CHEST || packet.getBlock() == Blocks.TRAPPED_CHEST) && packet.getB0() == 1 && packet.getB1() == 1) { + openedChests.add(packet.getPos()); +} +``` + +```java +// Zen ChestESP.java +if ((clientboundBlockEventPacket.getBlock() == Blocks.CHEST || clientboundBlockEventPacket.getBlock() == Blocks.TRAPPED_CHEST) && clientboundBlockEventPacket.getB0() == 1 && clientboundBlockEventPacket.getB1() == 1) { + this.openedChestPositions.add(clientboundBlockEventPacket.getPos()); +``` + +四重条件(CHEST、TRAPPED_CHEST、b0 == 1、b1 == 1)完全一样,魔法常数 `1` 都来自 ChestBlockEntity.lidEvent。 + +### `getChestBox` 双箱合并算法 + +```java +// Naven ChestESP.java +ChestType chestType = state.getValue(ChestBlock.TYPE); +if (chestType == ChestType.LEFT) { + return null; +} +BlockPos pos = chestBE.getBlockPos(); +AABB box = BlockUtils.getBoundingBox(pos); +if (chestType != ChestType.SINGLE) { + BlockPos pos2 = pos.relative(ChestBlock.getConnectedDirection(state)); + if (BlockUtils.canBeClicked(pos2)) { + AABB box2 = BlockUtils.getBoundingBox(pos2); + box = box.minmax(box2); +``` + +```java +// Zen ChestESP.java +ChestType chestType = blockState.getValue(ChestBlock.TYPE); +if (chestType == ChestType.LEFT) { + return null; +} +BlockPos blockPos2 = chestBlockEntity.getBlockPos(); +AABB aABB = BlockUtil.getBoundingBox(blockPos2); +if (chestType != ChestType.SINGLE && BlockUtil.canBeClicked(blockPos = blockPos2.relative(ChestBlock.getConnectedDirection(blockState)))) { + AABB aABB2 = BlockUtil.getBoundingBox(blockPos); + aABB = aABB.minmax(aABB2); +``` + +`LEFT` 跳过、`SINGLE` 单箱、`getConnectedDirection` 双箱合并、`minmax` 合并 AABB —— 整段算法逐行复刻。 + +### 箱子颜色常量 + +```java +// Naven ChestESP.java +private static final float[] chestColor = {0, 1, 0}; +private static final float[] openedChestColor = {1, 0, 0}; +``` + +```java +// Zen ChestESP.java (static init) +chestColor = new float[]{0.0f, 1.0f, 0.0f}; +openedChestColor = new float[]{1.0f, 0.0f, 0.0f}; +``` + +两个 `float[]` 颜色常数(绿色未开、红色已开)原值搬过来,连透明度 `0.25f` 也一致。 + +--- + +## Compass + +### yaw 角度计算公式 + +```java +// Naven Compass.java +float yaw = (float) (Math.toDegrees(Math.atan2(spawnPosition.getZ() - renderZ, spawnPosition.getX() - renderX)) - 90 - renderYaw); +float x = mc.getWindow().getGuiScaledWidth() / 2f; +float y = mc.getWindow().getGuiScaledHeight() / 2f; +``` + +```java +// Zen Compass.java +float angle = (float)(Math.toDegrees(Math.atan2((double)this.spawnPosition.getZ() - this.renderZ, (double)this.spawnPosition.getX() - this.renderX)) - 90.0 - (double)this.renderYaw); +float centerX = (float)mc.getWindow().getGuiScaledWidth() / 2.0f; +float centerY = (float)mc.getWindow().getGuiScaledHeight() / 2.0f; +``` + +`atan2(Z, X) - 90 - yaw` 这个公式连项数顺序都没变,魔法常数 `90` 直接搬。 + +### 两个 BooleanSetting 字面量 + +```java +// Naven Compass.java +public BooleanValue compassOnly = ValueBuilder.create(this, "Compass Only").setDefaultBooleanValue(true).build()...; +public BooleanValue noPlayerOnly = ValueBuilder.create(this, "No Player Only").setDefaultBooleanValue(true).build()...; +``` + +```java +// Zen Compass.java +private final BooleanSetting compassOnly = new BooleanSetting("Compass Only", true); +private final BooleanSetting noPlayerOnly = new BooleanSetting("No Player Only", true); +``` + +字面量名(含空格大小写)`"Compass Only"` / `"No Player Only"` 与默认值 `true / true` 完全一致,注册顺序也相同。 + +### `renderX / renderZ / renderYaw` 的 `Mth.lerp` 三连 + +```java +// Naven Compass.java +renderX = Mth.lerp(e.getRenderPartialTicks(), mc.player.xOld, mc.player.getX()); +renderZ = Mth.lerp(e.getRenderPartialTicks(), mc.player.zOld, mc.player.getZ()); +renderYaw = Mth.lerp(e.getRenderPartialTicks(), mc.player.yRotO, mc.player.getYRot()); +``` + +```java +// Zen Compass.java +this.renderX = Mth.lerp(renderEvent.partialTick(), mc.player.xOld, mc.player.getX()); +this.renderZ = Mth.lerp(renderEvent.partialTick(), mc.player.zOld, mc.player.getZ()); +this.renderYaw = Mth.lerp(renderEvent.partialTick(), mc.player.yRotO, mc.player.getYRot()); +``` + +`xOld / zOld / yRotO` 的顺序都没换。 + +--- + +## Projectiles + +### 弓蓄力公式(含魔法数 72000 / 3 / 0.1) + +```java +// Naven Projectile.java +float bowPower = (72000 - player.getUseItemRemainingTicks()) / 20.0f; +bowPower = (bowPower * bowPower + bowPower * 2.0f) / 3.0f; +if (bowPower > 1 || bowPower <= 0.1F) bowPower = 1; +bowPower *= 3F; +``` + +```java +// Zen Projectiles.java +float pull = (72000 - localPlayer.getUseItemRemainingTicks()) / 20.0f; +pull = (pull * pull + pull * 2.0f) / 3.0f; +if (pull > 1.0f || pull <= 0.1f) pull = 1.0f; +pull *= 3.0f; +``` + +完整公式 `(72000 - x) / 20 → (p² + 2p) / 3 → clamp → × 3` 一模一样,仅变量名 `bowPower → pull`。 + +### 抛物物品白名单 —— 9 项 instanceof 链顺序相同 + +```java +// Naven Projectile.java +return item instanceof BowItem || item instanceof CrossbowItem || item instanceof SnowballItem || item instanceof EggItem || item instanceof EnderpearlItem || item instanceof SplashPotionItem || item instanceof LingeringPotionItem || item instanceof FishingRodItem || item instanceof TridentItem; +``` + +```java +// Zen Projectiles.java +return item instanceof BowItem || item instanceof CrossbowItem + || item instanceof SnowballItem || item instanceof EggItem + || item instanceof EnderpearlItem || item instanceof SplashPotionItem + || item instanceof LingeringPotionItem || item instanceof FishingRodItem + || item instanceof TridentItem; +``` + +9 个 Item 类型按完全相同的顺序排列。 + +### 重力 switch + 三个颜色 RGB 常数 + +```java +// Naven Projectile.java +if (item instanceof BowItem || item instanceof CrossbowItem) return 0.05; +if (item instanceof PotionItem) return 0.4; +if (item instanceof FishingRodItem) return 0.15; +if (item instanceof TridentItem) return 0.015; +return 0.03; +... +new BasicProjectileData(Collections.singleton(ThrownEnderpearl.class), new Color(173, 12, 255)); +new BasicProjectileData(Collections.singleton(ThrownEgg.class), new Color(255, 238, 154)); +new BasicProjectileData(Collections.singleton(Snowball.class), new Color(255, 255, 255)); +``` + +```java +// Zen Projectiles.java +if (item instanceof BowItem || item instanceof CrossbowItem) return 0.05; +if (item instanceof PotionItem) return 0.4; +if (item instanceof FishingRodItem) return 0.15; +if (item instanceof TridentItem) return 0.015; +return 0.03; +... +new ClassEspColor(Collections.singleton(ThrownEnderpearl.class), new Color(173, 12, 255)); +new ClassEspColor(Collections.singleton(ThrownEgg.class), new Color(255, 238, 154)); +new ClassEspColor(Collections.singleton(Snowball.class), new Color(255, 255, 255)); +``` + +5 条 gravity 分支顺序与数值(`0.05 / 0.4 / 0.15 / 0.015 / 0.03`)完全照搬。3 个 RGB 颜色魔法数 `(173, 12, 255)` `(255, 238, 154)` `(255, 255, 255)` 完全一致,且 `Collections.singleton(X.class)` 包装方式一致。 + +### 5 个 BooleanSetting 名 + 默认值 + +```java +// Naven Projectile.java +ValueBuilder.create(this, "Show Arrows").setDefaultBooleanValue(true)... +ValueBuilder.create(this, "Show Pearls").setDefaultBooleanValue(true)... +ValueBuilder.create(this, "Show Potions").setDefaultBooleanValue(false)... +ValueBuilder.create(this, "Show Eggs").setDefaultBooleanValue(false)... +ValueBuilder.create(this, "Show Snowballs").setDefaultBooleanValue(false)... +``` + +```java +// Zen Projectiles.java +new BooleanSetting("Show Arrows", true); +new BooleanSetting("Show Pearls", true); +new BooleanSetting("Show Potions", false); +new BooleanSetting("Show Eggs", false); +new BooleanSetting("Show Snowballs", false); +``` + +5 个字符串(大小写、空格、复数 s)、5 个默认值(`true / true / false / false / false`)、注册顺序全部一致。 + +--- + +## 工具类 + +### `ChunkUtil.getLoadedChunks` —— `"Stream limit didn't work."` 异常文本逐字符一致 + +```java +// Naven ChunkUtils.java +public static Stream getLoadedChunks() { + int radius = Math.max(2, mc.options.getEffectiveRenderDistance()) + 3; + int diameter = radius * 2 + 1; + ChunkPos center = mc.player.chunkPosition(); + ChunkPos min = new ChunkPos(center.x - radius, center.z - radius); + ChunkPos max = new ChunkPos(center.x + radius, center.z + radius); + Stream stream = Stream.iterate(min, pos -> { + int x = pos.x; + int z = pos.z; + x++; + if (x > max.x) { x = min.x; z++; } + if (z > max.z) throw new IllegalStateException("Stream limit didn't work."); + return new ChunkPos(x, z); + }).limit((long) diameter * diameter).filter(c -> mc.level.hasChunk(c.x, c.z)) + .map(c -> mc.level.getChunk(c.x, c.z)).filter(Objects::nonNull); + return stream; +} +``` + +```java +// Zen ChunkUtil.java +public static Stream getLoadedChunks() { + if (mc.player == null || mc.level == null) return Stream.empty(); + final int radius = Math.max(2, mc.options.getEffectiveRenderDistance()) + 3; + final int side = radius * 2 + 1; + final ChunkPos center = mc.player.chunkPosition(); + final ChunkPos min = new ChunkPos(center.x - radius, center.z - radius); + final ChunkPos max = new ChunkPos(center.x + radius, center.z + radius); + return Stream.iterate(min, current -> { + int x = current.x; int z = current.z; + if (++x > max.x) { x = min.x; ++z; } + if (z > max.z) throw new IllegalStateException("Stream limit didn't work."); + return new ChunkPos(x, z); + }).limit((long) side * side).filter(pos -> mc.level.hasChunk(pos.x, pos.z)) + .map(pos -> mc.level.getChunk(pos.x, pos.z)).filter(Objects::nonNull); +} +``` + +异常字符串 `"Stream limit didn't work."` 含句点、含撇号、连大小写都一致;`Math.max(2, ...) + 3`、`radius * 2 + 1`、`Stream.iterate` 的 lambda 行内 `if (x > max.x) { x = min.x; z++; }` 写法骨架完全一样。这种带异常 throw 的 `Stream.iterate` 谁都不会自己写两遍。 + +### `RotationUtil` 把 antlr-runtime 的 `OrderedHashSet` 当 Set 用 + +```java +// Naven RotationUtils.java +import org.antlr.v4.runtime.misc.OrderedHashSet; +... +Set points = new OrderedHashSet<>(); +points.add(new Vec3(minX + maxX / 2, minY + maxY / 2, minZ + maxZ / 2)); +points.add(getClosestPoint(eyePos, targetBox)); +``` + +```java +// Zen RotationUtil.java +import org.antlr.v4.runtime.misc.OrderedHashSet; +... +OrderedHashSet samplePoints = new OrderedHashSet<>(); +samplePoints.add(new Vec3(minX + maxX / 2.0, minY + maxY / 2.0, minZ + maxZ / 2.0)); +samplePoints.add(RotationUtil.closestPoint(eyePos, aABB)); +``` + +`org.antlr.v4.runtime.misc.OrderedHashSet` 是 ANTLR 解析器运行时里的类,**任何一个正常人写的旋转工具类都不会去 import 它**。两边都用它存 `Vec3` 采样点;连"先 add 中心点、再 add 最近点"的顺序和起手式都一模一样。 + +### Sensitivity GCD 公式 `f * f * f * 1.2F` + +```java +// Naven RotationUtils.getFixedRotation +final float f = (float) (mc.options.sensitivity * 0.6F + 0.2F); +final float gcd = f * f * f * 1.2F; +final float deltaYaw = yaw - lastYaw; +final float deltaPitch = pitch - lastPitch; +final float fixedDeltaYaw = deltaYaw - (deltaYaw % gcd); +final float fixedDeltaPitch = deltaPitch - (deltaPitch % gcd); +return new Vector2f(lastYaw + fixedDeltaYaw, lastPitch + fixedDeltaPitch); +``` + +```java +// Zen RotationUtil.getSensitivitySnappedRotation +float sensitivityFactor = (float)(mc.options.sensitivity().get() * 0.6f + 0.2f); +float gcd = sensitivityFactor * sensitivityFactor * sensitivityFactor * 1.2f; +float yawDelta = yaw - prevYaw; +float pitchDelta = pitch - prevPitch; +float snappedYawDelta = yawDelta - yawDelta % gcd; +float snappedPitchDelta = pitchDelta - pitchDelta % gcd; +return new Rotation(prevYaw + snappedYawDelta, prevPitch + snappedPitchDelta); +``` + +魔法常量 `0.6 / 0.2 / 1.2` 三件套 + `f * f * f` 立方 + `delta - delta % gcd` 求余截断,整套公式逐行同构。 + +### `rotationTo` 的 yaw / pitch 公式 + +```java +// Naven RotationUtils.getRotations +double x = target.x - eye.x; +double y = target.y - eye.y; +double z = target.z - eye.z; +double diffXZ = Math.sqrt(x * x + z * z); +float yaw = (float) Math.toDegrees(Math.atan2(z, x)) - 90.0f; +float pitch = (float) (-Math.toDegrees(Math.atan2(y, diffXZ))); +return new Vector2f(MathHelper.wrapDegrees(yaw), MathHelper.wrapDegrees(pitch)); +``` + +```java +// Zen RotationUtil.exactRotation +double dx = to.x - from.x; +double dy = to.y - from.y; +double dz = to.z - from.z; +double horizontalDist = Math.sqrt(dx * dx + dz * dz); +float yaw = (float)Math.toDegrees(Math.atan2(dz, dx)) - 90.0f; +float pitch = (float)(-Math.toDegrees(Math.atan2(dy, horizontalDist))); +return new Rotation(Mth.wrapDegrees(yaw), Mth.wrapDegrees(pitch)); +``` + +yaw 用 `atan2(z, x) - 90`、pitch 用 `-atan2(y, sqrt(x² + z²))`,外层 `Math.toDegrees` 强转 float 再 `wrapDegrees`,连 `- 90.0f` 的字面量写法都一致。这段代码在 Zen 里出现了 4 次(`rotationTo / exactRotation / rotationFromDeltas / createRotation`)。 + +### `BlockUtil.canBeClicked / getBoundingBox` + +```java +// Naven BlockUtils.java +public static AABB getBoundingBox(BlockPos pos) { + return getOutlineShape(pos).bounds().move(pos); +} +private static VoxelShape getOutlineShape(BlockPos pos) { + return getState(pos).getShape(mc.level, pos); +} +public static boolean canBeClicked(BlockPos pos) { + return getOutlineShape(pos) != Shapes.empty(); +} +``` + +```java +// Zen BlockUtil.java +public static boolean canBeClicked(BlockPos blockPos) { + return BlockUtil.getVoxelShape(blockPos) != Shapes.empty(); +} +public static AABB getBoundingBox(BlockPos blockPos) { + return BlockUtil.getVoxelShape(blockPos).bounds().move(blockPos); +} +private static VoxelShape getVoxelShape(BlockPos blockPos) { + return BlockUtil.getBlockState(blockPos).getShape(mc.level, blockPos); +} +``` + +`!= Shapes.empty()`(而不是更地道的 `.isEmpty()`)这种引用比较的写法两边一致;三段方法的实现一行一行对得上。 + +### `MovementUtil` direction 函数 —— 7 个 if 分支顺序与角度全一致 + +```java +// Naven MoveUtils.direction +if (forward != 0.0F || strafe != 0.0F) { + if (isMovingBack && !isMovingSideways) return direction + 180.0F; + else if (isMovingForward && isMovingLeft) return direction + 45.0F; + else if (isMovingForward && isMovingRight) return direction - 45.0F; + else if (!isMovingStraight && isMovingLeft) return direction + 90.0F; + else if (!isMovingStraight && isMovingRight) return direction - 90.0F; + else if (isMovingBack && isMovingLeft) return direction + 135.0F; + else if (isMovingBack) return direction - 135.0F; +} +return direction; +``` + +```java +// Zen MovementUtil.getDirectionAngle +if (forward != 0.0f || strafe != 0.0f) { + if (forwardNegative && !hasStrafe) return yaw + 180.0f; + if (forwardPositive && strafeNegative) return yaw + 45.0f; + if (forwardPositive && strafePositive) return yaw - 45.0f; + if (!hasForward && strafeNegative) return yaw + 90.0f; + if (!hasForward && strafePositive) return yaw - 90.0f; + if (forwardNegative && strafeNegative) return yaw + 135.0f; + if (forwardNegative) return yaw - 135.0f; +} +return yaw; +``` + +7 个分支的顺序、判断组合、`+180 / +45 / -45 / +90 / -90 / +135 / -135` 七个常量一字不差。Zen 仅改了变量名(`isMovingBack` → `forwardNegative`),把 `else if` 改成提前 `return`,这是典型的"反编译后稍微整形"痕迹。注意 Zen 这个 `getDirectionAngle` 是 `private` 且**根本没被调用**(死代码),更说明是整团抄过来的。 + +### `RayTraceUtil.pick` 的 `+ 1.62` 硬编码 eye height + +```java +// Naven RayTraceUtils.pick +Vec3 vec3 = new Vec3(mc.player.getX(), mc.player.getY() + 1.62, mc.player.getZ()); +Vec3 vec31 = calculateViewVector(pXRot, pYRot); +Vec3 vec32 = vec3.add(vec31.x * pHitDistance, vec31.y * pHitDistance, vec31.z * pHitDistance); +return mc.player.level.clip(new ClipContext(vec3, vec32, + ClipContext.Block.OUTLINE, + pHitFluids ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, mc.player)); +``` + +```java +// Zen RayTraceUtil.rayTrace +Vec3 eyePos = new Vec3(mc.player.getX(), mc.player.getY() + 1.62, mc.player.getZ()); +Vec3 viewVec = RayTraceUtil.getViewVector(pitch, yaw); +Vec3 endPos = eyePos.add(viewVec.x * range, viewVec.y * range, viewVec.z * range); +return mc.player.level().clip(new ClipContext(eyePos, endPos, + ClipContext.Block.OUTLINE, + clipFluids ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, mc.player)); +``` + +在 1.17+ 应该用 `mc.player.getEyeHeight()` 或 `getEyePosition(partialTicks)`,硬编码 `+ 1.62` 是历史遗留写法。两边都犯同一个 hardcode + 同样的 `Block.OUTLINE` + 同样的三元 `Fluid.ANY : Fluid.NONE`,ClipContext 的 5 个参数顺序也一致。 + +--- + +## 结语 + +上面这些代码段,凡是真正难辩解的硬证据全都集中在三类: + +1. **字符串字面量**(`"Fake Staff Detected! ("`、`"Bot Detected! ("`、`"Bot Removed! ("`、`"Stream limit didn't work."`、`"点击使用"`、`"Compass Only"`、`"No Player Only"`、`"Show Arrows / Pearls / Potions / Eggs / Snowballs"`、`"Normal" / "Telly Bridge" / "Keep Y"`、`"Render Item Spoof"`、`"Respawn Time"`)。 +2. **魔法常量**(`+1337`、`(0.88, 1.88, 0.88)`、`> 2 && < 0.0001`、`i * i + 1` 附魔加成、72000 / 0.1 / 3 弓蓄力、`0.6 / 0.2 / 1.2` 灵敏度 GCD、`+ 1.62` 硬编码 eye height、RGB `(173, 12, 255)` / `(255, 238, 154)` 等)。 +3. **特征 import 与"死代码"**(工具类里出现 `org.antlr.v4.runtime.misc.OrderedHashSet`;`MovementUtil.getDirectionAngle` 是 `private` 且没人调用却整团抄了过来)。 + +这些东西在反混淆环节是**不会被自动还原**的——反编译只能把 `var1 / var2` 还原成可读变量,但字符串、常数、字节码里的 instanceof 链顺序、import 表都跟字节码绑死。它们出现在两个项目里完全一致,唯一合理解释就是源代码直接复制。