Skip to content

Commit f7f8381

Browse files
committed
Shader-Safe Mode
1 parent 13321d0 commit f7f8381

8 files changed

Lines changed: 408 additions & 25 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,14 @@ Examples:
663663
- ChunkSearcher now snapshots each chunk's block-state palettes on the client thread and lets the async scan read from that immutable copy, preventing the off-thread palette races that causes rare crashes.
664664
- Fixed rare empty outline shape crash (safe bounding box for empty shapes fix)
665665

666+
### Shader-Safe Mode
667+
- Detect shader usage (Iris/OptiFine) safely at runtime and keep a cached toggle state.
668+
- Automatically switch sensitive hacks to “shader-safe mode” when shaders are active.
669+
- Avoid render-thread crashes by snapshotting collections, disabling parallel streams, and avoiding unsafe joins.
670+
- Apply safe-mode behavior dynamically when shaders are toggled on/off.
671+
- Notify users once per toggle/enable so they know performance/behavior is adjusted.
672+
- Currently only in these higher risk hacks: Search, CaveFinder, PlayerESP and Excavator as well as in BlockVertexCompiler.
673+
666674
### Notes
667675
- Scanning only includes server-loaded chunks. Larger radii work best in single-player or on high view distance servers.
668676

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

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import net.wurstclient.util.RegionPos;
3838
import net.wurstclient.util.RenderUtils;
3939
import net.wurstclient.util.RotationUtils;
40+
import net.wurstclient.util.ShaderUtils;
4041
import net.wurstclient.util.chunk.ChunkSearcher;
4142
import net.wurstclient.util.chunk.ChunkSearcherCoordinator;
4243

@@ -70,6 +71,9 @@ public final class CaveFinderHack extends Hack
7071
private ForkJoinPool forkJoinPool;
7172
private ForkJoinTask<HashSet<BlockPos>> getMatchingBlocksTask;
7273
private ForkJoinTask<ArrayList<int[]>> compileVerticesTask;
74+
private boolean shaderSafeMode;
75+
private int buildGeneration;
76+
private int currentBuildGeneration;
7377

7478
private EasyVertexBuffer vertexBuffer;
7579
private RegionPos bufferRegion;
@@ -92,9 +96,14 @@ protected void onEnable()
9296
notify = true;
9397

9498
forkJoinPool = new ForkJoinPool();
99+
shaderSafeMode = ShaderUtils.refreshShadersActive();
100+
buildGeneration = 0;
101+
currentBuildGeneration = 0;
95102

96103
bufferUpToDate = false;
97-
104+
if(shaderSafeMode)
105+
ChatUtils
106+
.message("Shaders detected - using safe mode for CaveFinder.");
98107
EVENTS.add(UpdateListener.class, this);
99108
EVENTS.add(PacketInputListener.class, coordinator);
100109
EVENTS.add(RenderListener.class, this);
@@ -120,6 +129,19 @@ protected void onDisable()
120129
@Override
121130
public void onUpdate()
122131
{
132+
boolean currentShaderSafeMode = ShaderUtils.refreshShadersActive();
133+
if(currentShaderSafeMode != shaderSafeMode)
134+
{
135+
shaderSafeMode = currentShaderSafeMode;
136+
stopBuildingBuffer();
137+
if(shaderSafeMode)
138+
ChatUtils.message(
139+
"Shaders detected - using safe mode for CaveFinder.");
140+
else
141+
ChatUtils.message(
142+
"Shaders disabled - returning CaveFinder to normal mode.");
143+
}
144+
123145
boolean searchersChanged = coordinator.update();
124146

125147
if(searchersChanged)
@@ -136,6 +158,12 @@ public void onUpdate()
136158
notify = true;
137159
}
138160

161+
if(shaderSafeMode)
162+
{
163+
buildBufferSafeMode();
164+
return;
165+
}
166+
139167
// build the buffer
140168

141169
if(getMatchingBlocksTask == null)
@@ -176,6 +204,7 @@ public void onRender(PoseStack matrixStack, float partialTicks)
176204

