Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-11-08 17:20:24 +01:00
transform chunk bulk packets
Dieser Commit ist enthalten in:
Ursprung
8b65efc4bd
Commit
e436988303
@ -7,6 +7,7 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import us.myles.ViaVersion.chunks.ChunkManager;
|
||||||
import us.myles.ViaVersion.packets.State;
|
import us.myles.ViaVersion.packets.State;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -16,6 +17,7 @@ public class ConnectionInfo {
|
|||||||
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
||||||
|
|
||||||
private final SocketChannel channel;
|
private final SocketChannel channel;
|
||||||
|
private final ChunkManager chunkManager;
|
||||||
private Object lastPacket;
|
private Object lastPacket;
|
||||||
private java.util.UUID UUID;
|
private java.util.UUID UUID;
|
||||||
private State state = State.HANDSHAKE;
|
private State state = State.HANDSHAKE;
|
||||||
@ -29,6 +31,7 @@ public class ConnectionInfo {
|
|||||||
|
|
||||||
public ConnectionInfo(SocketChannel socketChannel) {
|
public ConnectionInfo(SocketChannel socketChannel) {
|
||||||
this.channel = socketChannel;
|
this.channel = socketChannel;
|
||||||
|
this.chunkManager = new ChunkManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package us.myles.ViaVersion.chunks;
|
package us.myles.ViaVersion.chunks;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -7,13 +8,13 @@ import org.bukkit.Bukkit;
|
|||||||
import us.myles.ViaVersion.ConnectionInfo;
|
import us.myles.ViaVersion.ConnectionInfo;
|
||||||
import us.myles.ViaVersion.util.PacketUtil;
|
import us.myles.ViaVersion.util.PacketUtil;
|
||||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||||
|
import us.myles.ViaVersion.util.ReflectionUtil.ClassReflection;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@ -33,21 +34,54 @@ public class ChunkManager {
|
|||||||
|
|
||||||
private final ConnectionInfo info;
|
private final ConnectionInfo info;
|
||||||
private final Set<Long> loadedChunks = Sets.newConcurrentHashSet();
|
private final Set<Long> loadedChunks = Sets.newConcurrentHashSet();
|
||||||
private Method getWorldHandle;
|
private final Set<Long> bulkChunks = Sets.newConcurrentHashSet();
|
||||||
private Method getChunkAt;
|
|
||||||
private Field getSections;
|
// Reflection
|
||||||
|
private static ClassReflection mapChunkBulkRef;
|
||||||
|
private static ClassReflection mapChunkRef;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
mapChunkBulkRef = new ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunkBulk"));
|
||||||
|
mapChunkRef = new ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunk"));
|
||||||
|
} catch(Exception e) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "Failed to initialise chunk reflection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ChunkManager(ConnectionInfo info) {
|
public ChunkManager(ConnectionInfo info) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a map chunk bulk in to separate map chunk packets.
|
||||||
|
* These packets are registered so that they will never be seen as unload packets.
|
||||||
|
*
|
||||||
|
* @param packet to transform
|
||||||
|
* @return List of chunk data packets
|
||||||
|
*/
|
||||||
|
public List<Object> transformMapChunkBulk(Object packet) {
|
||||||
|
List<Object> list = Lists.newArrayList();
|
||||||
try {
|
try {
|
||||||
this.getWorldHandle = ReflectionUtil.obc("CraftWorld").getDeclaredMethod("getHandle");
|
int[] xcoords = mapChunkBulkRef.getFieldValue("a", packet, int[].class);
|
||||||
this.getChunkAt = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class);
|
int[] zcoords = mapChunkBulkRef.getFieldValue("b", packet, int[].class);
|
||||||
this.getSections = ReflectionUtil.nms("Chunk").getDeclaredField("sections");
|
Object[] chunkMaps = mapChunkBulkRef.getFieldValue("c", packet, Object[].class);
|
||||||
getSections.setAccessible(true);
|
for(int i = 0; i < chunkMaps.length; i++) {
|
||||||
|
int x = xcoords[i];
|
||||||
|
int z = zcoords[i];
|
||||||
|
Object chunkMap = chunkMaps[i];
|
||||||
|
Object chunkPacket = mapChunkRef.newInstance();
|
||||||
|
mapChunkRef.setFieldValue("a", chunkPacket, x);
|
||||||
|
mapChunkRef.setFieldValue("b", chunkPacket, z);
|
||||||
|
mapChunkRef.setFieldValue("c", chunkPacket, chunkMap);
|
||||||
|
mapChunkRef.setFieldValue("d", chunkPacket, true); // Chunk bulk chunks are always ground-up
|
||||||
|
bulkChunks.add(toLong(x, z)); // Store for later
|
||||||
|
list.add(chunkPacket);
|
||||||
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
Bukkit.getLogger().log(Level.WARNING, "Failed to initialise chunk verification", e);
|
Bukkit.getLogger().log(Level.WARNING, "Failed to transform chunk bulk", e);
|
||||||
}
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,35 +110,15 @@ public class ChunkManager {
|
|||||||
usedSections.set(i);
|
usedSections.set(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unloading & empty chunks
|
|
||||||
int sectionCount = usedSections.cardinality(); // the amount of sections set
|
int sectionCount = usedSections.cardinality(); // the amount of sections set
|
||||||
if(sectionCount == 0 && groundUp) {
|
|
||||||
if(loadedChunks.contains(chunkHash)) {
|
|
||||||
// This is a chunk unload packet
|
|
||||||
loadedChunks.remove(chunkHash);
|
|
||||||
return new Chunk(chunkX, chunkZ);
|
|
||||||
} else {
|
|
||||||
// Check if chunk data is invalid
|
|
||||||
try {
|
|
||||||
Object nmsWorld = getWorldHandle.invoke(info.getPlayer().getWorld());
|
|
||||||
Object nmsChunk = getChunkAt.invoke(info.getPlayer().getWorld());
|
|
||||||
Object[] nmsSections = (Object[]) getSections.get(nmsChunk);
|
|
||||||
|
|
||||||
// Check if chunk is actually empty
|
// If the chunk is from a chunk bulk, it is never an unload packet
|
||||||
boolean isEmpty = false;
|
// Other wise, if it has no data, it is :)
|
||||||
int i = 0;
|
boolean isBulkPacket = bulkChunks.remove(chunkHash);
|
||||||
while(i < nmsSections.length) {
|
if(sectionCount == 0 && groundUp && !isBulkPacket && loadedChunks.contains(chunkHash)) {
|
||||||
if(!(isEmpty = nmsSections[i++] == null)) break;
|
// This is a chunk unload packet
|
||||||
}
|
loadedChunks.remove(chunkHash);
|
||||||
if(isEmpty) {
|
return new Chunk(chunkX, chunkZ);
|
||||||
// not empty, LOL
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
Bukkit.getLogger().log(Level.WARNING, "Failed to verify chunk", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int startIndex = input.readerIndex();
|
int startIndex = input.readerIndex();
|
||||||
|
@ -26,24 +26,7 @@ public class ViaChunkHandler extends MessageToMessageEncoder {
|
|||||||
info.setLastPacket(o);
|
info.setLastPacket(o);
|
||||||
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
||||||
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
||||||
final int[] locX = ReflectionUtil.get(o, "a", int[].class);
|
list.addAll(info.getChunkManager().transformMapChunkBulk(o));
|
||||||
final int[] locZ = ReflectionUtil.get(o, "b", int[].class);
|
|
||||||
final Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
|
|
||||||
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
|
|
||||||
final Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
|
|
||||||
for(int i = 0; i < locX.length; i++) {
|
|
||||||
int x = locX[i];
|
|
||||||
int z = locZ[i];
|
|
||||||
// world invoke function
|
|
||||||
try {
|
|
||||||
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
|
|
||||||
Object packet = constructor.newInstance(chunk, true, 65535);
|
|
||||||
list.add(packet);
|
|
||||||
} catch(InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ public class OutgoingTransformer {
|
|||||||
private final ViaVersionPlugin plugin = (ViaVersionPlugin) ViaVersion.getInstance();
|
private final ViaVersionPlugin plugin = (ViaVersionPlugin) ViaVersion.getInstance();
|
||||||
|
|
||||||
private final ConnectionInfo info;
|
private final ConnectionInfo info;
|
||||||
private final ChunkManager chunkManager;
|
|
||||||
private final Map<Integer, UUID> uuidMap = new HashMap<>();
|
private final Map<Integer, UUID> uuidMap = new HashMap<>();
|
||||||
private final Map<Integer, EntityType> clientEntityTypes = new HashMap<>();
|
private final Map<Integer, EntityType> clientEntityTypes = new HashMap<>();
|
||||||
private final Map<Integer, Integer> vehicleMap = new HashMap<>();
|
private final Map<Integer, Integer> vehicleMap = new HashMap<>();
|
||||||
@ -54,7 +53,6 @@ public class OutgoingTransformer {
|
|||||||
|
|
||||||
public OutgoingTransformer(ConnectionInfo info) {
|
public OutgoingTransformer(ConnectionInfo info) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
this.chunkManager = new ChunkManager(info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String fixJson(String line) {
|
public static String fixJson(String line) {
|
||||||
@ -781,6 +779,7 @@ public class OutgoingTransformer {
|
|||||||
}
|
}
|
||||||
if (packet == PacketType.PLAY_CHUNK_DATA) {
|
if (packet == PacketType.PLAY_CHUNK_DATA) {
|
||||||
// Read chunk
|
// Read chunk
|
||||||
|
ChunkManager chunkManager = info.getChunkManager();
|
||||||
Chunk chunk = chunkManager.readChunk(input);
|
Chunk chunk = chunkManager.readChunk(input);
|
||||||
if(chunk == null) {
|
if(chunk == null) {
|
||||||
throw new CancelException();
|
throw new CancelException();
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package us.myles.ViaVersion.util;
|
package us.myles.ViaVersion.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ReflectionUtil {
|
public class ReflectionUtil {
|
||||||
private static String BASE = Bukkit.getServer().getClass().getPackage().getName();
|
private static String BASE = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
@ -50,4 +54,74 @@ public class ReflectionUtil {
|
|||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
field.set(o, value);
|
field.set(o, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class ClassReflection {
|
||||||
|
private final Class<?> handle;
|
||||||
|
private final Map<String, Field> fields = Maps.newConcurrentMap();
|
||||||
|
private final Map<String, Method> methods = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
public ClassReflection(Class<?> handle) {
|
||||||
|
this(handle, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassReflection(Class<?> handle, boolean recursive) {
|
||||||
|
this.handle = handle;
|
||||||
|
scanFields(handle, recursive);
|
||||||
|
scanMethods(handle, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanFields(Class<?> host, boolean recursive) {
|
||||||
|
if(host.getSuperclass() != null && recursive) {
|
||||||
|
scanFields(host.getSuperclass(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Field field : host.getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
fields.put(field.getName(), field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanMethods(Class<?> host, boolean recursive) {
|
||||||
|
if(host.getSuperclass() != null && recursive) {
|
||||||
|
scanMethods(host.getSuperclass(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Method method : host.getDeclaredMethods()) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
methods.put(method.getName(), method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object newInstance() throws IllegalAccessException, InstantiationException {
|
||||||
|
return handle.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Field getField(String name) {
|
||||||
|
return fields.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldValue(String fieldName, Object instance, Object value) throws IllegalAccessException {
|
||||||
|
getField(fieldName).set(instance, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getFieldValue(String fieldName, Object instance, Class<T> type) throws IllegalAccessException {
|
||||||
|
return type.cast(getField(fieldName).get(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T invokeMethod(Class<T> type, String methodName, Object instance, Object... args) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
return type.cast(getMethod(methodName).invoke(instance, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getMethod(String name) {
|
||||||
|
return methods.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Field> getFields() {
|
||||||
|
return Collections.unmodifiableCollection(fields.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Method> getMethods() {
|
||||||
|
return Collections.unmodifiableCollection(methods.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren