-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Removed unnecessary section wrapping of sortings and replaced with list format * Created base PacketListener to handle interception of TAB list * Loaded PacketListener * Added write packet interception and recognition of update info packet * Added filtering of game mode enum by spectator * Added replacement logic * Completed first working implementation of replacing packet * Switched FoliaTpsProvider system to provide global TPS * Added hide-spectator-mode-self option * Implemented hide-spectator-mode-self option * Fixed constructor call * Fixed invalid uuid retrieval * Extracted method should hide self Fixed some inconsistencies * Removed hide-spectator-self option * Set the folia TPS provider to 15 seconds --------- Co-authored-by: Netherwhal <[email protected]>
- Loading branch information
1 parent
ee8dc94
commit 764367e
Showing
5 changed files
with
233 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
src/main/java/net/simplyvanilla/simplytab/packets/PacketListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package net.simplyvanilla.simplytab.packets; | ||
|
||
import io.netty.channel.*; | ||
import org.bukkit.Bukkit; | ||
import org.bukkit.entity.Player; | ||
import org.bukkit.event.EventHandler; | ||
import org.bukkit.event.Listener; | ||
import org.bukkit.event.player.PlayerJoinEvent; | ||
import org.bukkit.event.player.PlayerQuitEvent; | ||
|
||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import java.util.*; | ||
import java.util.stream.Stream; | ||
|
||
public class PacketListener implements Listener { | ||
private static final String UPDATE_INFO_PACKET = "ClientboundPlayerInfoUpdatePacket"; | ||
private static final String UPDATE_GAME_MODE_PACKET = "UPDATE_GAME_MODE"; | ||
private static final String GAME_MODE_ENUM_NAME = "EnumGamemode"; | ||
private static final String SPECTATOR_ENUM_NAME = "SPECTATOR"; | ||
private static final String REPLACEMENT_ENUM = "c"; // ADVENTURE | ||
|
||
@EventHandler | ||
public void onPlayerJoin(PlayerJoinEvent event) { | ||
injectPlayer(event.getPlayer()); | ||
} | ||
|
||
@EventHandler | ||
public void onPlayerQuit(PlayerQuitEvent event) { | ||
removePlayer(event.getPlayer()); | ||
} | ||
|
||
public void loadAll() { | ||
Bukkit.getOnlinePlayers().forEach(this::injectPlayer); | ||
} | ||
|
||
public void unloadAll() { | ||
Bukkit.getOnlinePlayers().forEach(this::removePlayer); | ||
} | ||
|
||
private void injectPlayer(Player player) { | ||
ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { | ||
|
||
@Override | ||
public void write(ChannelHandlerContext channelHandlerContext, Object packet, ChannelPromise promise) throws Exception { | ||
try { | ||
if (!packet.toString().contains(UPDATE_INFO_PACKET)) return; | ||
|
||
EnumSet<?> actions = getFieldObject(packet, EnumSet.class.getSimpleName()); | ||
List<Object> previousEntries = getFieldObject(packet, List.class.getSimpleName()); | ||
List<Object> entries = new LinkedList<>(previousEntries); | ||
if (actions.stream().noneMatch(p -> p.name().equals(UPDATE_GAME_MODE_PACKET))) return; | ||
|
||
for (int i = 0; i < entries.size(); i++) { | ||
Object entry = entries.get(i); | ||
|
||
Field gameTypeField = getField(entry, GAME_MODE_ENUM_NAME); | ||
Enum<?> gameType = (Enum<?>) gameTypeField.get(entry); | ||
if (gameType == null) continue; | ||
if (!gameType.name().equals(SPECTATOR_ENUM_NAME)) continue; | ||
|
||
UUID uuid = getFieldObject(entry, UUID.class.getSimpleName()); | ||
if (player.getUniqueId().equals(uuid)) continue; | ||
|
||
Class<?> clazz = gameType.getClass(); | ||
Field replaceField = clazz.getDeclaredField(REPLACEMENT_ENUM); | ||
replaceField.setAccessible(true); | ||
Enum<?> replacement = (Enum<?>) replaceField.get(clazz); | ||
|
||
Class<?> entryClass = entry.getClass(); | ||
Field[] fields = entryClass.getDeclaredFields(); | ||
Object[] objects = Arrays.stream(fields).peek(f -> f.setAccessible(true)).map(f -> { | ||
try { | ||
if (f.getType().getSimpleName().equals(GAME_MODE_ENUM_NAME)) return replacement; | ||
return f.get(entry); | ||
} catch (IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}).toList().toArray(new Object[0]); | ||
Class<?>[] classes = Arrays.stream(fields).map(Field::getType).toList().toArray(new Class[0]); | ||
|
||
Constructor<?> entryConstructor = entryClass.getDeclaredConstructor(classes); | ||
Object newEntry = entryConstructor.newInstance(objects); | ||
entries.set(i, newEntry); | ||
} | ||
|
||
if (previousEntries.equals(entries)) return; | ||
|
||
Class<?> packetClass = packet.getClass(); | ||
Constructor<?> constructor = packetClass.getDeclaredConstructor(EnumSet.class, List.class); | ||
constructor.setAccessible(true); | ||
packet = constructor.newInstance(actions, entries); | ||
|
||
} finally { | ||
super.write(channelHandlerContext, packet, promise); | ||
} | ||
} | ||
|
||
}; | ||
|
||
ChannelPipeline pipeline = getPlayerChannel(player).pipeline(); | ||
pipeline.addBefore("packet_handler", player.getName(), channelDuplexHandler); | ||
} | ||
|
||
private void removePlayer(Player player) { | ||
Channel channel = getPlayerChannel(player); | ||
channel.eventLoop().submit(() -> channel.pipeline().remove(player.getName())); | ||
} | ||
|
||
private Channel getPlayerChannel(Player player) { | ||
try { | ||
if (player == null) throw new NullPointerException("Player cannot be null"); | ||
|
||
// ((CraftPlayer) player).getHandle() | ||
Method getHandle = Arrays.stream(player.getClass().getMethods()) | ||
.filter(m -> m.getParameterCount() == 0) | ||
.filter(m -> m.getReturnType().getSimpleName().equals("EntityPlayer")) | ||
.findFirst().orElse(null); | ||
if (getHandle == null) | ||
throw new NoSuchMethodException(String.format("Could not find method EntityPlayer() in %s", player.getClass().getCanonicalName())); | ||
Object handle = getHandle.invoke(player); | ||
|
||
// handle.playerConnection | ||
Object playerConnection = getFieldObject(handle, "PlayerConnection"); | ||
|
||
// playerConnection.networkManager | ||
Object networkManager = getFieldObject(playerConnection, "NetworkManager"); | ||
|
||
// networkManager.channel | ||
return getFieldObject(networkManager, "Channel"); | ||
} catch (Exception e) { | ||
if (e instanceof RuntimeException) throw (RuntimeException) e; | ||
else throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private <T> T getFieldObject(Object object, String typeName) { | ||
try { | ||
Field field = getField(object, typeName); | ||
return (T) field.get(object); | ||
} catch (Exception e) { | ||
if (e instanceof RuntimeException) throw (RuntimeException) e; | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private Field getField(Object object, String typeName) { | ||
try { | ||
Field field = Stream.concat(Arrays.stream(object.getClass().getFields()), | ||
Arrays.stream(object.getClass().getDeclaredFields())) | ||
.filter(f -> f.getType().getSimpleName().equals(typeName)) | ||
.findFirst().orElse(null); | ||
if (field == null) | ||
throw new NoSuchFieldException(String.format("Could not find field %s in %s", typeName, object.getClass().getCanonicalName())); | ||
field.setAccessible(true); | ||
return field; | ||
} catch (Exception e) { | ||
if (e instanceof RuntimeException) throw (RuntimeException) e; | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters