Skip to content

Commit 29c495e

Browse files
committed
fix portal-god-mode patch
1 parent c28c154 commit 29c495e

File tree

2 files changed

+131
-81
lines changed

2 files changed

+131
-81
lines changed
Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package me.xginko.aef.modules.combat;
22

3-
import com.github.benmanes.caffeine.cache.Cache;
4-
import com.github.benmanes.caffeine.cache.Caffeine;
5-
import com.github.benmanes.caffeine.cache.RemovalCause;
63
import com.github.retrooper.packetevents.PacketEvents;
74
import com.github.retrooper.packetevents.event.PacketListenerPriority;
85
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
@@ -11,6 +8,7 @@
118
import com.github.retrooper.packetevents.protocol.player.User;
129
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientTeleportConfirm;
1310
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPlayerPositionAndLook;
11+
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
1412
import me.xginko.aef.modules.packets.PacketModule;
1513
import org.bukkit.event.EventHandler;
1614
import org.bukkit.event.EventPriority;
@@ -20,50 +18,70 @@
2018
import org.bukkit.event.player.PlayerQuitEvent;
2119
import org.jetbrains.annotations.NotNull;
2220

23-
import java.time.Duration;
21+
import java.util.Map;
2422
import java.util.UUID;
23+
import java.util.concurrent.ConcurrentHashMap;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.function.Consumer;
2526

2627
public class PortalGodMode extends PacketModule implements Listener {
2728

28-
private final long delayMillis;
29-
private Cache<UUID, FallbackTeleportConfirm> cachedFallbackConfirms;
29+
private final long timeoutMillis;
30+
private final boolean kickPlayer, log;
31+
32+
private Map<UUID, ConfirmTimeout> timeoutMap;
3033

3134
public PortalGodMode() {
3235
super("combat.portal-god-mode-patch", false, PacketListenerPriority.MONITOR, """
3336
Prevents an exploit that allows players to stand in nether portals and not\s
3437
take damage indefinitely by just never sending a TeleportConfirm packet to\s
3538
the server.\s
3639
A similar method is used for the chorus tp exploit.""");
37-
this.delayMillis = config.getInt(configPath + ".client-wait-millis", 300, """
40+
this.log = config.getBoolean(configPath + ".log", true);
41+
this.kickPlayer = config.getBoolean(configPath + ".kick-player", true,
42+
"If set to false, will simulate a TeleportConfirm packet to the server.");
43+
this.timeoutMillis = config.getLong(configPath + ".client-wait-millis", 1000, """
3844
How many millis to wait for the client to respond with a TeleportConfirm\s
3945
packet before we simulate it.""");
4046
}
4147

42-
private static class FallbackTeleportConfirm implements Runnable {
48+
private static class ConfirmTimeout implements Consumer<ScheduledTask> {
4349

44-
public final User user;
45-
public final int teleportId;
50+
private final PortalGodMode module;
51+
private final User user;
52+
private final int teleportId;
53+
private final ScheduledTask task;
4654

47-
public FallbackTeleportConfirm(User user, int teleportId) {
55+
private ConfirmTimeout(PortalGodMode module, User user, int teleportId) {
56+
this.module = module;
4857
this.user = user;
4958
this.teleportId = teleportId;
59+
this.task = module.plugin.getServer().getAsyncScheduler()
60+
.runDelayed(module.plugin, this, module.timeoutMillis, TimeUnit.MILLISECONDS);
5061
}
5162

5263
@Override
53-
public void run() {
54-
user.receivePacketSilently(new WrapperPlayClientTeleportConfirm(teleportId));
64+
public void accept(ScheduledTask task) {
65+
if (user == null) return;
66+
67+
if (module.log) {
68+
module.warn("Player '" + user.getName() + "' either lagging or hacking. " +
69+
"No TeleportConfirm in " + module.timeoutMillis + "ms!");
70+
}
71+
72+
if (module.kickPlayer) {
73+
if (module.log) module.warn("Disconnecting " + user.getName() + ".");
74+
user.closeConnection();
75+
} else {
76+
if (module.log) module.warn("Simulating a TeleportConfirm response from " + user.getName() + ".");
77+
user.receivePacketSilently(new WrapperPlayClientTeleportConfirm(teleportId));
78+
}
5579
}
5680
}
5781

5882
@Override
5983
public void enable() {
60-
cachedFallbackConfirms = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(delayMillis))
61-
.<UUID, FallbackTeleportConfirm>evictionListener((uuid , confirm , cause) -> {
62-
if (cause == RemovalCause.EXPIRED && confirm != null) {
63-
confirm.run();
64-
}
65-
})
66-
.build();
84+
timeoutMap = new ConcurrentHashMap<>(Math.max(16, plugin.getServer().getOnlinePlayers().size()));
6785
PacketEvents.getAPI().getEventManager().registerListener(asAbstract);
6886
plugin.getServer().getPluginManager().registerEvents(this, plugin);
6987
}
@@ -72,43 +90,47 @@ public void enable() {
7290
public void disable() {
7391
HandlerList.unregisterAll(this);
7492
PacketEvents.getAPI().getEventManager().unregisterListener(asAbstract);
75-
if (cachedFallbackConfirms != null) {
76-
cachedFallbackConfirms.invalidateAll();
77-
cachedFallbackConfirms.cleanUp();
78-
cachedFallbackConfirms = null;
93+
if (timeoutMap != null) {
94+
timeoutMap.forEach(((uuid, timeout) -> timeout.task.cancel()));
95+
timeoutMap.clear();
96+
timeoutMap = null;
7997
}
8098
}
8199

82100
@Override
83101
public void onPacketSend(@NotNull PacketSendEvent event) {
84-
if (event.getPacketType() == PacketType.Play.Server.PLAYER_POSITION_AND_LOOK) {
85-
cachedFallbackConfirms.put(
86-
event.getUser().getUUID(),
87-
new FallbackTeleportConfirm(event.getUser(), new WrapperPlayServerPlayerPositionAndLook(event).getTeleportId())
88-
);
102+
if (event.getPacketType() != PacketType.Play.Server.PLAYER_POSITION_AND_LOOK) {
103+
return;
104+
}
105+
if (timeoutMap.containsKey(event.getUser().getUUID())) {
106+
timeoutMap.get(event.getUser().getUUID()).task.cancel();
89107
}
108+
timeoutMap.put(
109+
event.getUser().getUUID(),
110+
new ConfirmTimeout(this, event.getUser(), new WrapperPlayServerPlayerPositionAndLook(event).getTeleportId())
111+
);
90112
}
91113

92114
@Override
93115
public void onPacketReceive(PacketReceiveEvent event) {
94-
if (event.getPacketType() != PacketType.Play.Client.TELEPORT_CONFIRM) {
95-
return;
96-
}
97-
98-
FallbackTeleportConfirm fallbackTeleportConfirm = cachedFallbackConfirms.getIfPresent(event.getUser().getUUID());
99-
100-
if (fallbackTeleportConfirm != null && new WrapperPlayClientTeleportConfirm(event).getTeleportId() == fallbackTeleportConfirm.teleportId) {
101-
cachedFallbackConfirms.invalidate(event.getUser().getUUID());
116+
if (event.getPacketType() == PacketType.Play.Client.TELEPORT_CONFIRM
117+
&& timeoutMap.containsKey(event.getUser().getUUID())
118+
&& new WrapperPlayClientTeleportConfirm(event).getTeleportId() == timeoutMap.get(event.getUser().getUUID()).teleportId) {
119+
timeoutMap.remove(event.getUser().getUUID()).task.cancel();
102120
}
103121
}
104122

105123
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
106124
private void onPlayerQuit(PlayerQuitEvent event) {
107-
cachedFallbackConfirms.invalidate(event.getPlayer().getUniqueId());
125+
if (timeoutMap.containsKey(event.getPlayer().getUniqueId())) {
126+
timeoutMap.remove(event.getPlayer().getUniqueId()).task.cancel();
127+
}
108128
}
109129

110130
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
111131
private void onPlayerKick(PlayerKickEvent event) {
112-
cachedFallbackConfirms.invalidate(event.getPlayer().getUniqueId());
132+
if (timeoutMap.containsKey(event.getPlayer().getUniqueId())) {
133+
timeoutMap.remove(event.getPlayer().getUniqueId()).task.cancel();
134+
}
113135
}
114136
}
Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package me.xginko.aef.modules.combat;
22

3-
import com.github.benmanes.caffeine.cache.Cache;
4-
import com.github.benmanes.caffeine.cache.Caffeine;
5-
import com.github.benmanes.caffeine.cache.RemovalCause;
63
import com.github.retrooper.packetevents.PacketEvents;
74
import com.github.retrooper.packetevents.event.PacketListenerPriority;
85
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
@@ -20,50 +17,73 @@
2017
import org.bukkit.event.player.PlayerQuitEvent;
2118
import org.jetbrains.annotations.NotNull;
2219

23-
import java.time.Duration;
20+
import java.util.Map;
2421
import java.util.UUID;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.concurrent.Executors;
24+
import java.util.concurrent.ScheduledExecutorService;
25+
import java.util.concurrent.ScheduledFuture;
26+
import java.util.concurrent.TimeUnit;
2527

2628
public class PortalGodMode extends PacketModule implements Listener {
2729

28-
private final long delayMillis;
29-
private Cache<UUID, FallbackTeleportConfirm> cachedFallbackConfirms;
30+
private final long timeoutMillis;
31+
private final boolean kickPlayer, log;
32+
33+
private ScheduledExecutorService executorService;
34+
private Map<UUID, ConfirmTimeout> timeoutMap;
3035

3136
public PortalGodMode() {
3237
super("combat.portal-god-mode-patch", false, PacketListenerPriority.MONITOR,
3338
"Prevents an exploit that allows players to stand in nether portals and not\n" +
34-
"take damage indefinitely by just never sending a TeleportConfirm packet to\n" +
35-
"the server.\n" +
36-
"A similar method is used for the chorus tp exploit.");
37-
this.delayMillis = config.getInt(configPath + ".client-wait-millis", 300,
39+
"take damage indefinitely by just never sending a TeleportConfirm packet to\n" +
40+
"the server.\n" +
41+
"A similar method is used for the chorus tp exploit.");
42+
this.log = config.getBoolean(configPath + ".log", true);
43+
this.kickPlayer = config.getBoolean(configPath + ".kick-player", true,
44+
"If set to false, will simulate a TeleportConfirm packet to the server.");
45+
this.timeoutMillis = config.getLong(configPath + ".client-wait-millis", 1000,
3846
"How many millis to wait for the client to respond with a TeleportConfirm\n" +
39-
"packet before we simulate it.");
47+
"packet before we simulate it.");
4048
}
4149

42-
private static class FallbackTeleportConfirm implements Runnable {
50+
private static class ConfirmTimeout implements Runnable {
4351

44-
public final User user;
45-
public final int teleportId;
52+
private final PortalGodMode module;
53+
private final User user;
54+
private final int teleportId;
55+
private final ScheduledFuture<?> task;
4656

47-
public FallbackTeleportConfirm(User user, int teleportId) {
57+
private ConfirmTimeout(PortalGodMode module, User user, int teleportId) {
58+
this.module = module;
4859
this.user = user;
4960
this.teleportId = teleportId;
61+
this.task = module.executorService.scheduleWithFixedDelay(this, module.timeoutMillis, 0L, TimeUnit.MILLISECONDS);
5062
}
5163

5264
@Override
5365
public void run() {
54-
user.receivePacketSilently(new WrapperPlayClientTeleportConfirm(teleportId));
66+
if (user == null) return;
67+
68+
if (module.log) {
69+
module.warn("Player '" + user.getName() + "' either lagging or hacking. " +
70+
"No TeleportConfirm in " + module.timeoutMillis + "ms!");
71+
}
72+
73+
if (module.kickPlayer) {
74+
if (module.log) module.warn("Disconnecting " + user.getName() + ".");
75+
user.closeConnection();
76+
} else {
77+
if (module.log) module.warn("Simulating a TeleportConfirm response from " + user.getName() + ".");
78+
user.receivePacketSilently(new WrapperPlayClientTeleportConfirm(teleportId));
79+
}
5580
}
5681
}
5782

5883
@Override
5984
public void enable() {
60-
cachedFallbackConfirms = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(delayMillis))
61-
.<UUID, FallbackTeleportConfirm>evictionListener((uuid , confirm , cause) -> {
62-
if (cause == RemovalCause.EXPIRED && confirm != null) {
63-
confirm.run();
64-
}
65-
})
66-
.build();
85+
executorService = Executors.newScheduledThreadPool(4);
86+
timeoutMap = new ConcurrentHashMap<>(Math.max(16, plugin.getServer().getOnlinePlayers().size()));
6787
PacketEvents.getAPI().getEventManager().registerListener(asAbstract);
6888
plugin.getServer().getPluginManager().registerEvents(this, plugin);
6989
}
@@ -72,43 +92,51 @@ public void enable() {
7292
public void disable() {
7393
HandlerList.unregisterAll(this);
7494
PacketEvents.getAPI().getEventManager().unregisterListener(asAbstract);
75-
if (cachedFallbackConfirms != null) {
76-
cachedFallbackConfirms.invalidateAll();
77-
cachedFallbackConfirms.cleanUp();
78-
cachedFallbackConfirms = null;
95+
if (timeoutMap != null) {
96+
timeoutMap.forEach(((uuid, timeout) -> timeout.task.cancel(true)));
97+
timeoutMap.clear();
98+
timeoutMap = null;
99+
}
100+
if (executorService != null) {
101+
executorService.shutdownNow();
102+
executorService = null;
79103
}
80104
}
81105

82106
@Override
83107
public void onPacketSend(@NotNull PacketSendEvent event) {
84-
if (event.getPacketType() == PacketType.Play.Server.PLAYER_POSITION_AND_LOOK) {
85-
cachedFallbackConfirms.put(
86-
event.getUser().getUUID(),
87-
new FallbackTeleportConfirm(event.getUser(), new WrapperPlayServerPlayerPositionAndLook(event).getTeleportId())
88-
);
108+
if (event.getPacketType() != PacketType.Play.Server.PLAYER_POSITION_AND_LOOK) {
109+
return;
89110
}
111+
if (timeoutMap.containsKey(event.getUser().getUUID())) {
112+
timeoutMap.get(event.getUser().getUUID()).task.cancel(true);
113+
}
114+
timeoutMap.put(
115+
event.getUser().getUUID(),
116+
new ConfirmTimeout(this, event.getUser(), new WrapperPlayServerPlayerPositionAndLook(event).getTeleportId())
117+
);
90118
}
91119

92120
@Override
93121
public void onPacketReceive(PacketReceiveEvent event) {
94-
if (event.getPacketType() != PacketType.Play.Client.TELEPORT_CONFIRM) {
95-
return;
96-
}
97-
98-
FallbackTeleportConfirm fallbackTeleportConfirm = cachedFallbackConfirms.getIfPresent(event.getUser().getUUID());
99-
100-
if (fallbackTeleportConfirm != null && new WrapperPlayClientTeleportConfirm(event).getTeleportId() == fallbackTeleportConfirm.teleportId) {
101-
cachedFallbackConfirms.invalidate(event.getUser().getUUID());
122+
if (event.getPacketType() == PacketType.Play.Client.TELEPORT_CONFIRM
123+
&& timeoutMap.containsKey(event.getUser().getUUID())
124+
&& new WrapperPlayClientTeleportConfirm(event).getTeleportId() == timeoutMap.get(event.getUser().getUUID()).teleportId) {
125+
timeoutMap.remove(event.getUser().getUUID()).task.cancel(true);
102126
}
103127
}
104128

105129
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
106130
private void onPlayerQuit(PlayerQuitEvent event) {
107-
cachedFallbackConfirms.invalidate(event.getPlayer().getUniqueId());
131+
if (timeoutMap.containsKey(event.getPlayer().getUniqueId())) {
132+
timeoutMap.remove(event.getPlayer().getUniqueId()).task.cancel(true);
133+
}
108134
}
109135

110136
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
111137
private void onPlayerKick(PlayerKickEvent event) {
112-
cachedFallbackConfirms.invalidate(event.getPlayer().getUniqueId());
138+
if (timeoutMap.containsKey(event.getPlayer().getUniqueId())) {
139+
timeoutMap.remove(event.getPlayer().getUniqueId()).task.cancel(true);
140+
}
113141
}
114142
}

0 commit comments

Comments
 (0)