177205
private void stopBuildingBuffer()
178206
{
207+
buildGeneration++;
179208
if(getMatchingBlocksTask != null)
180209
{
181210
getMatchingBlocksTask.cancel(true);
@@ -196,6 +225,7 @@ private void startGetMatchingBlocksTask()
196225
BlockPos eyesPos = BlockPos.containing(RotationUtils.getEyesPos());
197226
Comparator<BlockPos> comparator =
198227
Comparator.comparingInt(pos -> eyesPos.distManhattan(pos));
228+
currentBuildGeneration = buildGeneration;
199229

200230
getMatchingBlocksTask = forkJoinPool.submit(() -> coordinator
201231
.getMatches().parallel().map(ChunkSearcher.Result::pos)
@@ -205,6 +235,12 @@ private void startGetMatchingBlocksTask()
205235

206236
private void startCompileVerticesTask()
207237
{
238+
if(currentBuildGeneration != buildGeneration)
239+
{
240+
stopBuildingBuffer();
241+
return;
242+
}
243+
208244
HashSet<BlockPos> matchingBlocks = getMatchingBlocksTask.join();
209245

210246
if(matchingBlocks.size() < limit.getValueLog())
@@ -221,9 +257,53 @@ else if(notify)
221257
.submit(() -> BlockVertexCompiler.compile(matchingBlocks));
222258
}
223259

260+
private void buildBufferSafeMode()
261+
{
262+
if(bufferUpToDate)
263+
return;
264+
265+
if(getMatchingBlocksTask != null || compileVerticesTask != null)
266+
stopBuildingBuffer();
267+
268+
BlockPos eyesPos = BlockPos.containing(RotationUtils.getEyesPos());
269+
Comparator<BlockPos> comparator =
270+
Comparator.comparingInt(pos -> eyesPos.distManhattan(pos));
271+
java.util.ArrayList<ChunkSearcher.Result> matches =
272+
coordinator.getMatches().collect(
273+
java.util.stream.Collectors.toCollection(ArrayList::new));
274+
HashSet<BlockPos> matchingBlocks =
275+
matches.stream().map(ChunkSearcher.Result::pos).sorted(comparator)
276+
.limit(limit.getValueLog())
277+
.collect(Collectors.toCollection(HashSet::new));
278+
279+
if(matchingBlocks.size() < limit.getValueLog())
280+
notify = true;
281+
else if(notify)
282+
{
283+
ChatUtils.warning("CaveFinder found \u00a7lA LOT\u00a7r of blocks!"
284+
+ " To prevent lag, it will only show the closest \u00a76"
285+
+ limit.getValueString() + "\u00a7r results.");
286+
notify = false;
287+
}
288+
289+
ArrayList<int[]> vertices = BlockVertexCompiler.compile(matchingBlocks);
290+
setBufferFromVertices(vertices);
291+
}
292+
224293
private void setBufferFromTask()
225294
{
295+
if(currentBuildGeneration != buildGeneration)
296+
{
297+
stopBuildingBuffer();
298+
return;
299+
}
300+
226301
ArrayList<int[]> vertices = compileVerticesTask.join();
302+
setBufferFromVertices(vertices);
303+
}
304+
305+
private void setBufferFromVertices(ArrayList<int[]> vertices)
306+
{
227307
RegionPos region = RenderUtils.getCameraRegion();
228308

229309
if(vertexBuffer != null)
@@ -240,3 +320,6 @@ private void setBufferFromTask()
240320
bufferRegion = region;
241321
}
242322
}
323+
324+
325+

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88
package net.wurstclient.hacks;
99

10-
// ### ADDED IMPORTS ###
1110
import static net.wurstclient.WurstClient.MC;
1211

1312
import java.util.HashMap;
@@ -34,9 +33,8 @@
3433
import net.wurstclient.util.RenderUtils;
3534
import net.wurstclient.util.RotationUtils;
3635
import net.minecraft.world.phys.AABB;
37-
import net.wurstclient.events.PacketInputListener; // ### ADDED ###
38-
import net.wurstclient.events.PacketInputListener.PacketInputEvent; // ### ADDED
39-
// ###
36+
import net.wurstclient.events.PacketInputListener;
37+
import net.wurstclient.events.PacketInputListener.PacketInputEvent;
4038
import net.wurstclient.hack.Hack;
4139
import net.wurstclient.settings.CheckboxSetting;
4240
import net.wurstclient.settings.SliderSetting;
@@ -128,7 +126,6 @@ private enum EventCategory
128126

129127
static
130128
{
131-
// ########## DIRECT COPY OF WorldEvents IDS (with names) ##########
132129
// Doors / trapdoors
133130
register(1005, "IRON_DOOR_OPENS", EventCategory.DOORS);
134131
register(1011, "IRON_DOOR_CLOSES", EventCategory.DOORS);
@@ -254,10 +251,9 @@ private static final class EventMarker
254251

255252
public CoordLoggerHack()
256253
{
257-
super("CoordLogger"); // ### MODIFIED ### (Hack(String) only)
258-
setCategory(Category.OTHER); // you can move it to a better category if
259-
// you want
260-
254+
super("CoordLogger");
255+
setCategory(Category.OTHER);
256+
261257
// Settings in ClickGUI
262258
addSetting(minDistance);
263259
addSetting(playerTeleports);

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@
3838
import net.wurstclient.settings.SliderSetting.ValueDisplay;
3939
import net.wurstclient.util.BlockBreaker;
4040
import net.wurstclient.util.BlockUtils;
41+
import net.wurstclient.util.ChatUtils;
4142
import net.wurstclient.util.OverlayRenderer;
4243
import net.wurstclient.util.RenderUtils;
4344
import net.wurstclient.util.RotationUtils;
45+
import net.wurstclient.util.ShaderUtils;
4446

4547
public final class ExcavatorHack extends Hack
4648
implements UpdateListener, RenderListener, GUIRenderListener
@@ -52,6 +54,7 @@ public final class ExcavatorHack extends Hack
5254
new EnumSetting<>("Mode", Mode.values(), Mode.FAST);
5355

5456
private final OverlayRenderer overlay = new OverlayRenderer();
57+
private boolean shaderSafeMode;
5558

5659
private Step step;
5760
private BlockPos posLookingAt;
@@ -99,7 +102,10 @@ protected void onEnable()
99102
WURST.getHax().veinMinerHack.setEnabled(false);
100103

101104
step = Step.START_POS;
102-
105+
shaderSafeMode = ShaderUtils.refreshShadersActive();
106+
if(shaderSafeMode)
107+
ChatUtils
108+
.message("Shaders detected - using safe mode for Excavator.");
103109
EVENTS.add(UpdateListener.class, this);
104110
EVENTS.add(RenderListener.class, this);
105111
EVENTS.add(GUIRenderListener.class, this);
@@ -129,6 +135,18 @@ protected void onDisable()
129135
@Override
130136
public void onUpdate()
131137
{
138+
boolean currentShaderSafeMode = ShaderUtils.refreshShadersActive();
139+
if(currentShaderSafeMode != shaderSafeMode)
140+
{
141+
shaderSafeMode = currentShaderSafeMode;
142+
if(shaderSafeMode)
143+
ChatUtils.message(
144+
"Shaders detected - using safe mode for Excavator.");
145+
else
146+
ChatUtils.message(
147+
"Shaders disabled - returning Excavator to normal mode.");
148+
}
149+
132150
if(step.selectPos)
133151
handlePositionSelection();
134152
else if(step == Step.SCAN_AREA)
@@ -374,8 +392,14 @@ Comparator.<BlockPos> comparingInt(pos -> pos.getY()).reversed()
374392
Predicate<BlockPos> pBreakable = MC.player.getAbilities().instabuild
375393
? BlockUtils::canBeClicked : pos -> BlockUtils.canBeClicked(pos)
376394
&& !BlockUtils.isUnbreakable(pos);
377-
area.remainingBlocks =
378-
(int)area.blocksList.parallelStream().filter(pBreakable).count();
395+
java.util.List<BlockPos> blocksSnapshot =
396+
shaderSafeMode ? new ArrayList<>(area.blocksList) : null;
397+
if(shaderSafeMode)
398+
area.remainingBlocks =
399+
(int)blocksSnapshot.stream().filter(pBreakable).count();
400+
else
401+
area.remainingBlocks = (int)area.blocksList.parallelStream()
402+
.filter(pBreakable).count();
379403

380404
if(area.remainingBlocks == 0)
381405
{
@@ -385,8 +409,10 @@ Comparator.<BlockPos> comparingInt(pos -> pos.getY()).reversed()
385409

386410
if(pathFinder == null)
387411
{
388-
BlockPos closestBlock = area.blocksList.parallelStream()
389-
.filter(pBreakable).min(cNextTargetBlock).get();
412+
java.util.stream.Stream<BlockPos> blockStream = shaderSafeMode
413+
? blocksSnapshot.stream() : area.blocksList.parallelStream();
414+
BlockPos closestBlock =
415+
blockStream.filter(pBreakable).min(cNextTargetBlock).get();
390416

391417
pathFinder = new ExcavatorPathFinder(closestBlock);
392418
}
@@ -554,3 +580,6 @@ protected boolean checkDone()
554580
}
555581
}
556582
}
583+
584+
585+

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import net.wurstclient.util.RenderUtils;
5252
import net.wurstclient.util.RenderUtils.ColoredBox;
5353
import net.wurstclient.util.RenderUtils.ColoredPoint;
54+
import net.wurstclient.util.ShaderUtils;
5455

5556
@SearchTags({"player esp", "PlayerTracers", "player tracers"})
5657
public final class PlayerEspHack extends Hack implements UpdateListener,
@@ -68,6 +69,7 @@ public final class PlayerEspHack extends Hack implements UpdateListener,
6869
new FilterInvisibleSetting("Won't show invisible players.", false));
6970

7071
private final ArrayList<Player> players = new ArrayList<>();
72+
private boolean shaderSafeMode;
7173
private final Map<UUID, PendingEnterAlert> pendingEnterAlerts =
7274
new HashMap<>();
7375
// Alert settings & tracking for enter/exit notifications
@@ -203,6 +205,10 @@ protected void onEnable()
203205
EVENTS.add(CameraTransformViewBobbingListener.class, this);
204206
EVENTS.add(RenderListener.class, this);
205207
alertManager.addListener(alertListener);
208+
shaderSafeMode = ShaderUtils.refreshShadersActive();
209+
if(shaderSafeMode)
210+
ChatUtils
211+
.message("Shaders detected - using safe mode for PlayerESP.");
206212
}
207213

208214
@Override
@@ -219,13 +225,28 @@ protected void onDisable()
219225
@Override
220226
public void onUpdate()
221227
{
228+
boolean currentShaderSafeMode = ShaderUtils.refreshShadersActive();
229+
if(currentShaderSafeMode != shaderSafeMode)
230+
{
231+
shaderSafeMode = currentShaderSafeMode;
232+
if(shaderSafeMode)
233+
ChatUtils.message(
234+
"Shaders detected - using safe mode for PlayerESP.");
235+
else
236+
ChatUtils.message(
237+
"Shaders disabled - returning PlayerESP to normal mode.");
238+
}
239+
222240
players.clear();
223241

224-
Stream<AbstractClientPlayer> stream = MC.level.players()
225-
.parallelStream().filter(e -> !e.isRemoved() && e.getHealth() > 0)
226-
.filter(e -> e != MC.player)
227-
.filter(e -> !(e instanceof FakePlayerEntity))
228-
.filter(e -> Math.abs(e.getY() - MC.player.getY()) <= 1e6);
242+
java.util.List<AbstractClientPlayer> playerSnapshot =
243+
shaderSafeMode ? new ArrayList<>(MC.level.players()) : null;
244+
Stream<AbstractClientPlayer> stream = (shaderSafeMode
245+
? playerSnapshot.stream() : MC.level.players().parallelStream())
246+
.filter(e -> !e.isRemoved() && e.getHealth() > 0)
247+
.filter(e -> e != MC.player)
248+
.filter(e -> !(e instanceof FakePlayerEntity))
249+
.filter(e -> Math.abs(e.getY() - MC.player.getY()) <= 1e6);
229250

230251
// If enabled, filter out players that aren't present on the client's
231252
// player list (likely NPCs spawned by server plugins).
@@ -918,3 +939,6 @@ public String toString()
918939
}
919940
}
920941
}
942+
943+
944+

0 commit comments

Comments
 (0)