/* * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion * Copyright (C) 2016-2024 ViaVersion 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 com.viaversion.viaversion.protocols.protocol1_9to1_8.packets; import com.google.gson.JsonObject; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_10; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.protocols.protocol1_8.ClientboundPackets1_8; import com.viaversion.viaversion.protocols.protocol1_9to1_8.ItemRewriter; import com.viaversion.viaversion.protocols.protocol1_9to1_8.PlayerMovementMapper; import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; import com.viaversion.viaversion.protocols.protocol1_9to1_8.ServerboundPackets1_9; import com.viaversion.viaversion.protocols.protocol1_9to1_8.chat.ChatRewriter; import com.viaversion.viaversion.protocols.protocol1_9to1_8.chat.GameMode; import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.CommandBlockProvider; import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.CompressionProvider; import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.MainHandProvider; import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.ClientChunks; import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.EntityTracker1_9; import java.util.logging.Level; public class PlayerPackets { public static void register(Protocol1_9To1_8 protocol) { protocol.registerClientbound(ClientboundPackets1_8.CHAT_MESSAGE, new PacketHandlers() { @Override public void register() { map(Type.STRING, Protocol1_9To1_8.FIX_JSON); // 0 - Chat Message (json) map(Type.BYTE); // 1 - Chat Position handler(wrapper -> { try { JsonObject obj = (JsonObject) wrapper.get(Type.COMPONENT, 0); ChatRewriter.toClient(obj, wrapper.user()); } catch (Exception e) { Via.getPlatform().getLogger().log(Level.SEVERE, "Failed to transform chat component", e); } }); } }); protocol.registerClientbound(ClientboundPackets1_8.TAB_LIST, new PacketHandlers() { @Override public void register() { map(Type.STRING, Protocol1_9To1_8.FIX_JSON); // 0 - Header map(Type.STRING, Protocol1_9To1_8.FIX_JSON); // 1 - Footer } }); protocol.registerClientbound(ClientboundPackets1_8.DISCONNECT, new PacketHandlers() { @Override public void register() { map(Type.STRING, Protocol1_9To1_8.FIX_JSON); // 0 - Reason } }); protocol.registerClientbound(ClientboundPackets1_8.TITLE, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Action // We only handle if the title or subtitle is set then just write through. handler(wrapper -> { int action = wrapper.get(Type.VAR_INT, 0); if (action == 0 || action == 1) { Protocol1_9To1_8.FIX_JSON.write(wrapper, wrapper.read(Type.STRING)); } }); // Everything else is handled. } }); protocol.registerClientbound(ClientboundPackets1_8.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // 0 - Player X map(Type.DOUBLE); // 1 - Player Y map(Type.DOUBLE); // 2 - Player Z map(Type.FLOAT); // 3 - Player Yaw map(Type.FLOAT); // 4 - Player Pitch map(Type.BYTE); // 5 - Player Flags handler(wrapper -> { wrapper.write(Type.VAR_INT, 0); // 6 - Teleport ID was added }); } }); protocol.registerClientbound(ClientboundPackets1_8.TEAMS, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Team Name map(Type.BYTE); // 1 - Mode handler(wrapper -> { byte mode = wrapper.get(Type.BYTE, 0); // 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); // Friendly Fire wrapper.passthrough(Type.STRING); // Name tag visibility wrapper.write(Type.STRING, Via.getConfig().isPreventCollision() ? "never" : ""); wrapper.passthrough(Type.BYTE); // Colour } if (mode == 0 || mode == 3 || mode == 4) { String[] players = wrapper.passthrough(Type.STRING_ARRAY); // Players final EntityTracker1_9 entityTracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); String myName = wrapper.user().getProtocolInfo().getUsername(); String teamName = wrapper.get(Type.STRING, 0); for (String player : players) { if (entityTracker.isAutoTeam() && player.equalsIgnoreCase(myName)) { if (mode == 4) { // since removing add to auto team // Workaround for packet order issue wrapper.send(Protocol1_9To1_8.class); wrapper.cancel(); entityTracker.sendTeamPacket(true, true); entityTracker.setCurrentTeam("viaversion"); } else { // since adding remove from auto team entityTracker.sendTeamPacket(false, true); entityTracker.setCurrentTeam(teamName); } } } } if (mode == 1) { // Remove team final EntityTracker1_9 entityTracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); String teamName = wrapper.get(Type.STRING, 0); if (entityTracker.isAutoTeam() && teamName.equals(entityTracker.getCurrentTeam())) { // team was removed // Workaround for packet order issue wrapper.send(Protocol1_9To1_8.class); wrapper.cancel(); entityTracker.sendTeamPacket(true, true); entityTracker.setCurrentTeam("viaversion"); } } }); } }); protocol.registerClientbound(ClientboundPackets1_8.JOIN_GAME, new PacketHandlers() { @Override public void register() { map(Type.INT); // 0 - Player ID // Parse this info handler(wrapper -> { int entityId = wrapper.get(Type.INT, 0); EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); tracker.addEntity(entityId, EntityTypes1_10.EntityType.PLAYER); tracker.setClientEntityId(entityId); }); map(Type.UNSIGNED_BYTE); // 1 - Player Gamemode map(Type.BYTE); // 2 - Player Dimension map(Type.UNSIGNED_BYTE); // 3 - World Difficulty map(Type.UNSIGNED_BYTE); // 4 - Max Players (Tab) map(Type.STRING); // 5 - Level Type map(Type.BOOLEAN); // 6 - Reduced Debug info handler(wrapper -> { EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); tracker.setGameMode(GameMode.getById(wrapper.get(Type.UNSIGNED_BYTE, 0))); //Set player gamemode }); // Track player's dimension handler(wrapper -> { ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); int dimensionId = wrapper.get(Type.BYTE, 0); clientWorld.setEnvironment(dimensionId); }); // Fake their op status handler(wrapper -> { CommandBlockProvider provider = Via.getManager().getProviders().get(CommandBlockProvider.class); provider.sendPermission(wrapper.user()); }); // Scoreboard will be cleared when join game is received handler(wrapper -> { EntityTracker1_9 entityTracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); if (Via.getConfig().isAutoTeam()) { entityTracker.setAutoTeam(true); // Workaround for packet order issue wrapper.send(Protocol1_9To1_8.class); wrapper.cancel(); entityTracker.sendTeamPacket(true, true); entityTracker.setCurrentTeam("viaversion"); } else { entityTracker.setAutoTeam(false); } }); } }); protocol.registerClientbound(ClientboundPackets1_8.PLAYER_INFO, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Action map(Type.VAR_INT); // 1 - Player Count // Due to this being a complex data structure we just use a handler. handler(wrapper -> { int action = wrapper.get(Type.VAR_INT, 0); int count = wrapper.get(Type.VAR_INT, 1); for (int i = 0; i < count; i++) { wrapper.passthrough(Type.UUID); // Player UUID if (action == 0) { // add player wrapper.passthrough(Type.STRING); // Player Name int properties = wrapper.passthrough(Type.VAR_INT); // loop through properties for (int j = 0; j < properties; j++) { wrapper.passthrough(Type.STRING); // name wrapper.passthrough(Type.STRING); // value wrapper.passthrough(Type.OPTIONAL_STRING); // signature } wrapper.passthrough(Type.VAR_INT); // gamemode wrapper.passthrough(Type.VAR_INT); // ping String displayName = wrapper.read(Type.OPTIONAL_STRING); wrapper.write(Type.OPTIONAL_COMPONENT, displayName != null ? Protocol1_9To1_8.FIX_JSON.transform(wrapper, displayName) : null); } else if ((action == 1) || (action == 2)) { // update gamemode || update latency wrapper.passthrough(Type.VAR_INT); } else if (action == 3) { // update display name String displayName = wrapper.read(Type.OPTIONAL_STRING); wrapper.write(Type.OPTIONAL_COMPONENT, displayName != null ? Protocol1_9To1_8.FIX_JSON.transform(wrapper, displayName) : null); } } }); } }); protocol.registerClientbound(ClientboundPackets1_8.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Channel Name handler(wrapper -> { String name = wrapper.get(Type.STRING, 0); if (name.equalsIgnoreCase("MC|BOpen")) { wrapper.write(Type.VAR_INT, 0); } else if (name.equalsIgnoreCase("MC|TrList")) { wrapper.passthrough(Type.INT); // ID Short size = wrapper.passthrough(Type.UNSIGNED_BYTE); for (int i = 0; i < size; ++i) { Item item1 = wrapper.passthrough(Type.ITEM1_8); ItemRewriter.toClient(item1); Item item2 = wrapper.passthrough(Type.ITEM1_8); ItemRewriter.toClient(item2); boolean present = wrapper.passthrough(Type.BOOLEAN); if (present) { Item item3 = wrapper.passthrough(Type.ITEM1_8); ItemRewriter.toClient(item3); } wrapper.passthrough(Type.BOOLEAN); wrapper.passthrough(Type.INT); wrapper.passthrough(Type.INT); } } }); } }); protocol.registerClientbound(ClientboundPackets1_8.RESPAWN, new PacketHandlers() { @Override public void register() { map(Type.INT); // 0 - Dimension map(Type.UNSIGNED_BYTE); // 1 - Difficulty map(Type.UNSIGNED_BYTE); // 2 - GameMode map(Type.STRING); // 3 - Level Type // Track player's dimension handler(wrapper -> { ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); int dimensionId = wrapper.get(Type.INT, 0); clientWorld.setEnvironment(dimensionId); }); handler(wrapper -> { // Client unloads chunks on respawn wrapper.user().get(ClientChunks.class).getLoadedChunks().clear(); int gamemode = wrapper.get(Type.UNSIGNED_BYTE, 0); EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); tracker.setGameMode(GameMode.getById(gamemode)); }); // Fake permissions to get Commandblocks working handler(wrapper -> { CommandBlockProvider provider = Via.getManager().getProviders().get(CommandBlockProvider.class); provider.sendPermission(wrapper.user()); provider.unloadChunks(wrapper.user()); }); } }); protocol.registerClientbound(ClientboundPackets1_8.GAME_EVENT, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); //0 - Reason map(Type.FLOAT); //1 - Value handler(wrapper -> { short reason = wrapper.get(Type.UNSIGNED_BYTE, 0); if (reason == 3) { //Change gamemode int gamemode = wrapper.get(Type.FLOAT, 0).intValue(); EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); tracker.setGameMode(GameMode.getById(gamemode)); } else if (reason == 4) { //Open credits screen wrapper.set(Type.FLOAT, 0, 1F); } }); } }); /* Removed packets */ protocol.registerClientbound(ClientboundPackets1_8.SET_COMPRESSION, null, wrapper -> { wrapper.cancel(); CompressionProvider provider = Via.getManager().getProviders().get(CompressionProvider.class); provider.handlePlayCompression(wrapper.user(), wrapper.read(Type.VAR_INT)); }); /* Incoming Packets */ protocol.registerServerbound(ServerboundPackets1_9.TAB_COMPLETE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Requested Command read(Type.BOOLEAN); // 1 - Is Command Block } }); protocol.registerServerbound(ServerboundPackets1_9.CLIENT_SETTINGS, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - locale map(Type.BYTE); // 1 - View Distance map(Type.VAR_INT, Type.BYTE); // 2 - Chat Mode map(Type.BOOLEAN); // 3 - If Chat Colours on map(Type.UNSIGNED_BYTE); // 4 - Skin Parts handler(wrapper -> { int hand = wrapper.read(Type.VAR_INT); // Add 0x80 if left-handed if (Via.getConfig().isLeftHandedHandling() && hand == 0) { wrapper.set(Type.UNSIGNED_BYTE, 0, (short) (wrapper.get(Type.UNSIGNED_BYTE, 0).intValue() | 0x80)); } wrapper.sendToServer(Protocol1_9To1_8.class); wrapper.cancel(); Via.getManager().getProviders().get(MainHandProvider.class).setMainHand(wrapper.user(), hand); }); } }); protocol.registerServerbound(ServerboundPackets1_9.ANIMATION, new PacketHandlers() { @Override public void register() { read(Type.VAR_INT); // 0 - Hand } }); protocol.cancelServerbound(ServerboundPackets1_9.TELEPORT_CONFIRM); protocol.cancelServerbound(ServerboundPackets1_9.VEHICLE_MOVE); protocol.cancelServerbound(ServerboundPackets1_9.STEER_BOAT); protocol.registerServerbound(ServerboundPackets1_9.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Channel Name handler(wrapper -> { String name = wrapper.get(Type.STRING, 0); if (name.equalsIgnoreCase("MC|BSign")) { Item item = wrapper.passthrough(Type.ITEM1_8); if (item != null) { item.setIdentifier(387); // Written Book ItemRewriter.rewriteBookToServer(item); } } if (name.equalsIgnoreCase("MC|AutoCmd")) { wrapper.set(Type.STRING, 0, "MC|AdvCdm"); wrapper.write(Type.BYTE, (byte) 0); wrapper.passthrough(Type.INT); // X wrapper.passthrough(Type.INT); // Y wrapper.passthrough(Type.INT); // Z wrapper.passthrough(Type.STRING); // Command wrapper.passthrough(Type.BOOLEAN); // Flag wrapper.clearInputBuffer(); } if (name.equalsIgnoreCase("MC|AdvCmd")) { wrapper.set(Type.STRING, 0, "MC|AdvCdm"); } }); } }); protocol.registerServerbound(ServerboundPackets1_9.CLIENT_STATUS, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Action ID handler(wrapper -> { int action = wrapper.get(Type.VAR_INT, 0); if (action == 2) { // cancel any blocking >.> EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); if (tracker.isBlocking()) { if (!Via.getConfig().isShowShieldWhenSwordInHand()) { tracker.setSecondHand(null); } tracker.setBlocking(false); } } }); } }); protocol.registerServerbound(ServerboundPackets1_9.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // 0 - X map(Type.DOUBLE); // 1 - Y map(Type.DOUBLE); // 2 - Z map(Type.BOOLEAN); // 3 - Ground handler(new PlayerMovementMapper()); } }); protocol.registerServerbound(ServerboundPackets1_9.PLAYER_POSITION_AND_ROTATION, new PacketHandlers() { @Override public void register() { map(Type.DOUBLE); // 0 - X map(Type.DOUBLE); // 1 - Y map(Type.DOUBLE); // 2 - Z map(Type.FLOAT); // 3 - Yaw map(Type.FLOAT); // 4 - Pitch map(Type.BOOLEAN); // 5 - Ground handler(new PlayerMovementMapper()); } }); protocol.registerServerbound(ServerboundPackets1_9.PLAYER_ROTATION, new PacketHandlers() { @Override public void register() { map(Type.FLOAT); // 0 - Yaw map(Type.FLOAT); // 1 - Pitch map(Type.BOOLEAN); // 2 - Ground handler(new PlayerMovementMapper()); } }); protocol.registerServerbound(ServerboundPackets1_9.PLAYER_MOVEMENT, new PacketHandlers() { @Override public void register() { map(Type.BOOLEAN); // 0 - Ground handler(new PlayerMovementMapper()); } }); } }