11package 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 ;
63import com .github .retrooper .packetevents .PacketEvents ;
74import com .github .retrooper .packetevents .event .PacketListenerPriority ;
85import com .github .retrooper .packetevents .event .PacketReceiveEvent ;
2017import org .bukkit .event .player .PlayerQuitEvent ;
2118import org .jetbrains .annotations .NotNull ;
2219
23- import java .time . Duration ;
20+ import java .util . Map ;
2421import 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
2628public 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