1
1
package me .xginko .aef .modules .combat ;
2
2
3
- import com .github .benmanes .caffeine .cache .Cache ;
4
- import com .github .benmanes .caffeine .cache .Caffeine ;
5
- import com .github .benmanes .caffeine .cache .RemovalCause ;
6
3
import com .github .retrooper .packetevents .PacketEvents ;
7
4
import com .github .retrooper .packetevents .event .PacketListenerPriority ;
8
5
import com .github .retrooper .packetevents .event .PacketReceiveEvent ;
20
17
import org .bukkit .event .player .PlayerQuitEvent ;
21
18
import org .jetbrains .annotations .NotNull ;
22
19
23
- import java .time . Duration ;
20
+ import java .util . Map ;
24
21
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 ;
25
27
26
28
public class PortalGodMode extends PacketModule implements Listener {
27
29
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 ;
30
35
31
36
public PortalGodMode () {
32
37
super ("combat.portal-god-mode-patch" , false , PacketListenerPriority .MONITOR ,
33
38
"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 ,
38
46
"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." );
40
48
}
41
49
42
- private static class FallbackTeleportConfirm implements Runnable {
50
+ private static class ConfirmTimeout implements Runnable {
43
51
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 ;
46
56
47
- public FallbackTeleportConfirm (User user , int teleportId ) {
57
+ private ConfirmTimeout (PortalGodMode module , User user , int teleportId ) {
58
+ this .module = module ;
48
59
this .user = user ;
49
60
this .teleportId = teleportId ;
61
+ this .task = module .executorService .scheduleWithFixedDelay (this , module .timeoutMillis , 0L , TimeUnit .MILLISECONDS );
50
62
}
51
63
52
64
@ Override
53
65
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
+ }
55
80
}
56
81
}
57
82
58
83
@ Override
59
84
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 ()));
67
87
PacketEvents .getAPI ().getEventManager ().registerListener (asAbstract );
68
88
plugin .getServer ().getPluginManager ().registerEvents (this , plugin );
69
89
}
@@ -72,43 +92,51 @@ public void enable() {
72
92
public void disable () {
73
93
HandlerList .unregisterAll (this );
74
94
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 ;
79
103
}
80
104
}
81
105
82
106
@ Override
83
107
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 ;
89
110
}
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
+ );
90
118
}
91
119
92
120
@ Override
93
121
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 );
102
126
}
103
127
}
104
128
105
129
@ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
106
130
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
+ }
108
134
}
109
135
110
136
@ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
111
137
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
+ }
113
141
}
114
142
}
0 commit comments