Clean up JoinGame packet encoding/decoding

Specifically, the JoinGame packet handling is now split between pre-1.16 encodings of the packet and post-1.16 handlings of the packet. This packet is one of the most amorphous packets in the entire Minecraft protocol, from Velocity's perspective.
This commit is contained in:
Andrew Steinborn 2021-08-21 02:17:34 -04:00
parent 65db0fad6a
commit e6a93ad0c6
3 changed files with 145 additions and 87 deletions

View File

@ -44,6 +44,10 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
if (varintEnd == -1) {
// We tried to go beyond the end of the buffer. This is probably a good sign that the
// buffer was too short to hold a proper varint.
if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
// Special case where the entire packet is just a run of zeroes. We ignore them all.
in.clear();
}
return;
}
@ -54,7 +58,7 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
in.clear();
throw BAD_LENGTH_CACHED;
} else if (readVarint == 0) {
// skip over the empty packet and ignore it
// skip over the empty packet(s) and ignore it
in.readerIndex(varintEnd + 1);
} else {
int minimumRead = bytesRead + readVarint;

View File

@ -27,6 +27,15 @@ class VarintByteDecoder implements ByteProcessor {
@Override
public boolean process(byte k) {
if (k == 0 && bytesRead == 0) {
// tentatively say it's invalid, but there's a possibility of redemption
result = DecodeResult.RUN_OF_ZEROES;
return true;
}
if (result == DecodeResult.RUN_OF_ZEROES) {
result = DecodeResult.SUCCESS;
return false;
}
readVarint |= (k & 0x7F) << bytesRead++ * 7;
if (bytesRead > 3) {
result = DecodeResult.TOO_BIG;
@ -54,6 +63,7 @@ class VarintByteDecoder implements ByteProcessor {
public enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
TOO_BIG,
RUN_OF_ZEROES
}
}

View File

@ -99,7 +99,7 @@ public class JoinGame implements MinecraftPacket {
return levelType;
}
public void setLevelType(String levelType) {
public void setLevelType(@Nullable String levelType) {
this.levelType = levelType;
}
@ -183,43 +183,22 @@ public class JoinGame implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.entityId = buf.readInt();
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
this.isHardcore = buf.readBoolean();
this.gamemode = buf.readByte();
} else {
this.gamemode = buf.readByte();
this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08;
}
String dimensionIdentifier = null;
String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
this.previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
ListBinaryTag dimensionRegistryContainer = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
.getList("value", BinaryTagTypes.COMPOUND);
this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome");
} else {
dimensionRegistryContainer = registryContainer.getList("dimension",
BinaryTagTypes.COMPOUND);
}
ImmutableSet<DimensionData> readData =
DimensionRegistry.fromGameData(dimensionRegistryContainer, version);
this.dimensionRegistry = new DimensionRegistry(readData, levelNames);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
.annotateWith(dimensionIdentifier, null);
} else {
dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf);
}
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.decode116Up(buf, version);
} else {
this.decodeLegacy(buf, version);
}
}
private void decodeLegacy(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
this.gamemode = buf.readByte();
this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) {
this.dimension = buf.readInt();
} else {
this.dimension = buf.readByte();
@ -230,14 +209,8 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.partialHashedSeed = buf.readLong();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
this.maxPlayers = ProtocolUtils.readVarInt(buf);
} else {
this.maxPlayers = buf.readUnsignedByte();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) {
this.levelType = ProtocolUtils.readString(buf, 16);
}
this.maxPlayers = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
this.viewDistance = ProtocolUtils.readVarInt(buf);
}
@ -247,15 +220,76 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.showRespawnScreen = buf.readBoolean();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
}
private void decode116Up(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
this.isHardcore = buf.readBoolean();
this.gamemode = buf.readByte();
} else {
this.gamemode = buf.readByte();
this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08;
}
this.previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
ListBinaryTag dimensionRegistryContainer;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
.getList("value", BinaryTagTypes.COMPOUND);
this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome");
} else {
dimensionRegistryContainer = registryContainer.getList("dimension",
BinaryTagTypes.COMPOUND);
}
ImmutableSet<DimensionData> readData =
DimensionRegistry.fromGameData(dimensionRegistryContainer, version);
this.dimensionRegistry = new DimensionRegistry(readData, levelNames);
String dimensionIdentifier;
String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
.annotateWith(dimensionIdentifier, null);
} else {
dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf);
}
this.partialHashedSeed = buf.readLong();
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
this.maxPlayers = ProtocolUtils.readVarInt(buf);
} else {
this.maxPlayers = buf.readUnsignedByte();
}
this.viewDistance = ProtocolUtils.readVarInt(buf);
this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.encode116Up(buf, version);
} else {
this.encodeLegacy(buf, version);
}
}
private void encodeLegacy(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
buf.writeBoolean(isHardcore);
@ -263,29 +297,7 @@ public class JoinGame implements MinecraftPacket {
} else {
buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
buf.writeByte(previousGamemode);
ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames().toArray(new String[0]));
CompoundBinaryTag.Builder registryContainer = CompoundBinaryTag.builder();
ListBinaryTag encodedDimensionRegistry = dimensionRegistry.encodeRegistry(version);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag.Builder dimensionRegistryEntry = CompoundBinaryTag.builder();
dimensionRegistryEntry.putString("type", "minecraft:dimension_type");
dimensionRegistryEntry.put("value", encodedDimensionRegistry);
registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build());
registryContainer.put("minecraft:worldgen/biome", biomeRegistry);
} else {
registryContainer.put("dimension", encodedDimensionRegistry);
}
ProtocolUtils.writeCompoundTag(buf, registryContainer.build());
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
}
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) {
buf.writeInt(dimension);
} else {
buf.writeByte(dimension);
@ -296,17 +308,11 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeLong(partialHashedSeed);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
ProtocolUtils.writeVarInt(buf, maxPlayers);
} else {
buf.writeByte(maxPlayers);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) {
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
}
ProtocolUtils.writeString(buf, levelType);
buf.writeByte(maxPlayers);
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
}
ProtocolUtils.writeString(buf, levelType);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
ProtocolUtils.writeVarInt(buf, viewDistance);
}
@ -316,10 +322,48 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeBoolean(showRespawnScreen);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
}
private void encode116Up(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
buf.writeBoolean(isHardcore);
buf.writeByte(gamemode);
} else {
buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
}
buf.writeByte(previousGamemode);
ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames().toArray(new String[0]));
CompoundBinaryTag.Builder registryContainer = CompoundBinaryTag.builder();
ListBinaryTag encodedDimensionRegistry = dimensionRegistry.encodeRegistry(version);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag.Builder dimensionRegistryEntry = CompoundBinaryTag.builder();
dimensionRegistryEntry.putString("type", "minecraft:dimension_type");
dimensionRegistryEntry.put("value", encodedDimensionRegistry);
registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build());
registryContainer.put("minecraft:worldgen/biome", biomeRegistry);
} else {
registryContainer.put("dimension", encodedDimensionRegistry);
}
ProtocolUtils.writeCompoundTag(buf, registryContainer.build());
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
}
buf.writeLong(partialHashedSeed);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
ProtocolUtils.writeVarInt(buf, maxPlayers);
} else {
buf.writeByte(maxPlayers);
}
ProtocolUtils.writeVarInt(buf, viewDistance);
buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
}
@Override