本文档基于公开可获取的两个仓库进行代码段并排对比:
- 参考实现(被抄袭方):
Margele/Naven-Modern - 待审项目(疑似抄袭方):本仓 OpenZen(Zen 客户端反混淆产物)
字段 / 类 / 方法名是反编译时根据 Naven 重命名出来的,不能算抄袭指纹。所以下面所有证据全是真实的字符串字面量、魔法常量、控制流、lambda 表达式——这些东西在反混淆过程中不会被自动还原,只能是抄过来的。
- AntiBots
- CrystalAura
- AntiFireball
- Scaffold
- FastWeb
- Stuck
- AutoMLG
- SafeWalk
- Disabler
- AutoTools
- ChestStealer
- InventoryManager
- ChestESP
- Compass
- Projectiles
- 工具类
// Naven AntiBots.java
ChatUtils.addChatMessage("Fake Staff Detected! (" + uuidDisplayNames.get(entry.getKey()) + ")");
...
ChatUtils.addChatMessage("Bot Detected! (" + displayName + ")");
...
ChatUtils.addChatMessage("Bot Removed! (" + displayName + ")");// Zen AntiBots.java
ChatUtil.print("Fake Staff Detected! (" + suspectNames.get(entry.getKey()) + ")");
...
ChatUtil.print("Bot Detected! (" + botName + ")");
...
ChatUtil.print("Bot Removed! (" + name + ")");三条字面量一字不差,拼接模板 "X! (" + name + ")" 形状完全一致。这是模块作者主观选的英文短句,撞车概率为零。
// 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());
}// 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();
}
public static boolean isBot(Entity entity) {
return confirmedBotIds.contains(entity.getId());
}Zen 在前面加了几条短路判断遮掩,但核心三步 < 1 哨兵 → containsKey 检查 → currentTimeMillis() - map.get(uuid) < timeout 比对完全照抄,isBot 一行实现一模一样。
// Naven AntiBots.java
private final FloatValue respawnTimeValue = ValueBuilder.create(this, "Respawn Time")
.setDefaultFloatValue(2500).setFloatStep(100)
.setMinFloatValue(0).setMaxFloatValue(10000).build().getFloatValue();// Zen AntiBots.java
private final NumberSetting newPlayerTimeout =
new NumberSetting("Respawn Time", 2500.0, 0.0, 10000.0, 100.0);抛开字段名,"Respawn Time" 字符串、默认 2500、范围 0–10000、步长 100 四个字面量一一对应。
// 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 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 顺序完全相同。
// Naven AntiBots.java
for (Map.Entry<UUID, Long> entry : uuids.entrySet()) {
if (System.currentTimeMillis() - entry.getValue() > 500) {
ChatUtils.addChatMessage("Fake Staff Detected! (" + uuidDisplayNames.get(entry.getKey()) + ")");
uuids.remove(entry.getKey());
}
}// Zen AntiBots.java
for (Map.Entry<UUID, Long> 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());
}500 ms 这个魔法常量、currentTimeMillis() - entry.getValue() 减法表达式、循环里就地 remove 的反模式写法三处都对得上。
// 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);// 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);相同的距离阈值 <= 4、相同的 9 步操作序列(new EndCrystal → setId → distance → getRotations → PosRot → UseItem → save yaw/pitch → setYRot/XRot → InteractPacket → swing)。
// Naven AttackCrystal.java
Optional<Entity> 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;// Zen CrystalAura.java
Optional<Entity> 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;StreamSupport.stream(... .spliterator(), true) 并行流、EndCrystal instanceof 过滤、<= 3 命中距离阈值、aimRotation = null 再赋值的控制流完全一致。
// Naven AntiFireball.java
Stream<Entity> stream = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), true);
Optional<Fireball> 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);// Zen AntiFireball.java
Stream<Entity> stream = StreamSupport.stream(mc.level.entitiesForRendering().spliterator(), false);
Optional<Fireball> 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);lambda 链 instanceof Fireball && distanceTo < 6 一字不差,魔法数 6 完全一致,mc.gameMode.attack(mc.player, ...) + swing(MAIN_HAND) 收尾相同。Zen 仅多一行 debug 输出。
// 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;
}
}// 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();
}把 yaw 减 180 度作为脚手架初始 yaw(让玩家朝身后放方块)是 Naven 自定义做法;4 个字段、顺序、yRotO/xRotO 完全对应。
// 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)...// 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);模式列表 Normal / Telly Bridge / Keep Y 三个字符串字面量和顺序完全一致;Eagle/Snap 都用 visibility = mode == "Normal" 这种 lambda;"Render Item Spoof" 这种特定字符串字面量在两边都用作选项名。
// Naven Scaffold.java
if (!shouldPlaceBlock) {
rots.setX(mc.player.getYRot() + RandomUtils.nextFloat(0, 0.5F) - 0.25F);
}// Zen Scaffold.java
if (!lookingAtBlock && mc.player.tickCount % 4 == 0) {
this.rots.setYaw(mc.player.getYRot() + RandomUtils.nextFloat(0.0f, 0.5f) - 0.25f);
}用 nextFloat(0, 0.5) - 0.25 来得到 [-0.25, +0.25] 抖动量的写法是 Naven 风格(直接 nextFloat(-0.25, 0.25) 更自然)。这种"减半法"指纹两边完全一致。
// 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));// 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 Scaffold 把 SafeWalk 的同一份 isOnBlockEdge 在自己类里又粘贴了一遍,连 0.3f 阈值与 keyShift.setDown(onGround && isOnBlockEdge(0.3f)) 这一整行表达式都一样。
// 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);
}
}
}// 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);
}
}
}> 5 阈值、(0.88, 1.88, 0.88) 三元魔法常量、记录 tickCount 然后递增计数器的顺序——三层细节全部撞车。0.88 / 1.88 不是 Minecraft 物理常量,是 Naven 独有的口味。
// Naven FastWeb.java
public void onMotion(EventMotion e) {
if (e.getType() == EventType.POST) {
if (playerInWebTick < mc.player.tickCount) {
ticksInWeb = 0;
}
}
}// Zen FastWeb.java
public void onMotion(MotionEvent motionEvent) {
if (motionEvent.isPre() && mc.player != null && this.lastWebTick < mc.player.tickCount) {
this.webCount = 0;
}
}lastTick < player.tickCount 条件 + reset 计数到 0 的逻辑完全相同(仅 PRE/POST 差异)。
// 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;
}// 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<ServerGamePacketListener>) this.pongQueue.poll());
}
...
this.pendingDisable = false;
}把玩家 X/Z 同时加 1337 这种"骇客致敬"魔法常量、外加紧跟其后的 pongQueue.poll() 循环——这就是经典 Naven 指纹。
// 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();
}
}// 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)也完全平行。
// 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;
}// 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 翻译。
// Naven AutoMLG.java
public static boolean isOnGround(double height) {
Iterable<VoxelShape> 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;
...
}
}// 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 字面量参数也完全一致。
// 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();
}// 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 指纹。
// Naven SafeWalk.java
public void onDisable() {
boolean isHoldingShift = InputConstants.isKeyDown(mc.getWindow().getWindow(), mc.options.keyShift.getKey().getValue());
mc.options.keyShift.setDown(isHoldingShift);
}// Zen SafeWalk.java
public void onDisable() {
boolean keyDown = InputConstants.isKeyDown(mc.getWindow().getWindow(), mc.options.keyShift.getKey().getValue());
mc.options.keyShift.setDown(keyDown);
}仅变量名不同,控制流、API 调用、参数顺序完全一致。
// 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;
}
}// 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 两份。
// 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;// 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 类名重命名后的同义替换。
// 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))) {// 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(...)。
// 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;
}
...
}// 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 是直接复制粘贴的。
// 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;。
// Naven ChestESP.java
if ((packet.getBlock() == Blocks.CHEST || packet.getBlock() == Blocks.TRAPPED_CHEST) && packet.getB0() == 1 && packet.getB1() == 1) {
openedChests.add(packet.getPos());
}// 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。
// 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);// 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 —— 整段算法逐行复刻。
// Naven ChestESP.java
private static final float[] chestColor = {0, 1, 0};
private static final float[] openedChestColor = {1, 0, 0};// 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 也一致。
// 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;// 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 直接搬。
// 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()...;// 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 完全一致,注册顺序也相同。
// 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());// 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 的顺序都没换。
// 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;// 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。
// 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;// 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 类型按完全相同的顺序排列。
// 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));// 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) 包装方式一致。
// 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)...// 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)、注册顺序全部一致。
// Naven ChunkUtils.java
public static Stream<LevelChunk> 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<LevelChunk> 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;
}// Zen ChunkUtil.java
public static Stream<LevelChunk> 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 谁都不会自己写两遍。
// Naven RotationUtils.java
import org.antlr.v4.runtime.misc.OrderedHashSet;
...
Set<Vec3> points = new OrderedHashSet<>();
points.add(new Vec3(minX + maxX / 2, minY + maxY / 2, minZ + maxZ / 2));
points.add(getClosestPoint(eyePos, targetBox));// Zen RotationUtil.java
import org.antlr.v4.runtime.misc.OrderedHashSet;
...
OrderedHashSet<Vec3> 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 最近点"的顺序和起手式都一模一样。
// 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);// 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 求余截断,整套公式逐行同构。
// 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));// 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)。
// 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();
}// 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())这种引用比较的写法两边一致;三段方法的实现一行一行对得上。
// 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;// 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 且根本没被调用(死代码),更说明是整团抄过来的。
// 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));// 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 个参数顺序也一致。
上面这些代码段,凡是真正难辩解的硬证据全都集中在三类:
- 字符串字面量(
"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")。 - 魔法常量(
+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)等)。 - 特征 import 与"死代码"(工具类里出现
org.antlr.v4.runtime.misc.OrderedHashSet;MovementUtil.getDirectionAngle是private且没人调用却整团抄了过来)。
这些东西在反混淆环节是不会被自动还原的——反编译只能把 var1 / var2 还原成可读变量,但字符串、常数、字节码里的 instanceof 链顺序、import 表都跟字节码绑死。它们出现在两个项目里完全一致,唯一合理解释就是源代码直接复制。