342 lines
12 KiB
Java
342 lines
12 KiB
Java
package us.myles.ViaVersion;
|
|
|
|
import com.google.common.base.Charsets;
|
|
import com.google.common.base.Preconditions;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
|
import io.netty.handler.codec.MessageToByteEncoder;
|
|
import us.myles.ViaVersion.chunks.MagicBitSet;
|
|
import us.myles.ViaVersion.chunks.PacketChunk;
|
|
import us.myles.ViaVersion.chunks.PacketChunkData;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.ShortBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.BitSet;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
|
|
public class PacketUtil {
|
|
private static Method DECODE_METHOD;
|
|
private static Method ENCODE_METHOD;
|
|
|
|
static {
|
|
try {
|
|
DECODE_METHOD = ByteToMessageDecoder.class.getDeclaredMethod("decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
|
|
DECODE_METHOD.setAccessible(true);
|
|
} catch (NoSuchMethodException e) {
|
|
e.printStackTrace();
|
|
System.out.println("Netty issue?");
|
|
}
|
|
try {
|
|
ENCODE_METHOD = MessageToByteEncoder.class.getDeclaredMethod("encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
|
|
ENCODE_METHOD.setAccessible(true);
|
|
} catch (NoSuchMethodException e) {
|
|
e.printStackTrace();
|
|
System.out.println("Netty issue?");
|
|
}
|
|
}
|
|
|
|
public static ByteBuf decompress(ChannelHandlerContext ctx, ByteBuf msg) {
|
|
ByteToMessageDecoder x = (ByteToMessageDecoder) ctx.pipeline().get("decompress");
|
|
List<Object> output = new ArrayList<Object>();
|
|
try {
|
|
PacketUtil.DECODE_METHOD.invoke(x, ctx, msg, output);
|
|
} catch (IllegalAccessException e) {
|
|
e.printStackTrace();
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return output.size() == 0 ? null : (ByteBuf) output.get(0);
|
|
}
|
|
|
|
public static ByteBuf compress(ChannelHandlerContext ctx, ByteBuf msg) {
|
|
MessageToByteEncoder x = (MessageToByteEncoder) ctx.pipeline().get("compress");
|
|
ByteBuf output = ctx.alloc().buffer();
|
|
try {
|
|
PacketUtil.ENCODE_METHOD.invoke(x, ctx, msg, output);
|
|
} catch (IllegalAccessException e) {
|
|
e.printStackTrace();
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return output;
|
|
}
|
|
|
|
/* I take no credit, these are taken from BungeeCord */
|
|
// https://github.com/SpigotMC/BungeeCord/blob/master/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java
|
|
public static void writeString(String s, ByteBuf buf) {
|
|
Preconditions.checkArgument(s.length() <= Short.MAX_VALUE, "Cannot send string longer than Short.MAX_VALUE (got %s characters)", s.length());
|
|
|
|
byte[] b = s.getBytes(Charsets.UTF_8);
|
|
writeVarInt(b.length, buf);
|
|
buf.writeBytes(b);
|
|
}
|
|
|
|
public static String readString(ByteBuf buf) {
|
|
int len = readVarInt(buf);
|
|
Preconditions.checkArgument(len <= Short.MAX_VALUE, "Cannot receive string longer than Short.MAX_VALUE (got %s characters)", len);
|
|
|
|
byte[] b = new byte[len];
|
|
buf.readBytes(b);
|
|
|
|
return new String(b, Charsets.UTF_8);
|
|
}
|
|
|
|
public static void writeArrayLegacy(byte[] b, ByteBuf buf, boolean allowExtended) {
|
|
// (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit
|
|
if (allowExtended) {
|
|
Preconditions.checkArgument(b.length <= (Integer.MAX_VALUE & 0x1FFF9A), "Cannot send array longer than 2097050 (got %s bytes)", b.length);
|
|
} else {
|
|
Preconditions.checkArgument(b.length <= Short.MAX_VALUE, "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length);
|
|
}
|
|
// Write a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only)
|
|
// No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour.
|
|
writeVarShort(buf, b.length);
|
|
buf.writeBytes(b);
|
|
}
|
|
|
|
public static byte[] readArrayLegacy(ByteBuf buf) {
|
|
// Read in a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only)
|
|
// No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour.
|
|
int len = readVarShort(buf);
|
|
|
|
// (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit
|
|
Preconditions.checkArgument(len <= (Integer.MAX_VALUE & 0x1FFF9A), "Cannot receive array longer than 2097050 (got %s bytes)", len);
|
|
|
|
byte[] ret = new byte[len];
|
|
buf.readBytes(ret);
|
|
return ret;
|
|
}
|
|
|
|
public static void writeArray(byte[] b, ByteBuf buf) {
|
|
writeVarInt(b.length, buf);
|
|
buf.writeBytes(b);
|
|
}
|
|
|
|
public static byte[] readArray(ByteBuf buf) {
|
|
byte[] ret = new byte[readVarInt(buf)];
|
|
buf.readBytes(ret);
|
|
return ret;
|
|
}
|
|
|
|
public static void writeStringArray(List<String> s, ByteBuf buf) {
|
|
writeVarInt(s.size(), buf);
|
|
for (String str : s) {
|
|
writeString(str, buf);
|
|
}
|
|
}
|
|
|
|
public static List<String> readStringArray(ByteBuf buf) {
|
|
int len = readVarInt(buf);
|
|
List<String> ret = new ArrayList<String>(len);
|
|
for (int i = 0; i < len; i++) {
|
|
ret.add(readString(buf));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public static int readVarInt(ByteBuf input) {
|
|
return readVarInt(input, 5);
|
|
}
|
|
|
|
public static int readVarInt(ByteBuf input, int maxBytes) {
|
|
int out = 0;
|
|
int bytes = 0;
|
|
byte in;
|
|
while (true) {
|
|
in = input.readByte();
|
|
|
|
out |= (in & 0x7F) << (bytes++ * 7);
|
|
|
|
if (bytes > maxBytes) {
|
|
throw new RuntimeException("VarInt too big");
|
|
}
|
|
|
|
if ((in & 0x80) != 0x80) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
public static void writeVarInt(int value, ByteBuf output) {
|
|
int part;
|
|
while (true) {
|
|
part = value & 0x7F;
|
|
|
|
value >>>= 7;
|
|
if (value != 0) {
|
|
part |= 0x80;
|
|
}
|
|
|
|
output.writeByte(part);
|
|
|
|
if (value == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int readVarShort(ByteBuf buf) {
|
|
int low = buf.readUnsignedShort();
|
|
int high = 0;
|
|
if ((low & 0x8000) != 0) {
|
|
low = low & 0x7FFF;
|
|
high = buf.readUnsignedByte();
|
|
}
|
|
return ((high & 0xFF) << 15) | low;
|
|
}
|
|
|
|
public static void writeVarShort(ByteBuf buf, int toWrite) {
|
|
int low = toWrite & 0x7FFF;
|
|
int high = (toWrite & 0x7F8000) >> 15;
|
|
if (high != 0) {
|
|
low = low | 0x8000;
|
|
}
|
|
buf.writeShort(low);
|
|
if (high != 0) {
|
|
buf.writeByte(high);
|
|
}
|
|
}
|
|
|
|
public static void writeUUID(UUID value, ByteBuf output) {
|
|
output.writeLong(value.getMostSignificantBits());
|
|
output.writeLong(value.getLeastSignificantBits());
|
|
}
|
|
|
|
public static UUID readUUID(ByteBuf input) {
|
|
return new UUID(input.readLong(), input.readLong());
|
|
}
|
|
|
|
public static void writeLongs(long[] data, ByteBuf output) {
|
|
for (int index = 0; index < data.length; index++) {
|
|
output.writeLong(data[index]);
|
|
}
|
|
}
|
|
|
|
public static long[] readLongs(int amount, ByteBuf output) {
|
|
long data[] = new long[amount];
|
|
for (int index = 0; index < amount; index++) {
|
|
data[index] = output.readLong();
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// This method is based on one from here
|
|
// https://github.com/Steveice10/MCProtocolLib/blob/master/src/main/java/org/spacehq/mc/protocol/util/NetUtil.java
|
|
public static PacketChunk readChunkData(boolean isFullChunk, int bitmask, byte[] input) {
|
|
PacketChunkData[] chunks = new PacketChunkData[16];
|
|
boolean sky = false;
|
|
int expected = 0;
|
|
int position = 0;
|
|
ShortBuffer blockData = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
|
|
|
for (int i = 0; i < chunks.length; i++) {
|
|
if ((bitmask & 1 << i) != 0) {
|
|
expected += (4096 * 2) + 2048;
|
|
}
|
|
}
|
|
// If there is more data, then there must be skylights :D
|
|
if (input.length > expected) {
|
|
sky = true;
|
|
}
|
|
|
|
// Read block data
|
|
for (int i = 0; i < chunks.length; i++) {
|
|
if ((bitmask & 1 << i) != 0) {
|
|
chunks[i] = new PacketChunkData(sky);
|
|
blockData.position(position / 2);
|
|
blockData.get(chunks[i].getBlocks(), 0, 4096);
|
|
position = position + (4096 * 2);
|
|
} else {
|
|
chunks[i] = new PacketChunkData(sky);
|
|
}
|
|
}
|
|
// Read blocklight data
|
|
for (int i = 0; i < chunks.length; i++) {
|
|
if ((bitmask & 1 << i) != 0) {
|
|
System.arraycopy(input, position, chunks[i].getBlockLight(), 0, 2048);
|
|
position = position + 2048;
|
|
}
|
|
}
|
|
// Read skylight data
|
|
if (sky) {
|
|
for (int i = 0; i < chunks.length; i++) {
|
|
if ((bitmask & 1 << i) != 0) {
|
|
System.arraycopy(input, position, chunks[i].getSkyLight(), 0, 2048);
|
|
position = position + 2048;
|
|
}
|
|
}
|
|
}
|
|
|
|
byte[] biomeData = null;
|
|
|
|
if (isFullChunk) {
|
|
// yay im a full chunk meaning i know my biomes, mommy get the camera!
|
|
biomeData = new byte[256];
|
|
System.arraycopy(input, position, biomeData, 0, 256);
|
|
}
|
|
return new PacketChunk(chunks, biomeData);
|
|
}
|
|
|
|
public static void writeNewChunk(ByteBuf buffer, PacketChunkData chunk) {
|
|
// Bits Per Block (We use 0, cause we're not gonna write a palette ;) )
|
|
buffer.writeByte(0);
|
|
// No Palette nor length :D
|
|
|
|
// Data Array Length
|
|
byte[] blockData = convertBlockArray(chunk.getBlocks());
|
|
writeVarInt(blockData.length / 8, buffer); // Notchian is divide by 8
|
|
|
|
buffer.writeBytes(blockData);
|
|
// Block Light
|
|
buffer.writeBytes(chunk.getBlockLight());
|
|
// If has skylight, write it
|
|
if (chunk.getSkyLight() != null) {
|
|
buffer.writeBytes(chunk.getSkyLight());
|
|
}
|
|
}
|
|
|
|
private static byte[] convertBlockArray(short[] blocks) {
|
|
// block ID for the first 9 bits, and the block damage value for the last 4 bits
|
|
byte[] output = new byte[6664]; // (16 * 16 * 16 * 13) / 8 :) (plus some for padding ...)
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
for (short block : blocks) {
|
|
int blockID = block >> 4; // (Needs to be to 9 bits)
|
|
int data = block & 0xF; // 8 bits
|
|
|
|
}
|
|
return null; // todo: finish
|
|
}
|
|
|
|
private static BitSet append(BitSet base, int index, MagicBitSet toAdd) {
|
|
int length = index;
|
|
for (int i = 0; i < toAdd.getTrueLength(); i++) {
|
|
base.set(length + i, toAdd.get(i));
|
|
}
|
|
return base;
|
|
}
|
|
|
|
private static MagicBitSet getPaddedBitSet(int value, int bitSize) {
|
|
MagicBitSet output = new MagicBitSet(bitSize);
|
|
BitSet temp = BitSet.valueOf(new long[]{value});
|
|
for (int i = 0; i < bitSize; i++) {
|
|
output.set(i, false);
|
|
}
|
|
int toShift = bitSize - temp.length();
|
|
for (int i = 0; i < temp.length(); i++) {
|
|
output.set(toShift + i, temp.get(i));
|
|
}
|
|
return output;
|
|
}
|
|
|
|
}
|