/* * This file is part of ViaLegacy - https://github.com/RaphiMC/ViaLegacy * Copyright (C) 2020-2024 RK_01/RaphiMC and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10; import com.google.common.base.Joiner; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.ProtocolInfo; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.Environment; import com.viaversion.viaversion.api.minecraft.Position; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_10; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.metadata.Metadata; import com.viaversion.viaversion.api.platform.providers.ViaProviders; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.types.chunk.BulkChunkType1_8; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_8; import com.viaversion.viaversion.api.type.types.version.Types1_8; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.mcstructs.text.serializer.TextComponentSerializer; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.protocol1_8.ClientboundPackets1_8; import com.viaversion.viaversion.protocols.protocol1_8.ServerboundPackets1_8; import com.viaversion.viaversion.util.IdAndData; import net.raphimc.vialegacy.ViaLegacy; import net.raphimc.vialegacy.api.data.ItemList1_6; import net.raphimc.vialegacy.api.remapper.LegacyItemRewriter; import net.raphimc.vialegacy.api.util.PacketUtil; import net.raphimc.vialegacy.protocols.release.protocol1_7_6_10to1_7_2_5.ClientboundPackets1_7_2; import net.raphimc.vialegacy.protocols.release.protocol1_7_6_10to1_7_2_5.ServerboundPackets1_7_2; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.data.Particle; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.metadata.MetadataRewriter; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.model.GameProfile; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.model.MapData; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.model.MapIcon; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.model.TabListEntry; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.providers.GameProfileFetcher; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.rewriter.ChatComponentRewriter; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.rewriter.ItemRewriter; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.storage.*; import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.types.Types1_7_6; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.logging.Level; public class Protocol1_8to1_7_6_10 extends AbstractProtocol { private final LegacyItemRewriter itemRewriter = new ItemRewriter(this); private final ChatComponentRewriter chatComponentRewriter = new ChatComponentRewriter(this); private final MetadataRewriter metadataRewriter = new MetadataRewriter(this); public Protocol1_8to1_7_6_10() { super(ClientboundPackets1_7_2.class, ClientboundPackets1_8.class, ServerboundPackets1_7_2.class, ServerboundPackets1_8.class); } public static final ValueTransformer LEGACY_TO_JSON = new ValueTransformer(Type.STRING, Type.STRING) { @Override public String transform(PacketWrapper packetWrapper, String message) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("text", message); return jsonObject.toString(); } }; public static final ValueTransformer LEGACY_TO_JSON_TRANSLATE = new ValueTransformer(Type.STRING, Type.STRING) { @Override public String transform(PacketWrapper packetWrapper, String message) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("translate", message); return jsonObject.toString(); } }; @Override protected void registerPackets() { this.itemRewriter.register(); this.registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO.getId(), ClientboundLoginPackets.HELLO.getId(), new PacketHandlers() { @Override public void register() { map(Type.STRING); // server hash map(Type.SHORT_BYTE_ARRAY, Type.BYTE_ARRAY_PRIMITIVE); // public key map(Type.SHORT_BYTE_ARRAY, Type.BYTE_ARRAY_PRIMITIVE); // verify token } }); this.registerClientbound(State.LOGIN, ClientboundLoginPackets.GAME_PROFILE.getId(), ClientboundLoginPackets.GAME_PROFILE.getId(), new PacketHandlers() { @Override public void register() { read(Type.STRING); // uuid read(Type.STRING); // name handler(wrapper -> { // 1.7.10 ignores the data from the server final ProtocolInfo protocolInfo = wrapper.user().getProtocolInfo(); wrapper.write(Type.STRING, protocolInfo.getUuid().toString()); // uuid wrapper.write(Type.STRING, protocolInfo.getUsername()); // name }); } }); this.registerClientbound(ClientboundPackets1_7_2.KEEP_ALIVE, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // key } }); this.registerClientbound(ClientboundPackets1_7_2.JOIN_GAME, new PacketHandlers() { @Override public void register() { map(Type.INT); // entity id map(Type.UNSIGNED_BYTE); // gamemode map(Type.BYTE); // dimension id map(Type.UNSIGNED_BYTE); // difficulty map(Type.UNSIGNED_BYTE); // max players map(Type.STRING); // level type create(Type.BOOLEAN, false); // reduced debug info handler(wrapper -> { final ProtocolInfo protocolInfo = wrapper.user().getProtocolInfo(); final TablistStorage tablistStorage = wrapper.user().get(TablistStorage.class); tablistStorage.sendTempEntry(new TabListEntry(protocolInfo.getUsername(), protocolInfo.getUuid())); // load own skin final int entityId = wrapper.get(Type.INT, 0); final byte dimensionId = wrapper.get(Type.BYTE, 0); final EntityTracker tracker = wrapper.user().get(EntityTracker.class); tracker.trackEntity(entityId, EntityTypes1_10.EntityType.PLAYER); tracker.setPlayerID(entityId); wrapper.user().get(DimensionTracker.class).changeDimension(dimensionId); }); } }); this.registerClientbound(ClientboundPackets1_7_2.CHAT_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> wrapper.write(Type.STRING, chatComponentRewriter.toClient(wrapper.user(), wrapper.read(Type.STRING)))); // message create(Type.BYTE, (byte) 0); // position } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_EQUIPMENT, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.SHORT); // slot map(Types1_7_6.ITEM, Type.ITEM1_8); // item handler(wrapper -> itemRewriter.handleItemToClient(wrapper.user(), wrapper.get(Type.ITEM1_8, 0))); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_POSITION, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_INT, Type.POSITION1_8); // position } }); this.registerClientbound(ClientboundPackets1_7_2.UPDATE_HEALTH, new PacketHandlers() { @Override public void register() { map(Type.FLOAT); // health map(Type.SHORT, Type.VAR_INT); // food map(Type.FLOAT); // saturation } }); this.registerClientbound(ClientboundPackets1_7_2.RESPAWN, new PacketHandlers() { @Override public void register() { map(Type.INT); // dimension id map(Type.UNSIGNED_BYTE); // difficulty map(Type.UNSIGNED_BYTE); // gamemode map(Type.STRING); // worldType handler(wrapper -> { if (wrapper.user().get(DimensionTracker.class).changeDimension(wrapper.get(Type.INT, 0))) { wrapper.user().get(ChunkTracker.class).clear(); wrapper.user().get(EntityTracker.class).clear(); } final ProtocolInfo protocolInfo = wrapper.user().getProtocolInfo(); final TablistStorage tablistStorage = wrapper.user().get(TablistStorage.class); tablistStorage.sendTempEntry(new TabListEntry(protocolInfo.getUsername(), protocolInfo.getUuid())); // load own skin }); } }); this.registerClientbound(ClientboundPackets1_7_2.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // x map(Type.DOUBLE, Type.DOUBLE, stance -> stance - 1.62F); // y map(Type.DOUBLE); // z map(Type.FLOAT); // yaw map(Type.FLOAT); // pitch read(Type.BOOLEAN); // onGround create(Type.BYTE, (byte) 0); // flags } }); this.registerClientbound(ClientboundPackets1_7_2.USE_BED, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Types1_7_6.POSITION_BYTE, Type.POSITION1_8); // position } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_PLAYER, wrapper -> { final int entityID = wrapper.passthrough(Type.VAR_INT); // entity id final UUID uuid = UUID.fromString(wrapper.read(Type.STRING)); // uuid wrapper.write(Type.UUID, uuid); final String name = wrapper.read(Type.STRING); // name final TablistStorage tablistStorage = wrapper.user().get(TablistStorage.class); final TabListEntry tempTabEntry = new TabListEntry(name, uuid); final int dataCount = wrapper.read(Type.VAR_INT); // properties count for (int i = 0; i < dataCount; i++) { final String key = wrapper.read(Type.STRING); // name final String value = wrapper.read(Type.STRING); // value final String signature = wrapper.read(Type.STRING); // signature tempTabEntry.gameProfile.addProperty(new GameProfile.Property(key, value, signature)); } wrapper.passthrough(Type.INT); // x wrapper.passthrough(Type.INT); // y wrapper.passthrough(Type.INT); // z wrapper.passthrough(Type.BYTE); // yaw wrapper.passthrough(Type.BYTE); // pitch final short itemId = wrapper.read(Type.SHORT); // item in hand final Item currentItem = new DataItem(itemId, (byte) 1, (short) 0, null); itemRewriter.handleItemToClient(wrapper.user(), currentItem); wrapper.write(Type.SHORT, (short) currentItem.identifier()); final List metadata = wrapper.read(Types1_7_6.METADATA_LIST); // metadata metadataRewriter.transform(wrapper.user(), EntityTypes1_10.EntityType.PLAYER, metadata); wrapper.write(Types1_8.METADATA_LIST, metadata); tablistStorage.sendTempEntry(tempTabEntry); wrapper.user().get(EntityTracker.class).trackEntity(entityID, EntityTypes1_10.EntityType.PLAYER); }); this.registerClientbound(ClientboundPackets1_7_2.COLLECT_ITEM, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // collected entity id map(Type.INT, Type.VAR_INT); // collector entity id handler(wrapper -> wrapper.user().get(EntityTracker.class).removeEntity(wrapper.get(Type.VAR_INT, 0))); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_ENTITY, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // entity id map(Type.BYTE); // type id map(Type.INT); // x map(Type.INT); // y map(Type.INT); // z map(Type.BYTE); // pitch map(Type.BYTE); // yaw map(Type.INT); // data handler(wrapper -> { final EntityTracker tracker = wrapper.user().get(EntityTracker.class); final int entityID = wrapper.get(Type.VAR_INT, 0); final int typeID = wrapper.get(Type.BYTE, 0); int x = wrapper.get(Type.INT, 0); int y = wrapper.get(Type.INT, 1); int z = wrapper.get(Type.INT, 2); byte yaw = wrapper.get(Type.BYTE, 2); int data = wrapper.get(Type.INT, 3); final EntityTypes1_10.EntityType type = EntityTypes1_10.getTypeFromId(typeID, true); tracker.trackEntity(entityID, type); tracker.updateEntityLocation(entityID, x, y, z, false); if (type == EntityTypes1_10.ObjectType.ITEM_FRAME.getType()) { switch (data) { case 0: z += 32; yaw = 0; break; case 1: x -= 32; yaw = 64; break; case 2: z -= 32; yaw = -128; break; case 3: x += 32; yaw = -64; break; } } else if (type == EntityTypes1_10.ObjectType.FALLING_BLOCK.getType()) { final int id = data & 0xffff; final int metadata = data >> 16; final IdAndData block = new IdAndData(id, metadata); wrapper.user().get(ChunkTracker.class).remapBlockParticle(block); data = block.getId() | block.getData() << 12; } y = realignEntityY(type, y); wrapper.set(Type.INT, 0, x); wrapper.set(Type.INT, 1, y); wrapper.set(Type.INT, 2, z); wrapper.set(Type.BYTE, 2, yaw); wrapper.set(Type.INT, 3, data); }); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_MOB, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // entity id map(Type.UNSIGNED_BYTE); // type id map(Type.INT); // x map(Type.INT); // y map(Type.INT); // z map(Type.BYTE); // yaw map(Type.BYTE); // pitch map(Type.BYTE); // head yaw map(Type.SHORT); // velocity x map(Type.SHORT); // velocity y map(Type.SHORT); // velocity z map(Types1_7_6.METADATA_LIST, Types1_8.METADATA_LIST); // metadata handler(wrapper -> { final EntityTracker tracker = wrapper.user().get(EntityTracker.class); final int entityID = wrapper.get(Type.VAR_INT, 0); final int typeID = wrapper.get(Type.UNSIGNED_BYTE, 0); final int x = wrapper.get(Type.INT, 0); final int y = wrapper.get(Type.INT, 1); final int z = wrapper.get(Type.INT, 2); final List metadataList = wrapper.get(Types1_8.METADATA_LIST, 0); final EntityTypes1_10.EntityType entityType = EntityTypes1_10.getTypeFromId(typeID, false); tracker.trackEntity(entityID, entityType); tracker.updateEntityLocation(entityID, x, y, z, false); tracker.updateEntityMetadata(entityID, metadataList); metadataRewriter.transform(wrapper.user(), entityType, metadataList); }); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_PAINTING, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // entity id map(Type.STRING); // motive map(Types1_7_6.POSITION_INT, Type.POSITION1_8); // position map(Type.INT, Type.BYTE); // rotation handler(wrapper -> { final short rotation = wrapper.get(Type.BYTE, 0); final Position pos = wrapper.get(Type.POSITION1_8, 0); int modX = 0; int modZ = 0; switch (rotation) { case 0: modZ = 1; break; case 1: modX = -1; break; case 2: modZ = -1; break; case 3: modX = 1; break; } wrapper.set(Type.POSITION1_8, 0, new Position(pos.x() + modX, pos.y(), pos.z() + modZ)); final int entityID = wrapper.get(Type.VAR_INT, 0); wrapper.user().get(EntityTracker.class).trackEntity(entityID, EntityTypes1_10.EntityType.PAINTING); }); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_EXPERIENCE_ORB, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // entity id map(Type.INT); // x map(Type.INT); // y map(Type.INT); // z map(Type.SHORT); // count handler(wrapper -> { final int entityID = wrapper.get(Type.VAR_INT, 0); wrapper.user().get(EntityTracker.class).trackEntity(entityID, EntityTypes1_10.EntityType.EXPERIENCE_ORB); wrapper.set(Type.INT, 1, realignEntityY(EntityTypes1_10.EntityType.EXPERIENCE_ORB, wrapper.get(Type.INT, 1))); }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_VELOCITY, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.SHORT); // velocity x map(Type.SHORT); // velocity y map(Type.SHORT); // velocity z } }); this.registerClientbound(ClientboundPackets1_7_2.DESTROY_ENTITIES, new PacketHandlers() { @Override public void register() { map(Types1_7_6.INT_ARRAY, Type.VAR_INT_ARRAY_PRIMITIVE); // entity ids handler(wrapper -> { final EntityTracker tracker = wrapper.user().get(EntityTracker.class); for (int entityId : wrapper.get(Type.VAR_INT_ARRAY_PRIMITIVE, 0)) { tracker.removeEntity(entityId); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_MOVEMENT, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_POSITION, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // x map(Type.BYTE); // y map(Type.BYTE); // z handler(wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); final int entityId = wrapper.get(Type.VAR_INT, 0); final byte x = wrapper.get(Type.BYTE, 0); final byte y = wrapper.get(Type.BYTE, 1); final byte z = wrapper.get(Type.BYTE, 2); entityTracker.updateEntityLocation(entityId, x, y, z, true); if (ViaLegacy.getConfig().isDynamicOnground()) { final boolean onGround = wrapper.get(Type.BYTE, 1) > -8/*0.25D*/; entityTracker.getGroundMap().put(wrapper.get(Type.VAR_INT, 0), onGround); wrapper.write(Type.BOOLEAN, onGround); // onGround } else { wrapper.write(Type.BOOLEAN, true); // onGround } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_ROTATION, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // yaw map(Type.BYTE); // pitch handler(wrapper -> { if (ViaLegacy.getConfig().isDynamicOnground()) { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); wrapper.write(Type.BOOLEAN, entityTracker.getGroundMap().getOrDefault(wrapper.get(Type.VAR_INT, 0), true)); // onGround } else { wrapper.write(Type.BOOLEAN, true); // onGround } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_POSITION_AND_ROTATION, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // x map(Type.BYTE); // y map(Type.BYTE); // z map(Type.BYTE); // yaw map(Type.BYTE); // pitch handler(wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); final int entityId = wrapper.get(Type.VAR_INT, 0); final byte x = wrapper.get(Type.BYTE, 0); final byte y = wrapper.get(Type.BYTE, 1); final byte z = wrapper.get(Type.BYTE, 2); entityTracker.updateEntityLocation(entityId, x, y, z, true); if (ViaLegacy.getConfig().isDynamicOnground()) { final boolean onGround = wrapper.get(Type.BYTE, 1) > -8/*0.25D*/; entityTracker.getGroundMap().put(wrapper.get(Type.VAR_INT, 0), onGround); wrapper.write(Type.BOOLEAN, onGround); // onGround } else { wrapper.write(Type.BOOLEAN, true); // onGround } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_TELEPORT, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.INT); // x map(Type.INT); // y map(Type.INT); // z map(Type.BYTE); // yaw map(Type.BYTE); // pitch create(Type.BOOLEAN, true); // onGround handler(wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); final int entityId = wrapper.get(Type.VAR_INT, 0); final int x = wrapper.get(Type.INT, 0); final int y = wrapper.get(Type.INT, 1); final int z = wrapper.get(Type.INT, 2); entityTracker.updateEntityLocation(entityId, x, y, z, false); final EntityTypes1_10.EntityType type = entityTracker.getTrackedEntities().get(entityId); wrapper.set(Type.INT, 1, realignEntityY(type, y)); }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_HEAD_LOOK, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // head yaw } }); this.registerClientbound(ClientboundPackets1_7_2.ATTACH_ENTITY, new PacketHandlers() { @Override public void register() { map(Type.INT); // riding entity id map(Type.INT); // vehicle entity id map(Type.UNSIGNED_BYTE); // leash state handler(wrapper -> { final short leashState = wrapper.get(Type.UNSIGNED_BYTE, 0); if (leashState == 0) { final EntityTracker tracker = wrapper.user().get(EntityTracker.class); final int ridingId = wrapper.get(Type.INT, 0); final int vehicleId = wrapper.get(Type.INT, 1); tracker.updateEntityAttachState(ridingId, vehicleId); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_METADATA, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Types1_7_6.METADATA_LIST, Types1_8.METADATA_LIST); // metadata handler(wrapper -> { final EntityTracker tracker = wrapper.user().get(EntityTracker.class); final List metadataList = wrapper.get(Types1_8.METADATA_LIST, 0); final int entityID = wrapper.get(Type.VAR_INT, 0); if (tracker.getTrackedEntities().containsKey(entityID)) { tracker.updateEntityMetadata(entityID, metadataList); metadataRewriter.transform(wrapper.user(), tracker.getTrackedEntities().get(entityID), metadataList); if (metadataList.isEmpty()) wrapper.cancel(); } else { wrapper.cancel(); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_EFFECT, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // effect id map(Type.BYTE); // amplifier map(Type.SHORT, Type.VAR_INT); // duration create(Type.BOOLEAN, false); // hide particles } }); this.registerClientbound(ClientboundPackets1_7_2.REMOVE_ENTITY_EFFECT, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id map(Type.BYTE); // effect id } }); this.registerClientbound(ClientboundPackets1_7_2.SET_EXPERIENCE, new PacketHandlers() { @Override public void register() { map(Type.FLOAT); // experience bar map(Type.SHORT, Type.VAR_INT); // level map(Type.SHORT, Type.VAR_INT); // total experience } }); this.registerClientbound(ClientboundPackets1_7_2.ENTITY_PROPERTIES, new PacketHandlers() { @Override public void register() { map(Type.INT, Type.VAR_INT); // entity id handler(wrapper -> { final int amount = wrapper.passthrough(Type.INT); // count for (int i = 0; i < amount; i++) { wrapper.passthrough(Type.STRING); // id wrapper.passthrough(Type.DOUBLE); // baseValue final int modifierlength = wrapper.read(Type.SHORT); // modifier count wrapper.write(Type.VAR_INT, modifierlength); for (int j = 0; j < modifierlength; j++) { wrapper.passthrough(Type.UUID); // modifier uuid wrapper.passthrough(Type.DOUBLE); // modifier amount wrapper.passthrough(Type.BYTE); // modifier operation } } }); } }); this.registerClientbound(ClientboundPackets1_7_2.CHUNK_DATA, wrapper -> { final Environment dimension = wrapper.user().get(DimensionTracker.class).getDimension(); final Chunk chunk = wrapper.read(Types1_7_6.getChunk(dimension)); wrapper.user().get(ChunkTracker.class).trackAndRemap(chunk); wrapper.write(ChunkType1_8.forEnvironment(dimension), chunk); }); this.registerClientbound(ClientboundPackets1_7_2.MULTI_BLOCK_CHANGE, new PacketHandlers() { @Override public void register() { map(Type.INT); // chunkX map(Type.INT); // chunkZ map(Types1_7_6.BLOCK_CHANGE_RECORD_ARRAY, Type.BLOCK_CHANGE_RECORD_ARRAY); // blockChangeRecords handler(wrapper -> { final int chunkX = wrapper.get(Type.INT, 0); final int chunkZ = wrapper.get(Type.INT, 1); final BlockChangeRecord[] blockChangeRecords = wrapper.get(Type.BLOCK_CHANGE_RECORD_ARRAY, 0); for (BlockChangeRecord record : blockChangeRecords) { final int targetX = record.getSectionX() + (chunkX << 4); final int targetY = record.getY(-1); final int targetZ = record.getSectionZ() + (chunkZ << 4); final IdAndData block = IdAndData.fromRawData(record.getBlockId()); final Position pos = new Position(targetX, targetY, targetZ); wrapper.user().get(ChunkTracker.class).trackAndRemap(pos, block); record.setBlockId(block.toRawData()); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.BLOCK_CHANGE, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_UBYTE, Type.POSITION1_8); // position handler(wrapper -> { final int blockId = wrapper.read(Type.VAR_INT); // block id final int data = wrapper.read(Type.UNSIGNED_BYTE); // block data final Position pos = wrapper.get(Type.POSITION1_8, 0); // position final IdAndData block = new IdAndData(blockId, data); wrapper.user().get(ChunkTracker.class).trackAndRemap(pos, block); wrapper.write(Type.VAR_INT, block.toRawData()); }); } }); this.registerClientbound(ClientboundPackets1_7_2.BLOCK_ACTION, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_SHORT, Type.POSITION1_8); // position map(Type.UNSIGNED_BYTE); // type map(Type.UNSIGNED_BYTE); // data map(Type.VAR_INT); // block id } }); this.registerClientbound(ClientboundPackets1_7_2.BLOCK_BREAK_ANIMATION, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // entity id map(Types1_7_6.POSITION_INT, Type.POSITION1_8); // position map(Type.BYTE); // progress } }); this.registerClientbound(ClientboundPackets1_7_2.MAP_BULK_CHUNK, wrapper -> { final Chunk[] chunks = wrapper.read(Types1_7_6.CHUNK_BULK); for (Chunk chunk : chunks) { wrapper.user().get(ChunkTracker.class).trackAndRemap(chunk); } wrapper.write(BulkChunkType1_8.TYPE, chunks); }); this.registerClientbound(ClientboundPackets1_7_2.EXPLOSION, new PacketHandlers() { @Override public void register() { map(Type.FLOAT); // x map(Type.FLOAT); // y map(Type.FLOAT); // z map(Type.FLOAT); // radius map(Type.INT); // record count handler(wrapper -> { final int x = wrapper.get(Type.FLOAT, 0).intValue(); final int y = wrapper.get(Type.FLOAT, 1).intValue(); final int z = wrapper.get(Type.FLOAT, 2).intValue(); final int recordCount = wrapper.get(Type.INT, 0); final ChunkTracker chunkTracker = wrapper.user().get(ChunkTracker.class); for (int i = 0; i < recordCount; i++) { final Position pos = new Position(x + wrapper.passthrough(Type.BYTE), y + wrapper.passthrough(Type.BYTE), z + wrapper.passthrough(Type.BYTE)); chunkTracker.trackAndRemap(pos, new IdAndData(0, 0)); } }); map(Type.FLOAT); // velocity x map(Type.FLOAT); // velocity y map(Type.FLOAT); // velocity z } }); this.registerClientbound(ClientboundPackets1_7_2.EFFECT, wrapper -> { int effectId = wrapper.read(Type.INT); // effect id final Position pos = wrapper.read(Types1_7_6.POSITION_UBYTE); // position int data = wrapper.read(Type.INT); // data final boolean disableRelativeVolume = wrapper.read(Type.BOOLEAN); // server wide if (!disableRelativeVolume && effectId == 2006) { // block dust effect wrapper.setPacketType(ClientboundPackets1_8.SPAWN_PARTICLE); final Random rnd = new Random(); final ChunkTracker chunkTracker = wrapper.user().get(ChunkTracker.class); final IdAndData block = chunkTracker.getBlockNotNull(pos); if (block.getId() != 0) { double var21 = Math.min(0.2F + (float) data / 15.0F, 10.0F); if (var21 > 2.5D) var21 = 2.5D; final float var25 = randomFloatClamp(rnd, 0.0F, ((float) Math.PI * 2F)); final double var26 = randomFloatClamp(rnd, 0.75F, 1.0F); final float offsetY = (float) (0.20000000298023224D + var21 / 100.0D); final float offsetX = (float) (Math.cos(var25) * 0.2F * var26 * var26 * (var21 + 0.2D)); final float offsetZ = (float) (Math.sin(var25) * 0.2F * var26 * var26 * (var21 + 0.2D)); final int amount = (int) (150.0D * var21); wrapper.write(Type.INT, Particle.BLOCK_DUST.ordinal()); wrapper.write(Type.BOOLEAN, false); // longDistance wrapper.write(Type.FLOAT, pos.x() + 0.5F); wrapper.write(Type.FLOAT, pos.y() + 1.0F); wrapper.write(Type.FLOAT, pos.z() + 0.5F); wrapper.write(Type.FLOAT, offsetX); wrapper.write(Type.FLOAT, offsetY); wrapper.write(Type.FLOAT, offsetZ); wrapper.write(Type.FLOAT, 0.15000000596046448F); // particleSpeed wrapper.write(Type.INT, amount); wrapper.write(Type.VAR_INT, block.getId() | (block.getData() << 12)); } else { wrapper.cancel(); } } else { if (!disableRelativeVolume && effectId == 1003) { // door_open if (Math.random() > 0.5) { effectId = 1006; // door_close } } else if (!disableRelativeVolume && effectId == 2001) { // block break effect final ChunkTracker chunkTracker = wrapper.user().get(ChunkTracker.class); final int blockID = data & 4095; final int blockData = data >> 12 & 255; final IdAndData block = new IdAndData(blockID, blockData); chunkTracker.remapBlockParticle(block); data = block.getId() | (block.getData() << 12); } wrapper.write(Type.INT, effectId); wrapper.write(Type.POSITION1_8, pos); wrapper.write(Type.INT, data); wrapper.write(Type.BOOLEAN, disableRelativeVolume); } }); this.registerClientbound(ClientboundPackets1_7_2.SPAWN_PARTICLE, wrapper -> { final String[] parts = wrapper.read(Type.STRING).split("_", 3); Particle particle = Particle.find(parts[0]); if (particle == null) { particle = Particle.BARRIER; if (!Via.getConfig().isSuppressConversionWarnings() || Via.getManager().isDebug()) { ViaLegacy.getPlatform().getLogger().warning("Could not find 1.8 particle for " + Arrays.toString(parts)); } } wrapper.write(Type.INT, particle.ordinal()); // particle id wrapper.write(Type.BOOLEAN, false); // long distance wrapper.passthrough(Type.FLOAT); // x wrapper.passthrough(Type.FLOAT); // y wrapper.passthrough(Type.FLOAT); // z wrapper.passthrough(Type.FLOAT); // offset x wrapper.passthrough(Type.FLOAT); // offset y wrapper.passthrough(Type.FLOAT); // offset z wrapper.passthrough(Type.FLOAT); // speed wrapper.passthrough(Type.INT); // amount if (particle == Particle.ICON_CRACK) { final int id = Integer.parseInt(parts[1]); int damage = 0; if (parts.length > 2) damage = Integer.parseInt(parts[2]); final DataItem item = new DataItem(id, (byte) 1, (short) damage, null); itemRewriter.handleItemToClient(wrapper.user(), item); wrapper.write(Type.VAR_INT, item.identifier()); // particle data if (item.data() != 0) wrapper.write(Type.VAR_INT, (int) item.data()); // particle data } else if (particle == Particle.BLOCK_CRACK || particle == Particle.BLOCK_DUST) { final int id = Integer.parseInt(parts[1]); final int metadata = Integer.parseInt(parts[2]); final IdAndData block = new IdAndData(id, metadata); wrapper.user().get(ChunkTracker.class).remapBlockParticle(block); wrapper.write(Type.VAR_INT, block.getId() | block.getData() << 12); // particle data } else if (particle.extra > 0) throw new IllegalStateException("Tried to write particle which requires extra data, but no handler was found"); }); this.registerClientbound(ClientboundPackets1_7_2.GAME_EVENT, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // reason map(Type.FLOAT); // value handler(wrapper -> { if (wrapper.get(Type.UNSIGNED_BYTE, 0) == 3) { final PacketWrapper chatMessage = PacketWrapper.create(ClientboundPackets1_8.CHAT_MESSAGE, wrapper.user()); chatMessage.write(Type.STRING, LEGACY_TO_JSON.transform(chatMessage, "Your game mode has been updated")); // message chatMessage.write(Type.BYTE, (byte) 0); // position chatMessage.send(Protocol1_8to1_7_6_10.class); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.OPEN_WINDOW, wrapper -> { final short windowId = wrapper.passthrough(Type.UNSIGNED_BYTE); // window id final short windowType = wrapper.read(Type.UNSIGNED_BYTE); // window type String title = wrapper.read(Type.STRING); // title short slots = wrapper.read(Type.UNSIGNED_BYTE); // slots boolean useProvidedWindowTitle = wrapper.read(Type.BOOLEAN); // use provided title wrapper.user().get(WindowTracker.class).types.put(windowId, windowType); final String inventoryName; switch (windowType) { case 0: inventoryName = "minecraft:chest"; break; case 1: inventoryName = "minecraft:crafting_table"; break; case 2: inventoryName = "minecraft:furnace"; break; case 3: inventoryName = "minecraft:dispenser"; break; case 4: inventoryName = "minecraft:enchanting_table"; break; case 5: inventoryName = "minecraft:brewing_stand"; break; case 6: inventoryName = "minecraft:villager"; if (!useProvidedWindowTitle || title.isEmpty()) { title = "entity.Villager.name"; useProvidedWindowTitle = false; } break; case 7: inventoryName = "minecraft:beacon"; break; case 8: inventoryName = "minecraft:anvil"; break; case 9: inventoryName = "minecraft:hopper"; break; case 10: inventoryName = "minecraft:dropper"; break; case 11: inventoryName = "EntityHorse"; break; default: throw new IllegalArgumentException("Unknown window type: " + windowType); } if (windowType == 1/*crafting_table*/ || windowType == 4/*enchanting_table*/ || windowType == 8/*anvil*/) { slots = 0; } if (useProvidedWindowTitle) { title = LEGACY_TO_JSON.transform(wrapper, title); } else { title = LEGACY_TO_JSON_TRANSLATE.transform(wrapper, title); } wrapper.write(Type.STRING, inventoryName); wrapper.write(Type.STRING, title); wrapper.write(Type.UNSIGNED_BYTE, slots); if (windowType == 11) wrapper.passthrough(Type.INT); // entity id }); this.registerClientbound(ClientboundPackets1_7_2.SET_SLOT, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final short windowId = wrapper.read(Type.BYTE); // window id wrapper.write(Type.UNSIGNED_BYTE, windowId); // actually wrong, should by BYTE but Via uses U_BYTE short slot = wrapper.read(Type.SHORT); // slot final short windowType = wrapper.user().get(WindowTracker.class).get(windowId); if (windowType == 4/*enchanting_table*/ && slot >= 1) slot += 1; wrapper.write(Type.SHORT, slot); }); map(Types1_7_6.ITEM, Type.ITEM1_8); // item handler(wrapper -> itemRewriter.handleItemToClient(wrapper.user(), wrapper.get(Type.ITEM1_8, 0))); } }); this.registerClientbound(ClientboundPackets1_7_2.WINDOW_ITEMS, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final short windowId = wrapper.passthrough(Type.UNSIGNED_BYTE); // window id final short windowType = wrapper.user().get(WindowTracker.class).get(windowId); Item[] items = wrapper.read(Types1_7_6.ITEM_ARRAY); // items if (windowType == 4/*enchanting_table*/) { final Item[] old = items; items = new Item[old.length + 1]; items[0] = old[0]; System.arraycopy(old, 1, items, 2, old.length - 1); items[1] = new DataItem(351/*lapis_lazuli*/, (byte) 3, (short) 4, null); } for (Item item : items) { itemRewriter.handleItemToClient(wrapper.user(), item); } wrapper.write(Type.ITEM1_8_SHORT_ARRAY, items); }); } }); this.registerClientbound(ClientboundPackets1_7_2.WINDOW_PROPERTY, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // window id map(Type.SHORT); // progress bar id map(Type.SHORT); // progress bar value handler(wrapper -> { final short windowId = wrapper.get(Type.UNSIGNED_BYTE, 0); short progressBar = wrapper.get(Type.SHORT, 0); final short windowType = wrapper.user().get(WindowTracker.class).get(windowId); if (windowType == 2) { // furnace switch (progressBar) { case 0: { // cookTime progressBar = 2; final PacketWrapper windowProperty = PacketWrapper.create(ClientboundPackets1_8.WINDOW_PROPERTY, wrapper.user()); windowProperty.write(Type.UNSIGNED_BYTE, windowId); windowProperty.write(Type.SHORT, (short) 3); windowProperty.write(Type.SHORT, (short) 200); windowProperty.send(Protocol1_8to1_7_6_10.class); break; } case 1: { // furnaceBurnTime progressBar = 0; break; } case 2: { // currentItemBurnTime progressBar = 1; break; } } wrapper.set(Type.SHORT, 0, progressBar); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.UPDATE_SIGN, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_SHORT, Type.POSITION1_8); // position map(LEGACY_TO_JSON); // line 1 map(LEGACY_TO_JSON); // line 2 map(LEGACY_TO_JSON); // line 3 map(LEGACY_TO_JSON); // line 4 } }); this.registerClientbound(ClientboundPackets1_7_2.MAP_DATA, wrapper -> { final int id = wrapper.passthrough(Type.VAR_INT); // map id final byte[] data = wrapper.read(Type.SHORT_BYTE_ARRAY); // data final MapStorage mapStorage = wrapper.user().get(MapStorage.class); MapData mapData = mapStorage.getMapData(id); if (mapData == null) mapStorage.putMapData(id, mapData = new MapData()); if (data[0] == 1) { final int count = (data.length - 1) / 3; mapData.mapIcons = new MapIcon[count]; for (int i = 0; i < count; i++) { mapData.mapIcons[i] = new MapIcon((byte) (data[i * 3 + 1] >> 4), (byte) (data[i * 3 + 1] & 0xF), data[i * 3 + 2], data[i * 3 + 3]); } } else if (data[0] == 2) { mapData.scale = data[1]; } wrapper.write(Type.BYTE, mapData.scale); wrapper.write(Type.VAR_INT, mapData.mapIcons.length); for (MapIcon mapIcon : mapData.mapIcons) { wrapper.write(Type.BYTE, (byte) (mapIcon.direction << 4 | mapIcon.type & 0xF)); wrapper.write(Type.BYTE, mapIcon.x); wrapper.write(Type.BYTE, mapIcon.z); } if (data[0] == 0) { final byte x = data[1]; final byte z = data[2]; final int rows = data.length - 3; final byte[] newData = new byte[rows]; System.arraycopy(data, 3, newData, 0, rows); wrapper.write(Type.BYTE, (byte) 1); wrapper.write(Type.BYTE, (byte) rows); wrapper.write(Type.BYTE, x); wrapper.write(Type.BYTE, z); wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, newData); } else { wrapper.write(Type.BYTE, (byte) 0); } }); this.registerClientbound(ClientboundPackets1_7_2.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_SHORT, Type.POSITION1_8); // position map(Type.UNSIGNED_BYTE); // type map(Types1_7_6.NBT, Type.NAMED_COMPOUND_TAG); // data } }); this.registerClientbound(ClientboundPackets1_7_2.OPEN_SIGN_EDITOR, new PacketHandlers() { @Override public void register() { map(Types1_7_6.POSITION_INT, Type.POSITION1_8); // position } }); this.registerClientbound(ClientboundPackets1_7_2.PLAYER_INFO, wrapper -> { final String name = wrapper.read(Type.STRING); // name final boolean online = wrapper.read(Type.BOOLEAN); // online final short ping = wrapper.read(Type.SHORT); // ping final TablistStorage tablistStorage = wrapper.user().get(TablistStorage.class); TabListEntry entry = tablistStorage.tablist.get(name); if (entry == null && online) { // add entry tablistStorage.tablist.put(name, entry = new TabListEntry(name, ping)); wrapper.write(Type.VAR_INT, 0); // action wrapper.write(Type.VAR_INT, 1); // count wrapper.write(Type.UUID, entry.gameProfile.uuid); // uuid wrapper.write(Type.STRING, entry.gameProfile.userName); // name wrapper.write(Type.VAR_INT, 0); // properties count wrapper.write(Type.VAR_INT, 0); // gamemode wrapper.write(Type.VAR_INT, entry.ping); // ping wrapper.write(Type.OPTIONAL_STRING, null); // display name } else if (entry != null && !online) { // remove entry tablistStorage.tablist.remove(name); wrapper.write(Type.VAR_INT, 4); // action wrapper.write(Type.VAR_INT, 1); // count wrapper.write(Type.UUID, entry.gameProfile.uuid); // uuid } else if (entry != null) { // update ping entry.ping = ping; wrapper.write(Type.VAR_INT, 2); // action wrapper.write(Type.VAR_INT, 1); // count wrapper.write(Type.UUID, entry.gameProfile.uuid); // uuid wrapper.write(Type.VAR_INT, entry.ping); // ping } else { wrapper.cancel(); } }); this.registerClientbound(ClientboundPackets1_7_2.SCOREBOARD_OBJECTIVE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // name handler(wrapper -> { final String value = wrapper.read(Type.STRING); // value final byte mode = wrapper.passthrough(Type.BYTE); // mode if (mode == 0/*CREATE*/ || mode == 2/*UPDATE*/) { wrapper.write(Type.STRING, value); wrapper.write(Type.STRING, "integer"); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.UPDATE_SCORE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // name map(Type.BYTE, Type.VAR_INT); // mode handler(wrapper -> { final int mode = wrapper.get(Type.VAR_INT, 0); if (mode == 0/*UPDATE*/) { wrapper.passthrough(Type.STRING); // objective wrapper.write(Type.VAR_INT, wrapper.read(Type.INT)); // score } else { wrapper.write(Type.STRING, ""); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.TEAMS, new PacketHandlers() { @Override public void register() { map(Type.STRING); // name handler(wrapper -> { final byte mode = wrapper.passthrough(Type.BYTE); // mode if (mode == 0 || mode == 2) { wrapper.passthrough(Type.STRING); // display name wrapper.passthrough(Type.STRING); // prefix wrapper.passthrough(Type.STRING); // suffix wrapper.passthrough(Type.BYTE); // flags wrapper.write(Type.STRING, "always"); // nametag visibility wrapper.write(Type.BYTE, (byte) 0); // color } if (mode == 0 || mode == 3 || mode == 4) { final int count = wrapper.read(Type.SHORT); // count final String[] playerNames = new String[count]; for (int i = 0; i < count; i++) { playerNames[i] = wrapper.read(Type.STRING); // player name } wrapper.write(Type.STRING_ARRAY, playerNames); } }); } }); this.registerClientbound(ClientboundPackets1_7_2.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // channel read(Type.UNSIGNED_SHORT); // length handlerSoftFail(wrapper -> { final String channel = wrapper.get(Type.STRING, 0); switch (channel) { case "MC|Brand": { wrapper.write(Type.STRING, new String(wrapper.read(Type.REMAINING_BYTES), StandardCharsets.UTF_8)); // brand break; } case "MC|TrList": wrapper.passthrough(Type.INT); // window id final int count = wrapper.passthrough(Type.UNSIGNED_BYTE); // count for (int i = 0; i < count; i++) { Item item = wrapper.read(Types1_7_6.ITEM); itemRewriter.handleItemToClient(wrapper.user(), item); wrapper.write(Type.ITEM1_8, item); // item 1 item = wrapper.read(Types1_7_6.ITEM); itemRewriter.handleItemToClient(wrapper.user(), item); wrapper.write(Type.ITEM1_8, item); // item 3 final boolean has3Items = wrapper.passthrough(Type.BOOLEAN); // has 3 items if (has3Items) { item = wrapper.read(Types1_7_6.ITEM); itemRewriter.handleItemToClient(wrapper.user(), item); wrapper.write(Type.ITEM1_8, item); // item 2 } wrapper.passthrough(Type.BOOLEAN); // unavailable wrapper.write(Type.INT, 0); // uses wrapper.write(Type.INT, Integer.MAX_VALUE); // max uses } break; case "MC|RPack": { final String url = new String(wrapper.read(Type.REMAINING_BYTES), StandardCharsets.UTF_8); // url wrapper.clearPacket(); wrapper.setPacketType(ClientboundPackets1_8.RESOURCE_PACK); wrapper.write(Type.STRING, url); // url wrapper.write(Type.STRING, "legacy"); // hash break; } } }); } }); this.registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO.getId(), ServerboundLoginPackets.HELLO.getId(), new PacketHandlers() { @Override public void register() { handler(wrapper -> { final String name = wrapper.passthrough(Type.STRING); // name final ProtocolInfo info = wrapper.user().getProtocolInfo(); // Set the information early if (info.getUsername() == null) { info.setUsername(name); } if (info.getUuid() == null) { info.setUuid(ViaLegacy.getConfig().isLegacySkinLoading() ? Via.getManager().getProviders().get(GameProfileFetcher.class).getMojangUUID(name) : new GameProfile(name).uuid); } }); } }); this.registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY.getId(), ServerboundLoginPackets.ENCRYPTION_KEY.getId(), new PacketHandlers() { @Override public void register() { map(Type.BYTE_ARRAY_PRIMITIVE, Type.SHORT_BYTE_ARRAY); // shared secret map(Type.BYTE_ARRAY_PRIMITIVE, Type.SHORT_BYTE_ARRAY); // verify token } }); this.registerServerbound(ServerboundPackets1_8.KEEP_ALIVE, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT, Type.INT); // key } }); this.registerServerbound(ServerboundPackets1_8.INTERACT_ENTITY, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT, Type.INT); // entity id handler(wrapper -> { final int mode = wrapper.read(Type.VAR_INT); // mode if (mode == 2) { // interactAt wrapper.write(Type.BYTE, (byte) 0); // mode wrapper.read(Type.FLOAT); // offsetX wrapper.read(Type.FLOAT); // offsetY wrapper.read(Type.FLOAT); // offsetZ final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); final EntityTypes1_10.EntityType entityType = entityTracker.getTrackedEntities().get(wrapper.get(Type.INT, 0)); if (entityType == null || !entityType.isOrHasParent(EntityTypes1_10.EntityType.ARMOR_STAND)) { wrapper.cancel(); } } else { wrapper.write(Type.BYTE, (byte) mode); // mode } }); } }); this.registerServerbound(ServerboundPackets1_8.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // x map(Type.DOUBLE); // y handler(wrapper -> wrapper.write(Type.DOUBLE, wrapper.get(Type.DOUBLE, 1) + 1.62)); // stance map(Type.DOUBLE); // z map(Type.BOOLEAN); // onGround } }); this.registerServerbound(ServerboundPackets1_8.PLAYER_POSITION_AND_ROTATION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // x map(Type.DOUBLE); // y handler(wrapper -> wrapper.write(Type.DOUBLE, wrapper.get(Type.DOUBLE, 1) + 1.62)); // stance map(Type.DOUBLE); // z map(Type.FLOAT); // yaw map(Type.FLOAT); // pitch map(Type.BOOLEAN); // onGround } }); this.registerServerbound(ServerboundPackets1_8.PLAYER_DIGGING, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT, Type.UNSIGNED_BYTE); // status map(Type.POSITION1_8, Types1_7_6.POSITION_UBYTE); // position map(Type.UNSIGNED_BYTE); // direction handler(wrapper -> { final short status = wrapper.get(Type.UNSIGNED_BYTE, 0); if (status == 5) { // RELEASE_USE_ITEM wrapper.set(Type.UNSIGNED_BYTE, 1, (short) 255); } }); } }); this.registerServerbound(ServerboundPackets1_8.PLAYER_BLOCK_PLACEMENT, new PacketHandlers() { @Override public void register() { map(Type.POSITION1_8, Types1_7_6.POSITION_UBYTE); // position map(Type.UNSIGNED_BYTE); // direction map(Type.ITEM1_8, Types1_7_6.ITEM); // item map(Type.UNSIGNED_BYTE); // offset x map(Type.UNSIGNED_BYTE); // offset y map(Type.UNSIGNED_BYTE); // offset z handler(wrapper -> { final short direction = wrapper.get(Type.UNSIGNED_BYTE, 0); final Item item = wrapper.get(Types1_7_6.ITEM, 0); itemRewriter.handleItemToServer(wrapper.user(), item); if (item != null && item.identifier() == ItemList1_6.writtenBook.itemID && direction == 255) { // If placed item is a book then cancel it and send a MC|BOpen to the client final PacketWrapper openBook = PacketWrapper.create(ClientboundPackets1_8.PLUGIN_MESSAGE, wrapper.user()); openBook.write(Type.STRING, "MC|BOpen"); // channel openBook.send(Protocol1_8to1_7_6_10.class); wrapper.cancel(); } }); } }); this.registerServerbound(ServerboundPackets1_8.ANIMATION, wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); wrapper.write(Type.INT, entityTracker.getPlayerID()); // entity id wrapper.write(Type.BYTE, (byte) 1); // animation }); this.registerServerbound(ServerboundPackets1_8.ENTITY_ACTION, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT, Type.INT); // entity id map(Type.VAR_INT, Type.BYTE, action -> (byte) (action + 1)); // action id map(Type.VAR_INT, Type.INT); // action parameter } }); this.registerServerbound(ServerboundPackets1_8.STEER_VEHICLE, new PacketHandlers() { @Override public void register() { map(Type.FLOAT); // sideways map(Type.FLOAT); // forwards handler(wrapper -> { final byte flags = wrapper.read(Type.BYTE); // flags wrapper.write(Type.BOOLEAN, (flags & 1) > 0); // jumping wrapper.write(Type.BOOLEAN, (flags & 2) > 0); // sneaking }); } }); this.registerServerbound(ServerboundPackets1_8.CLICK_WINDOW, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final short windowId = wrapper.read(Type.UNSIGNED_BYTE); // window id wrapper.write(Type.BYTE, (byte) windowId); // actually wrong, should be BYTE but Via uses U_BYTE final short slot = wrapper.passthrough(Type.SHORT); // slot final short windowType = wrapper.user().get(WindowTracker.class).get(windowId); if (windowType == 4/*enchanting_table*/) { if (slot == 1) { final PacketWrapper resetHandItem = PacketWrapper.create(ClientboundPackets1_8.SET_SLOT, wrapper.user()); resetHandItem.write(Type.UNSIGNED_BYTE, (short) -1); // window id resetHandItem.write(Type.SHORT, (short) 0); // slot resetHandItem.write(Type.ITEM1_8, new DataItem(-1, (byte) 0, (short) 0, null)); resetHandItem.send(Protocol1_8to1_7_6_10.class); final PacketWrapper setLapisSlot = PacketWrapper.create(ClientboundPackets1_8.SET_SLOT, wrapper.user()); setLapisSlot.write(Type.UNSIGNED_BYTE, windowId); setLapisSlot.write(Type.SHORT, slot); setLapisSlot.write(Type.ITEM1_8, new DataItem(351/*lapis_lazuli*/, (byte) 3, (short) 4, null)); setLapisSlot.send(Protocol1_8to1_7_6_10.class); wrapper.cancel(); } else if (slot > 1) { wrapper.set(Type.SHORT, 0, (short) (slot - 1)); } } }); map(Type.BYTE); // button map(Type.SHORT); // transaction id map(Type.BYTE); // action map(Type.ITEM1_8, Types1_7_6.ITEM); // item handler(wrapper -> itemRewriter.handleItemToServer(wrapper.user(), wrapper.get(Types1_7_6.ITEM, 0))); } }); this.registerServerbound(ServerboundPackets1_8.CREATIVE_INVENTORY_ACTION, new PacketHandlers() { @Override public void register() { map(Type.SHORT); // slot map(Type.ITEM1_8, Types1_7_6.ITEM); // item handler(wrapper -> itemRewriter.handleItemToServer(wrapper.user(), wrapper.get(Types1_7_6.ITEM, 0))); } }); this.registerServerbound(ServerboundPackets1_8.UPDATE_SIGN, new PacketHandlers() { @Override public void register() { map(Type.POSITION1_8, Types1_7_6.POSITION_SHORT); // position handler(wrapper -> { for (int i = 0; i < 4; i++) { final JsonElement component = wrapper.read(Type.COMPONENT); // line String text = TextComponentSerializer.V1_8.deserialize(component).asLegacyFormatString(); if (text.length() > 15) text = text.substring(0, 15); wrapper.write(Type.STRING, text); } }); } }); this.registerServerbound(ServerboundPackets1_8.TAB_COMPLETE, wrapper -> { final String text = wrapper.read(Type.STRING); // text wrapper.clearPacket(); // remove optional blockpos wrapper.write(Type.STRING, text); }); this.registerServerbound(ServerboundPackets1_8.CLIENT_SETTINGS, new PacketHandlers() { @Override public void register() { map(Type.STRING); // language map(Type.BYTE); // view distance map(Type.BYTE); // chat visibility map(Type.BOOLEAN); // enable colors create(Type.BYTE, (byte) 2); // difficulty (unused) map(Type.UNSIGNED_BYTE, Type.BOOLEAN, flags -> (flags & 1) == 1); // skin flags -> show cape } }); this.registerServerbound(ServerboundPackets1_8.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final String channel = wrapper.read(Type.STRING); // channel if (ViaLegacy.getConfig().isIgnoreLong1_8ChannelNames() && channel.length() > 16) { if (!Via.getConfig().isSuppressConversionWarnings() || Via.getManager().isDebug()) { ViaLegacy.getPlatform().getLogger().warning("Ignoring serverbound plugin channel, as it is longer than 16 characters: '" + channel + "'"); } wrapper.cancel(); return; } try { switch (channel) { case "MC|BEdit": case "MC|BSign": final Item item = wrapper.read(Type.ITEM1_8); // book itemRewriter.handleItemToServer(wrapper.user(), item); wrapper.write(Types1_7_6.ITEM, item); // book break; case "MC|Brand": case "MC|ItemName": final String content = wrapper.read(Type.STRING); // client brand or item name wrapper.write(Type.REMAINING_BYTES, content.getBytes(StandardCharsets.UTF_8)); // client brand or item name break; case "MC|AdvCdm": final byte type = wrapper.passthrough(Type.BYTE); // command block type (0 = Block, 1 = Minecart) if (type == 0) { wrapper.passthrough(Type.INT); // x wrapper.passthrough(Type.INT); // y wrapper.passthrough(Type.INT); // z } else if (type == 1) { wrapper.passthrough(Type.INT); // entity id } else { if (!Via.getConfig().isSuppressConversionWarnings() || Via.getManager().isDebug()) { ViaLegacy.getPlatform().getLogger().warning("Unknown 1.8 command block type: " + type); } wrapper.cancel(); return; } wrapper.passthrough(Type.STRING); // command wrapper.read(Type.BOOLEAN); // track output break; case "REGISTER": case "UNREGISTER": byte[] channels = wrapper.read(Type.REMAINING_BYTES); if (ViaLegacy.getConfig().isIgnoreLong1_8ChannelNames()) { final String[] registeredChannels = new String(channels, StandardCharsets.UTF_8).split("\0"); final List validChannels = new ArrayList<>(registeredChannels.length); for (String registeredChannel : registeredChannels) { if (registeredChannel.length() > 16) { if (!Via.getConfig().isSuppressConversionWarnings() || Via.getManager().isDebug()) { ViaLegacy.getPlatform().getLogger().warning("Ignoring serverbound plugin channel register of '" + registeredChannel + "', as it is longer than 16 characters"); } continue; } validChannels.add(registeredChannel); } if (validChannels.isEmpty()) { wrapper.cancel(); return; } channels = Joiner.on('\0').join(validChannels).getBytes(StandardCharsets.UTF_8); } wrapper.write(Type.REMAINING_BYTES, channels); // data break; } } catch (Exception e) { if (!Via.getConfig().isSuppressConversionWarnings() || Via.getManager().isDebug()) { Via.getPlatform().getLogger().log(Level.WARNING, "Failed to handle packet", e); } wrapper.cancel(); return; } final short length = (short) PacketUtil.calculateLength(wrapper); wrapper.resetReader(); wrapper.write(Type.STRING, channel); // channel wrapper.write(Type.SHORT, length); // length }); } }); this.cancelServerbound(ServerboundPackets1_8.SPECTATE); this.cancelServerbound(ServerboundPackets1_8.RESOURCE_PACK_STATUS); } private float randomFloatClamp(Random rnd, float min, float max) { return min >= max ? min : rnd.nextFloat() * (max - min) + min; } private int realignEntityY(final EntityTypes1_10.EntityType type, final int y) { float yPos = y / 32F; float yOffset = 0F; if (type == EntityTypes1_10.ObjectType.FALLING_BLOCK.getType()) yOffset = 0.98F / 2F; if (type == EntityTypes1_10.ObjectType.TNT_PRIMED.getType()) yOffset = 0.98F / 2F; if (type == EntityTypes1_10.ObjectType.ENDER_CRYSTAL.getType()) yOffset = 1F; else if (type == EntityTypes1_10.ObjectType.MINECART.getType()) yOffset = 0.7F / 2F; else if (type == EntityTypes1_10.ObjectType.BOAT.getType()) yOffset = 0.6F / 2F; else if (type == EntityTypes1_10.ObjectType.ITEM.getType()) yOffset = 0.25F / 2F; else if (type == EntityTypes1_10.ObjectType.LEASH.getType()) yOffset = 0.5F; else if (type == EntityTypes1_10.EntityType.EXPERIENCE_ORB) yOffset = 0.5F / 2F; return (int) Math.floor((yPos - yOffset) * 32F); } @Override public void register(ViaProviders providers) { providers.require(GameProfileFetcher.class); } @Override public void init(UserConnection userConnection) { userConnection.put(new TablistStorage(userConnection)); userConnection.put(new WindowTracker()); userConnection.put(new EntityTracker(userConnection)); userConnection.put(new MapStorage()); userConnection.put(new DimensionTracker()); userConnection.put(new ChunkTracker()); } @Override public LegacyItemRewriter getItemRewriter() { return this.itemRewriter; } public MetadataRewriter getMetadataRewriter() { return this.metadataRewriter; } }