Skip to content

Commit fde265e

Browse files
committed
Anti-ESP Detection
1 parent 28c494b commit fde265e

5 files changed

Lines changed: 1296 additions & 9 deletions

File tree

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,11 @@ Credits to [Trouser-Streak](https://github.com/etianl/Trouser-Streak/blob/main/s
530530
- Flags the likely protection so you know which hacks might be risky to use
531531
- Setback Detection, tracks sudden position corrections/teleports that indicate that the server rejected your hack and automatically disables it.
532532
- Works independantly of Anticheat Detector
533+
- Anti-ESP Detection:
534+
- Detects suspicious anti-ESP behavior world-wide based on packet patterns (rapid block swaps, high update bursts and delayed block-entity reveals)
535+
- Noise handling for fluid/air transitions
536+
- Will be able to detect anti-xray traffic as well but will likely trigger burst signals
537+
- Added Anti-ESP detection specifically to ChestESP and Search in addition to global detection to avoid any false positives
533538

534539
### Livestream Detector
535540
- Scans the player list for Youtube, Twitch, Tiktok and Kick for live streams and announces them in chat
@@ -911,6 +916,7 @@ Purpose: helps you avoid and debug anticheat flags by cleaning risky movement pa
911916
- Added Buried chest highlighting including a filter to only find buried chests
912917
- Added filters for chests and barrels to not highlight them if near spawners, trial chambers and villages (Excluding double chests and shulkers)
913918
- Added opacity settings
919+
- Added anti-ESP detection
914920
- Option to suppress single chests in favor of double
915921
- Optional alert for shulkerboxes in chat
916922

@@ -970,7 +976,8 @@ Examples:
970976
- Replaced full-sort approach to bounded max-heap (PriorityQueue) that keeps the closest N matches. This avoids sorting entire result and keeps search as fast as X-Ray.
971977
- Safer rescans and better crash handling.
972978
- Improved search speed.
973-
- Added highlight corners (lines) and fill settings (Same as X-Ray)
979+
- Added highlight corners (lines) and fill settings (Same as X-Ray).
980+
- Added anti-ESP detection.
974981

975982
![Search](https://i.imgur.com/jxcn89u.png)
976983

src/main/java/net/wurstclient/hacks/AntiCheatDetectHack.java

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,35 @@
77
*/
88
package net.wurstclient.hacks;
99

10+
import java.util.ArrayDeque;
11+
import java.util.HashMap;
12+
import net.minecraft.core.BlockPos;
13+
import net.minecraft.core.registries.BuiltInRegistries;
14+
import net.minecraft.network.protocol.Packet;
15+
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
16+
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
17+
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
18+
import net.minecraft.world.level.block.state.BlockState;
1019
import net.wurstclient.Category;
20+
import net.wurstclient.events.PacketInputListener;
21+
import net.wurstclient.events.PacketInputListener.PacketInputEvent;
22+
import net.wurstclient.events.UpdateListener;
1123
import net.wurstclient.hack.Hack;
1224
import net.wurstclient.settings.CheckboxSetting;
25+
import net.wurstclient.settings.SliderSetting;
26+
import net.wurstclient.settings.SliderSetting.ValueDisplay;
1327
import net.wurstclient.util.ChatUtils;
1428
import net.wurstclient.util.ServerObserver;
1529

1630
public final class AntiCheatDetectHack extends Hack
31+
implements PacketInputListener, UpdateListener
1732
{
33+
private static final long GLOBAL_BLOCK_BURST_WINDOW_MS = 2000L;
34+
private static final long GLOBAL_BLOCK_FLIP_WINDOW_MS = 1500L;
35+
private static final long GLOBAL_BLOCK_HISTORY_TTL_MS = 10000L;
36+
private static final long GLOBAL_BE_BURST_WINDOW_MS = 2000L;
37+
private static final int GLOBAL_BE_BURST_THRESHOLD = 40;
38+
private static final long GLOBAL_BLOCK_UPDATE_GRACE_MS = 1200L;
1839
private String lastAnnounced;
1940
private long lastAnnouncedMs;
2041
private static final long ANNOUNCE_COOLDOWN_MS = 3000L;
@@ -24,22 +45,89 @@ public final class AntiCheatDetectHack extends Hack
2445
private final CheckboxSetting suppressUnknown =
2546
new CheckboxSetting("Suppress unknown alerts",
2647
"description.wurst.setting.anticheatdetect.suppress_unknown", true);
48+
private final CheckboxSetting detectGlobalAntiEsp = new CheckboxSetting(
49+
"Detect global anti-esp",
50+
"Detect suspicious anti-ESP style block-update patterns across all block changes.",
51+
false);
52+
private final CheckboxSetting globalAntiEspAlerts = new CheckboxSetting(
53+
"Global anti-esp alerts",
54+
"Show chat warnings when global anti-ESP patterns are detected.", true);
55+
private final SliderSetting globalBlockBurstThreshold =
56+
new SliderSetting("Global block burst threshold",
57+
"Warn if this many block changes arrive within 2 seconds.", 400, 50,
58+
5000, 10, ValueDisplay.INTEGER);
59+
private final ArrayDeque<Long> globalBlockChangeTimes = new ArrayDeque<>();
60+
private final ArrayDeque<Long> globalBePacketTimes = new ArrayDeque<>();
61+
private final HashMap<Long, BlockChangeSnapshot> globalLastBlockChanges =
62+
new HashMap<>();
63+
private final HashMap<Long, Long> globalRecentBlockUpdates =
64+
new HashMap<>();
65+
private final HashMap<String, Long> globalAntiEspCooldowns =
66+
new HashMap<>();
67+
private final Object globalAntiEspLock = new Object();
68+
private boolean globalAntiEspSuspicious;
69+
private int globalAntiEspSignals;
2770

2871
public AntiCheatDetectHack()
2972
{
3073
super("AntiCheatDetect");
3174
setCategory(Category.OTHER);
3275
addSetting(setbackDetection);
3376
addSetting(suppressUnknown);
77+
addSetting(detectGlobalAntiEsp);
78+
addSetting(globalAntiEspAlerts);
79+
addSetting(globalBlockBurstThreshold);
3480
}
3581

3682
@Override
3783
protected void onEnable()
3884
{
3985
WURST.getServerObserver().requestCaptureIfNeeded();
86+
EVENTS.add(PacketInputListener.class, this);
87+
EVENTS.add(UpdateListener.class, this);
88+
resetGlobalAntiEspState();
4089
alertAboutAntiCheat();
4190
}
4291

92+
@Override
93+
protected void onDisable()
94+
{
95+
EVENTS.remove(PacketInputListener.class, this);
96+
EVENTS.remove(UpdateListener.class, this);
97+
resetGlobalAntiEspState();
98+
}
99+
100+
@Override
101+
public void onUpdate()
102+
{
103+
if(detectGlobalAntiEsp.isChecked())
104+
pruneGlobalAntiEspState();
105+
}
106+
107+
@Override
108+
public void onReceivedPacket(PacketInputEvent event)
109+
{
110+
if(!detectGlobalAntiEsp.isChecked())
111+
return;
112+
113+
Packet<?> packet = event.getPacket();
114+
if(packet instanceof ClientboundBlockUpdatePacket blockUpdate)
115+
{
116+
recordGlobalBlockChange(blockUpdate.getPos(),
117+
blockUpdate.getBlockState());
118+
return;
119+
}
120+
121+
if(packet instanceof ClientboundSectionBlocksUpdatePacket deltaUpdate)
122+
{
123+
deltaUpdate.runUpdates(this::recordGlobalBlockChange);
124+
return;
125+
}
126+
127+
if(packet instanceof ClientboundBlockEntityDataPacket bePacket)
128+
recordGlobalBlockEntityPacket(bePacket);
129+
}
130+
43131
public void completed()
44132
{
45133
if(isEnabled())
@@ -72,4 +160,147 @@ public boolean isSetbackDetectionEnabled()
72160
{
73161
return setbackDetection.isChecked();
74162
}
163+
164+
@Override
165+
public String getRenderName()
166+
{
167+
return super.getRenderName();
168+
}
169+
170+
private void recordGlobalBlockChange(BlockPos pos, BlockState state)
171+
{
172+
if(pos == null || state == null)
173+
return;
174+
175+
long now = System.currentTimeMillis();
176+
String blockId =
177+
BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString();
178+
boolean currentRelevant = isRelevantBlockState(state, blockId);
179+
180+
synchronized(globalAntiEspLock)
181+
{
182+
if(currentRelevant)
183+
{
184+
globalBlockChangeTimes.addLast(now);
185+
while(!globalBlockChangeTimes.isEmpty()
186+
&& now - globalBlockChangeTimes
187+
.peekFirst() > GLOBAL_BLOCK_BURST_WINDOW_MS)
188+
globalBlockChangeTimes.removeFirst();
189+
190+
if(globalBlockChangeTimes.size() >= globalBlockBurstThreshold
191+
.getValueI())
192+
flagGlobalAntiEspLocked("global-burst",
193+
"Observed " + globalBlockChangeTimes.size()
194+
+ " block changes in 2s.");
195+
}
196+
197+
long key = pos.asLong();
198+
globalRecentBlockUpdates.put(key, now);
199+
BlockChangeSnapshot previous = globalLastBlockChanges.put(key,
200+
new BlockChangeSnapshot(blockId, now, currentRelevant));
201+
if(previous != null && previous.relevant() && currentRelevant
202+
&& !previous.blockId().equals(blockId)
203+
&& now - previous.timestamp() <= GLOBAL_BLOCK_FLIP_WINDOW_MS)
204+
flagGlobalAntiEspLocked("flip-flop",
205+
"Rapid block swap at " + pos.getX() + ", " + pos.getY()
206+
+ ", " + pos.getZ() + " (" + previous.blockId() + " -> "
207+
+ blockId + ")");
208+
}
209+
}
210+
211+
private void flagGlobalAntiEspLocked(String key, String message)
212+
{
213+
long now = System.currentTimeMillis();
214+
Long cooldown = globalAntiEspCooldowns.get(key);
215+
if(cooldown != null && now - cooldown < 8000L)
216+
return;
217+
218+
globalAntiEspCooldowns.put(key, now);
219+
globalAntiEspSuspicious = true;
220+
globalAntiEspSignals = Math.min(globalAntiEspSignals + 1, 999);
221+
if(globalAntiEspAlerts.isChecked())
222+
ChatUtils.warning("AntiCheatDetect Global Anti-ESP: " + message);
223+
}
224+
225+
private void pruneGlobalAntiEspState()
226+
{
227+
long now = System.currentTimeMillis();
228+
synchronized(globalAntiEspLock)
229+
{
230+
while(!globalBlockChangeTimes.isEmpty()
231+
&& now - globalBlockChangeTimes
232+
.peekFirst() > GLOBAL_BLOCK_BURST_WINDOW_MS)
233+
globalBlockChangeTimes.removeFirst();
234+
while(!globalBePacketTimes.isEmpty() && now
235+
- globalBePacketTimes.peekFirst() > GLOBAL_BE_BURST_WINDOW_MS)
236+
globalBePacketTimes.removeFirst();
237+
globalRecentBlockUpdates.entrySet().removeIf(
238+
e -> now - e.getValue() > GLOBAL_BLOCK_HISTORY_TTL_MS);
239+
globalLastBlockChanges.entrySet().removeIf(e -> now
240+
- e.getValue().timestamp() > GLOBAL_BLOCK_HISTORY_TTL_MS);
241+
}
242+
}
243+
244+
private void resetGlobalAntiEspState()
245+
{
246+
synchronized(globalAntiEspLock)
247+
{
248+
globalBlockChangeTimes.clear();
249+
globalBePacketTimes.clear();
250+
globalLastBlockChanges.clear();
251+
globalRecentBlockUpdates.clear();
252+
globalAntiEspCooldowns.clear();
253+
globalAntiEspSuspicious = false;
254+
globalAntiEspSignals = 0;
255+
}
256+
}
257+
258+
private void recordGlobalBlockEntityPacket(
259+
ClientboundBlockEntityDataPacket packet)
260+
{
261+
BlockPos pos = packet.getPos();
262+
if(pos == null)
263+
return;
264+
265+
long now = System.currentTimeMillis();
266+
synchronized(globalAntiEspLock)
267+
{
268+
globalBePacketTimes.addLast(now);
269+
while(!globalBePacketTimes.isEmpty() && now
270+
- globalBePacketTimes.peekFirst() > GLOBAL_BE_BURST_WINDOW_MS)
271+
globalBePacketTimes.removeFirst();
272+
273+
if(globalBePacketTimes.size() >= GLOBAL_BE_BURST_THRESHOLD)
274+
flagGlobalAntiEspLocked("be-burst",
275+
"Received " + globalBePacketTimes.size()
276+
+ " block-entity packets in 2s.");
277+
278+
Long lastUpdate = globalRecentBlockUpdates.get(pos.asLong());
279+
if(lastUpdate == null
280+
|| now - lastUpdate > GLOBAL_BLOCK_UPDATE_GRACE_MS)
281+
flagGlobalAntiEspLocked("late-be",
282+
"Block entity at " + pos.getX() + ", " + pos.getY() + ", "
283+
+ pos.getZ()
284+
+ " arrived without a recent block update.");
285+
}
286+
}
287+
288+
private boolean isRelevantBlockState(BlockState state, String blockId)
289+
{
290+
if(state == null || blockId == null)
291+
return false;
292+
293+
if(state.isAir())
294+
return false;
295+
296+
if(!state.getFluidState().isEmpty())
297+
return false;
298+
299+
return !"minecraft:water".equals(blockId)
300+
&& !"minecraft:lava".equals(blockId);
301+
}
302+
303+
private record BlockChangeSnapshot(String blockId, long timestamp,
304+
boolean relevant)
305+
{}
75306
}

0 commit comments

Comments
 (0)