/* * 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_13to1_12_2; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Position; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; 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.PacketHandler; 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.misc.ParticleType; import com.viaversion.viaversion.api.type.types.version.Types1_13; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.protocol1_12_1to1_12.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.protocol1_12_1to1_12.ServerboundPackets1_12_1; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.ConnectionData; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.providers.BlockConnectionProvider; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.providers.PacketBlockConnectionProvider; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.BlockIdData; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.ComponentRewriter1_13; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.MappingData; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.RecipeData; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.StatisticData; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.StatisticMappings; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.metadata.MetadataRewriter1_13To1_12_2; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.EntityPackets; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.InventoryPackets; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.WorldPackets; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.BlockEntityProvider; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.PaintingProvider; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.PlayerLookTargetProvider; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockConnectionStorage; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.TabCompleteTracker; import com.viaversion.viaversion.rewriter.SoundRewriter; import com.viaversion.viaversion.util.ChatColorUtil; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.GsonUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; public class Protocol1_13To1_12_2 extends AbstractProtocol { public static final MappingData MAPPINGS = new MappingData(); // These are arbitrary rewrite values, it just needs an invalid color code character. private static final Map SCOREBOARD_TEAM_NAME_REWRITE = new HashMap<>(); private static final Set FORMATTING_CODES = Sets.newHashSet('k', 'l', 'm', 'n', 'o', 'r'); private final MetadataRewriter1_13To1_12_2 entityRewriter = new MetadataRewriter1_13To1_12_2(this); private final InventoryPackets itemRewriter = new InventoryPackets(this); private final ComponentRewriter1_13 componentRewriter = new ComponentRewriter1_13<>(this); static { SCOREBOARD_TEAM_NAME_REWRITE.put('0', 'g'); SCOREBOARD_TEAM_NAME_REWRITE.put('1', 'h'); SCOREBOARD_TEAM_NAME_REWRITE.put('2', 'i'); SCOREBOARD_TEAM_NAME_REWRITE.put('3', 'j'); SCOREBOARD_TEAM_NAME_REWRITE.put('4', 'p'); SCOREBOARD_TEAM_NAME_REWRITE.put('5', 'q'); SCOREBOARD_TEAM_NAME_REWRITE.put('6', 's'); SCOREBOARD_TEAM_NAME_REWRITE.put('7', 't'); SCOREBOARD_TEAM_NAME_REWRITE.put('8', 'u'); SCOREBOARD_TEAM_NAME_REWRITE.put('9', 'v'); SCOREBOARD_TEAM_NAME_REWRITE.put('a', 'w'); SCOREBOARD_TEAM_NAME_REWRITE.put('b', 'x'); SCOREBOARD_TEAM_NAME_REWRITE.put('c', 'y'); SCOREBOARD_TEAM_NAME_REWRITE.put('d', 'z'); SCOREBOARD_TEAM_NAME_REWRITE.put('e', '!'); SCOREBOARD_TEAM_NAME_REWRITE.put('f', '?'); SCOREBOARD_TEAM_NAME_REWRITE.put('k', '#'); SCOREBOARD_TEAM_NAME_REWRITE.put('l', '('); SCOREBOARD_TEAM_NAME_REWRITE.put('m', ')'); SCOREBOARD_TEAM_NAME_REWRITE.put('n', ':'); SCOREBOARD_TEAM_NAME_REWRITE.put('o', ';'); SCOREBOARD_TEAM_NAME_REWRITE.put('r', '/'); } public Protocol1_13To1_12_2() { super(ClientboundPackets1_12_1.class, ClientboundPackets1_13.class, ServerboundPackets1_12_1.class, ServerboundPackets1_13.class); } public static final PacketHandler POS_TO_3_INT = wrapper -> { Position position = wrapper.read(Type.POSITION1_8); wrapper.write(Type.INT, position.x()); wrapper.write(Type.INT, position.y()); wrapper.write(Type.INT, position.z()); }; public static final PacketHandler SEND_DECLARE_COMMANDS_AND_TAGS = w -> { // Send fake declare commands w.create(ClientboundPackets1_13.DECLARE_COMMANDS, wrapper -> { wrapper.write(Type.VAR_INT, 2); // Size // Write root node wrapper.write(Type.BYTE, (byte) 0); // Mark as command wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, new int[]{1}); // 1 child at index 1 // Write arg node wrapper.write(Type.BYTE, (byte) (0x02 | 0x04 | 0x10)); // Mark as command wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, new int[0]); // No children // Extra data wrapper.write(Type.STRING, "args"); // Arg name wrapper.write(Type.STRING, "brigadier:string"); wrapper.write(Type.VAR_INT, 2); // Greedy wrapper.write(Type.STRING, "minecraft:ask_server"); // Ask server wrapper.write(Type.VAR_INT, 0); // Root node index }).scheduleSend(Protocol1_13To1_12_2.class); // Send tags packet w.create(ClientboundPackets1_13.TAGS, wrapper -> { wrapper.write(Type.VAR_INT, MAPPINGS.getBlockTags().size()); // block tags for (Map.Entry tag : MAPPINGS.getBlockTags().entrySet()) { wrapper.write(Type.STRING, tag.getKey()); // Needs copy as other protocols may modify it wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue()); } wrapper.write(Type.VAR_INT, MAPPINGS.getItemTags().size()); // item tags for (Map.Entry tag : MAPPINGS.getItemTags().entrySet()) { wrapper.write(Type.STRING, tag.getKey()); // Needs copy as other protocols may modify it wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue()); } wrapper.write(Type.VAR_INT, MAPPINGS.getFluidTags().size()); // fluid tags for (Map.Entry tag : MAPPINGS.getFluidTags().entrySet()) { wrapper.write(Type.STRING, tag.getKey()); // Needs copy as other protocols may modify it wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue()); } }).scheduleSend(Protocol1_13To1_12_2.class); }; @Override protected void registerPackets() { super.registerPackets(); EntityPackets.register(this); WorldPackets.register(this); registerClientbound(State.LOGIN, 0x00, 0x00, wrapper -> componentRewriter.processText(wrapper.passthrough(Type.COMPONENT))); registerClientbound(State.STATUS, 0x00, 0x00, new PacketHandlers() { @Override public void register() { map(Type.STRING); handler(wrapper -> { String response = wrapper.get(Type.STRING, 0); try { JsonObject json = GsonUtil.getGson().fromJson(response, JsonObject.class); if (json.has("favicon")) { json.addProperty("favicon", json.get("favicon").getAsString().replace("\n", "")); } wrapper.set(Type.STRING, 0, GsonUtil.getGson().toJson(json)); } catch (JsonParseException e) { Via.getPlatform().getLogger().log(Level.SEVERE, "Error transforming status response", e); } }); } }); // New packet 0x04 - Login Plugin Message // Statistics registerClientbound(ClientboundPackets1_12_1.STATISTICS, wrapper -> { int size = wrapper.read(Type.VAR_INT); List remappedStats = new ArrayList<>(); for (int i = 0; i < size; i++) { String name = wrapper.read(Type.STRING); String[] split = name.split("\\."); int categoryId = 0; int newId = -1; int value = wrapper.read(Type.VAR_INT); if (split.length == 2) { // Custom types categoryId = 8; Integer newIdRaw = StatisticMappings.CUSTOM_STATS.get(name); if (newIdRaw != null) { newId = newIdRaw; } else { Via.getPlatform().getLogger().warning("Could not find 1.13 -> 1.12.2 statistic mapping for " + name); } } else if (split.length > 2) { String category = split[1]; //TODO convert string ids (blocks, items, entities) switch (category) { case "mineBlock": categoryId = 0; break; case "craftItem": categoryId = 1; break; case "useItem": categoryId = 2; break; case "breakItem": categoryId = 3; break; case "pickup": categoryId = 4; break; case "drop": categoryId = 5; break; case "killEntity": categoryId = 6; break; case "entityKilledBy": categoryId = 7; break; } } if (newId != -1) remappedStats.add(new StatisticData(categoryId, newId, value)); } wrapper.write(Type.VAR_INT, remappedStats.size()); // size for (StatisticData stat : remappedStats) { wrapper.write(Type.VAR_INT, stat.getCategoryId()); // category id wrapper.write(Type.VAR_INT, stat.getNewId()); // statistics id wrapper.write(Type.VAR_INT, stat.getValue()); // value } }); componentRewriter.registerBossBar(ClientboundPackets1_12_1.BOSSBAR); componentRewriter.registerComponentPacket(ClientboundPackets1_12_1.CHAT_MESSAGE); registerClientbound(ClientboundPackets1_12_1.TAB_COMPLETE, wrapper -> { wrapper.write(Type.VAR_INT, wrapper.user().get(TabCompleteTracker.class).getTransactionId()); String input = wrapper.user().get(TabCompleteTracker.class).getInput(); // Start & End int index; int length; // If no input or new word (then it's the start) if (input.endsWith(" ") || input.isEmpty()) { index = input.length(); length = 0; } else { // Otherwise find the last space (+1 as we include it) int lastSpace = input.lastIndexOf(' ') + 1; index = lastSpace; length = input.length() - lastSpace; } // Write index + length wrapper.write(Type.VAR_INT, index); wrapper.write(Type.VAR_INT, length); int count = wrapper.passthrough(Type.VAR_INT); for (int i = 0; i < count; i++) { String suggestion = wrapper.read(Type.STRING); // If we're at the start then handle removing slash if (suggestion.startsWith("/") && index == 0) { suggestion = suggestion.substring(1); } wrapper.write(Type.STRING, suggestion); wrapper.write(Type.OPTIONAL_COMPONENT, null); // Tooltip } }); registerClientbound(ClientboundPackets1_12_1.OPEN_WINDOW, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // Id map(Type.STRING); // Window type handler(wrapper -> componentRewriter.processText(wrapper.passthrough(Type.COMPONENT))); // Title } }); registerClientbound(ClientboundPackets1_12_1.COOLDOWN, wrapper -> { int item = wrapper.read(Type.VAR_INT); int ticks = wrapper.read(Type.VAR_INT); wrapper.cancel(); if (item == 383) { // Spawn egg for (int i = 0; i < 44; i++) { int newItem = MAPPINGS.getItemMappings().getNewId(item << 16 | i); if (newItem != -1) { PacketWrapper packet = wrapper.create(ClientboundPackets1_13.COOLDOWN); packet.write(Type.VAR_INT, newItem); packet.write(Type.VAR_INT, ticks); packet.send(Protocol1_13To1_12_2.class); } else { break; } } } else { for (int i = 0; i < 16; i++) { int newItem = MAPPINGS.getItemMappings().getNewId(item << 4 | i); if (newItem != -1) { PacketWrapper packet = wrapper.create(ClientboundPackets1_13.COOLDOWN); packet.write(Type.VAR_INT, newItem); packet.write(Type.VAR_INT, ticks); packet.send(Protocol1_13To1_12_2.class); } else { break; } } } }); componentRewriter.registerComponentPacket(ClientboundPackets1_12_1.DISCONNECT); registerClientbound(ClientboundPackets1_12_1.EFFECT, new PacketHandlers() { @Override public void register() { map(Type.INT); // Effect Id map(Type.POSITION1_8); // Location map(Type.INT); // Data handler(wrapper -> { int id = wrapper.get(Type.INT, 0); int data = wrapper.get(Type.INT, 1); if (id == 1010) { // Play record wrapper.set(Type.INT, 1, getMappingData().getItemMappings().getNewId(data << 4)); } else if (id == 2001) { // Block break + block break sound int blockId = data & 0xFFF; int blockData = data >> 12; wrapper.set(Type.INT, 1, WorldPackets.toNewId(blockId << 4 | blockData)); } }); } }); registerClientbound(ClientboundPackets1_12_1.CRAFT_RECIPE_RESPONSE, new PacketHandlers() { @Override public void register() { map(Type.BYTE); handler(wrapper -> wrapper.write(Type.STRING, "viaversion:legacy/" + wrapper.read(Type.VAR_INT))); } }); componentRewriter.registerCombatEvent(ClientboundPackets1_12_1.COMBAT_EVENT); registerClientbound(ClientboundPackets1_12_1.MAP_DATA, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Map id map(Type.BYTE); // 1 - Scale map(Type.BOOLEAN); // 2 - Tracking Position handler(wrapper -> { int iconCount = wrapper.passthrough(Type.VAR_INT); for (int i = 0; i < iconCount; i++) { byte directionAndType = wrapper.read(Type.BYTE); int type = (directionAndType & 0xF0) >> 4; wrapper.write(Type.VAR_INT, type); wrapper.passthrough(Type.BYTE); // Icon X wrapper.passthrough(Type.BYTE); // Icon Z byte direction = (byte) (directionAndType & 0x0F); wrapper.write(Type.BYTE, direction); wrapper.write(Type.OPTIONAL_COMPONENT, null); // Display Name } }); } }); registerClientbound(ClientboundPackets1_12_1.UNLOCK_RECIPES, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // action map(Type.BOOLEAN); // crafting book open map(Type.BOOLEAN); // crafting filter active handler(wrapper -> { wrapper.write(Type.BOOLEAN, false); // smelting book open wrapper.write(Type.BOOLEAN, false); // smelting filter active }); handler(wrapper -> { int action = wrapper.get(Type.VAR_INT, 0); for (int i = 0; i < (action == 0 ? 2 : 1); i++) { int[] ids = wrapper.read(Type.VAR_INT_ARRAY_PRIMITIVE); String[] stringIds = new String[ids.length]; for (int j = 0; j < ids.length; j++) { stringIds[j] = "viaversion:legacy/" + ids[j]; } wrapper.write(Type.STRING_ARRAY, stringIds); } if (action == 0) { wrapper.create(ClientboundPackets1_13.DECLARE_RECIPES, w -> writeDeclareRecipes(w)).send(Protocol1_13To1_12_2.class); } }); } }); registerClientbound(ClientboundPackets1_12_1.RESPAWN, new PacketHandlers() { @Override public void register() { map(Type.INT); // 0 - Dimension ID handler(wrapper -> { ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); int dimensionId = wrapper.get(Type.INT, 0); clientWorld.setEnvironment(dimensionId); if (Via.getConfig().isServersideBlockConnections()) { ConnectionData.clearBlockStorage(wrapper.user()); } }); handler(SEND_DECLARE_COMMANDS_AND_TAGS); } }); registerClientbound(ClientboundPackets1_12_1.SCOREBOARD_OBJECTIVE, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Objective name map(Type.BYTE); // 1 - Mode handler(wrapper -> { byte mode = wrapper.get(Type.BYTE, 0); // On create or update if (mode == 0 || mode == 2) { String value = wrapper.read(Type.STRING); // Value wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(value)); String type = wrapper.read(Type.STRING); // integer or hearts wrapper.write(Type.VAR_INT, type.equals("integer") ? 0 : 1); } }); } }); registerClientbound(ClientboundPackets1_12_1.TEAMS, new PacketHandlers() { @Override public void register() { map(Type.STRING); // 0 - Team Name map(Type.BYTE); // 1 - Mode handler(wrapper -> { byte action = wrapper.get(Type.BYTE, 0); if (action == 0 || action == 2) { String displayName = wrapper.read(Type.STRING); // Display Name wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(displayName)); String prefix = wrapper.read(Type.STRING); // Prefix moved String suffix = wrapper.read(Type.STRING); // Suffix moved wrapper.passthrough(Type.BYTE); // Flags wrapper.passthrough(Type.STRING); // Name Tag Visibility wrapper.passthrough(Type.STRING); // Collision rule // Handle new colors int colour = wrapper.read(Type.BYTE).intValue(); if (colour == -1) { colour = 21; // -1 changed to 21 } if (Via.getConfig().is1_13TeamColourFix()) { char lastColorChar = getLastColorChar(prefix); colour = ChatColorUtil.getColorOrdinal(lastColorChar); suffix = ChatColorUtil.COLOR_CHAR + Character.toString(lastColorChar) + suffix; } wrapper.write(Type.VAR_INT, colour); wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(prefix)); // Prefix wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(suffix)); // Suffix } if (action == 0 || action == 3 || action == 4) { String[] names = wrapper.read(Type.STRING_ARRAY); // Entities for (int i = 0; i < names.length; i++) { names[i] = rewriteTeamMemberName(names[i]); } wrapper.write(Type.STRING_ARRAY, names); } }); } }); registerClientbound(ClientboundPackets1_12_1.UPDATE_SCORE, wrapper -> { String displayName = wrapper.read(Type.STRING); // Display Name displayName = rewriteTeamMemberName(displayName); wrapper.write(Type.STRING, displayName); }); componentRewriter.registerTitle(ClientboundPackets1_12_1.TITLE); // New 0x4C - Stop Sound new SoundRewriter<>(this).registerSound(ClientboundPackets1_12_1.SOUND); registerClientbound(ClientboundPackets1_12_1.TAB_LIST, wrapper -> { componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); }); registerClientbound(ClientboundPackets1_12_1.ADVANCEMENTS, wrapper -> { wrapper.passthrough(Type.BOOLEAN); // Reset/clear int size = wrapper.passthrough(Type.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Type.STRING); // Identifier // Parent if (wrapper.passthrough(Type.BOOLEAN)) wrapper.passthrough(Type.STRING); // Display data if (wrapper.passthrough(Type.BOOLEAN)) { componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); // Title componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); // Description Item icon = wrapper.read(Type.ITEM1_8); itemRewriter.handleItemToClient(icon); wrapper.write(Type.ITEM1_13, icon); // Translate item to flat item wrapper.passthrough(Type.VAR_INT); // Frame type int flags = wrapper.passthrough(Type.INT); // Flags if ((flags & 1) != 0) { wrapper.passthrough(Type.STRING); // Background texture } wrapper.passthrough(Type.FLOAT); // X wrapper.passthrough(Type.FLOAT); // Y } wrapper.passthrough(Type.STRING_ARRAY); // Criteria int arrayLength = wrapper.passthrough(Type.VAR_INT); for (int array = 0; array < arrayLength; array++) { wrapper.passthrough(Type.STRING_ARRAY); // String array } } }); // Incoming packets // New packet 0x02 - Login Plugin Message cancelServerbound(State.LOGIN, 0x02); // New 0x01 - Query Block NBT cancelServerbound(ServerboundPackets1_13.QUERY_BLOCK_NBT); // Tab-Complete registerServerbound(ServerboundPackets1_13.TAB_COMPLETE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { // Disable auto-complete if configured if (Via.getConfig().isDisable1_13AutoComplete()) { wrapper.cancel(); } int tid = wrapper.read(Type.VAR_INT); // Save transaction id wrapper.user().get(TabCompleteTracker.class).setTransactionId(tid); }); // Prepend / map(Type.STRING, new ValueTransformer(Type.STRING) { @Override public String transform(PacketWrapper wrapper, String inputValue) { wrapper.user().get(TabCompleteTracker.class).setInput(inputValue); return "/" + inputValue; } }); // Fake the end of the packet handler(wrapper -> { wrapper.write(Type.BOOLEAN, false); final Position playerLookTarget = Via.getManager().getProviders().get(PlayerLookTargetProvider.class).getPlayerLookTarget(wrapper.user()); wrapper.write(Type.OPTIONAL_POSITION1_8, playerLookTarget); if (!wrapper.isCancelled() && Via.getConfig().get1_13TabCompleteDelay() > 0) { TabCompleteTracker tracker = wrapper.user().get(TabCompleteTracker.class); wrapper.cancel(); tracker.setTimeToSend(System.currentTimeMillis() + Via.getConfig().get1_13TabCompleteDelay() * 50L); tracker.setLastTabComplete(wrapper.get(Type.STRING, 0)); } }); } }); // New 0x0A - Edit book -> Plugin Message registerServerbound(ServerboundPackets1_13.EDIT_BOOK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> { Item item = wrapper.read(Type.ITEM1_13); boolean isSigning = wrapper.read(Type.BOOLEAN); itemRewriter.handleItemToServer(item); wrapper.write(Type.STRING, isSigning ? "MC|BSign" : "MC|BEdit"); // Channel wrapper.write(Type.ITEM1_8, item); }); // New 0x0C - Query Entity NBT cancelServerbound(ServerboundPackets1_13.ENTITY_NBT_REQUEST); // New 0x15 - Pick Item -> Plugin Message registerServerbound(ServerboundPackets1_13.PICK_ITEM, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> { wrapper.write(Type.STRING, "MC|PickItem"); // Channel }); registerServerbound(ServerboundPackets1_13.CRAFT_RECIPE_REQUEST, new PacketHandlers() { @Override public void register() { map(Type.BYTE); // Window id handler(wrapper -> { String s = wrapper.read(Type.STRING); Integer id; if (s.length() < 19 || (id = Ints.tryParse(s.substring(18))) == null) { wrapper.cancel(); return; } wrapper.write(Type.VAR_INT, id); }); } }); registerServerbound(ServerboundPackets1_13.RECIPE_BOOK_DATA, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Type handler(wrapper -> { int type = wrapper.get(Type.VAR_INT, 0); if (type == 0) { String s = wrapper.read(Type.STRING); Integer id; // Custom recipes if (s.length() < 19 || (id = Ints.tryParse(s.substring(18))) == null) { wrapper.cancel(); return; } wrapper.write(Type.INT, id); } if (type == 1) { wrapper.passthrough(Type.BOOLEAN); // Crafting Recipe Book Open wrapper.passthrough(Type.BOOLEAN); // Crafting Recipe Filter Active wrapper.read(Type.BOOLEAN); // Smelting Recipe Book Open | IGNORE NEW 1.13 FIELD wrapper.read(Type.BOOLEAN); // Smelting Recipe Filter Active | IGNORE NEW 1.13 FIELD } }); } }); // New 0x1C - Name Item -> Plugin Message registerServerbound(ServerboundPackets1_13.RENAME_ITEM, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> { wrapper.write(Type.STRING, "MC|ItemName"); // Channel }); // New 0x1F - Select Trade -> Plugin Message registerServerbound(ServerboundPackets1_13.SELECT_TRADE, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { wrapper.write(Type.STRING, "MC|TrSel"); // Channel }); map(Type.VAR_INT, Type.INT); // Slot } }); // New 0x20 - Set Beacon Effect -> Plugin Message registerServerbound(ServerboundPackets1_13.SET_BEACON_EFFECT, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { wrapper.write(Type.STRING, "MC|Beacon"); // Channel }); map(Type.VAR_INT, Type.INT); // Primary Effect map(Type.VAR_INT, Type.INT); // Secondary Effect } }); // New 0x22 - Update Command Block -> Plugin Message registerServerbound(ServerboundPackets1_13.UPDATE_COMMAND_BLOCK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> wrapper.write(Type.STRING, "MC|AutoCmd")); handler(POS_TO_3_INT); map(Type.STRING); // Command handler(wrapper -> { int mode = wrapper.read(Type.VAR_INT); byte flags = wrapper.read(Type.BYTE); String stringMode = mode == 0 ? "SEQUENCE" : mode == 1 ? "AUTO" : "REDSTONE"; wrapper.write(Type.BOOLEAN, (flags & 0x1) != 0); // Track output wrapper.write(Type.STRING, stringMode); wrapper.write(Type.BOOLEAN, (flags & 0x2) != 0); // Is conditional wrapper.write(Type.BOOLEAN, (flags & 0x4) != 0); // Automatic }); } }); // New 0x23 - Update Command Block Minecart -> Plugin Message registerServerbound(ServerboundPackets1_13.UPDATE_COMMAND_BLOCK_MINECART, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { wrapper.write(Type.STRING, "MC|AdvCmd"); wrapper.write(Type.BYTE, (byte) 1); // Type 1 for Entity }); map(Type.VAR_INT, Type.INT); // Entity Id } }); // 0x1B -> 0x24 in InventoryPackets // New 0x25 - Update Structure Block -> Message Channel registerServerbound(ServerboundPackets1_13.UPDATE_STRUCTURE_BLOCK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() { @Override public void register() { handler(wrapper -> { wrapper.write(Type.STRING, "MC|Struct"); // Channel }); handler(POS_TO_3_INT); map(Type.VAR_INT, new ValueTransformer(Type.BYTE) { // Action @Override public Byte transform(PacketWrapper wrapper, Integer action) throws Exception { return (byte) (action + 1); } }); // Action map(Type.VAR_INT, new ValueTransformer(Type.STRING) { @Override public String transform(PacketWrapper wrapper, Integer mode) throws Exception { return mode == 0 ? "SAVE" : mode == 1 ? "LOAD" : mode == 2 ? "CORNER" : "DATA"; } }); map(Type.STRING); // Name map(Type.BYTE, Type.INT); // Offset X map(Type.BYTE, Type.INT); // Offset Y map(Type.BYTE, Type.INT); // Offset Z map(Type.BYTE, Type.INT); // Size X map(Type.BYTE, Type.INT); // Size Y map(Type.BYTE, Type.INT); // Size Z map(Type.VAR_INT, new ValueTransformer(Type.STRING) { // Mirror @Override public String transform(PacketWrapper wrapper, Integer mirror) throws Exception { return mirror == 0 ? "NONE" : mirror == 1 ? "LEFT_RIGHT" : "FRONT_BACK"; } }); map(Type.VAR_INT, new ValueTransformer(Type.STRING) { // Rotation @Override public String transform(PacketWrapper wrapper, Integer rotation) throws Exception { return rotation == 0 ? "NONE" : rotation == 1 ? "CLOCKWISE_90" : rotation == 2 ? "CLOCKWISE_180" : "COUNTERCLOCKWISE_90"; } }); map(Type.STRING); handler(wrapper -> { float integrity = wrapper.read(Type.FLOAT); long seed = wrapper.read(Type.VAR_LONG); byte flags = wrapper.read(Type.BYTE); wrapper.write(Type.BOOLEAN, (flags & 0x1) != 0); // Ignore Entities wrapper.write(Type.BOOLEAN, (flags & 0x2) != 0); // Show air wrapper.write(Type.BOOLEAN, (flags & 0x4) != 0); // Show bounding box wrapper.write(Type.FLOAT, integrity); wrapper.write(Type.VAR_LONG, seed); }); } }); } private void writeDeclareRecipes(PacketWrapper recipesPacket) { recipesPacket.write(Type.VAR_INT, RecipeData.recipes.size()); for (Map.Entry entry : RecipeData.recipes.entrySet()) { recipesPacket.write(Type.STRING, entry.getKey()); // Id recipesPacket.write(Type.STRING, entry.getValue().getType()); switch (entry.getValue().getType()) { case "crafting_shapeless": { recipesPacket.write(Type.STRING, entry.getValue().getGroup()); recipesPacket.write(Type.VAR_INT, entry.getValue().getIngredients().length); for (Item[] ingredient : entry.getValue().getIngredients()) { Item[] clone = ingredient.clone(); // Clone because array and item is mutable for (int i = 0; i < clone.length; i++) { if (clone[i] == null) continue; clone[i] = new DataItem(clone[i]); } recipesPacket.write(Type.ITEM1_13_ARRAY, clone); } recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult())); break; } case "crafting_shaped": { recipesPacket.write(Type.VAR_INT, entry.getValue().getWidth()); recipesPacket.write(Type.VAR_INT, entry.getValue().getHeight()); recipesPacket.write(Type.STRING, entry.getValue().getGroup()); for (Item[] ingredient : entry.getValue().getIngredients()) { Item[] clone = ingredient.clone(); // Clone because array and item is mutable for (int i = 0; i < clone.length; i++) { if (clone[i] == null) continue; clone[i] = new DataItem(clone[i]); } recipesPacket.write(Type.ITEM1_13_ARRAY, clone); } recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult())); break; } case "smelting": { recipesPacket.write(Type.STRING, entry.getValue().getGroup()); Item[] clone = entry.getValue().getIngredient().clone(); // Clone because array and item is mutable for (int i = 0; i < clone.length; i++) { if (clone[i] == null) continue; clone[i] = new DataItem(clone[i]); } recipesPacket.write(Type.ITEM1_13_ARRAY, clone); recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult())); recipesPacket.write(Type.FLOAT, entry.getValue().getExperience()); recipesPacket.write(Type.VAR_INT, entry.getValue().getCookingTime()); break; } } } } @Override protected void onMappingDataLoaded() { ConnectionData.init(); RecipeData.init(); BlockIdData.init(); Types1_13.PARTICLE.filler(this) .reader(3, ParticleType.Readers.BLOCK) .reader(20, ParticleType.Readers.DUST) .reader(11, ParticleType.Readers.DUST) .reader(27, ParticleType.Readers.ITEM1_13); if (Via.getConfig().isServersideBlockConnections() && Via.getManager().getProviders().get(BlockConnectionProvider.class) instanceof PacketBlockConnectionProvider) { BlockConnectionStorage.init(); } } @Override public void init(UserConnection userConnection) { userConnection.addEntityTracker(this.getClass(), new EntityTrackerBase(userConnection, EntityTypes1_13.EntityType.PLAYER)); userConnection.put(new TabCompleteTracker()); if (!userConnection.has(ClientWorld.class)) userConnection.put(new ClientWorld()); userConnection.put(new BlockStorage()); if (Via.getConfig().isServersideBlockConnections()) { if (Via.getManager().getProviders().get(BlockConnectionProvider.class) instanceof PacketBlockConnectionProvider) { userConnection.put(new BlockConnectionStorage()); } } } @Override public void register(ViaProviders providers) { providers.register(BlockEntityProvider.class, new BlockEntityProvider()); providers.register(PaintingProvider.class, new PaintingProvider()); providers.register(PlayerLookTargetProvider.class, new PlayerLookTargetProvider()); } // Based on method from https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/ChatColor.java public char getLastColorChar(String input) { int length = input.length(); for (int index = length - 1; index > -1; index--) { char section = input.charAt(index); if (section == ChatColorUtil.COLOR_CHAR && index < length - 1) { char c = input.charAt(index + 1); if (ChatColorUtil.isColorCode(c) && !FORMATTING_CODES.contains(c)) { return c; } } } return 'r'; } protected String rewriteTeamMemberName(String name) { // The Display Name is just colours which overwrites the suffix // It also overwrites for ANY colour in name but most plugins // will just send colour as 'invisible' character if (ChatColorUtil.stripColor(name).isEmpty()) { StringBuilder newName = new StringBuilder(); for (int i = 1; i < name.length(); i += 2) { char colorChar = name.charAt(i); Character rewrite = SCOREBOARD_TEAM_NAME_REWRITE.get(colorChar); if (rewrite == null) { rewrite = colorChar; } newName.append(ChatColorUtil.COLOR_CHAR).append(rewrite); } name = newName.toString(); } return name; } @Override public MappingData getMappingData() { return MAPPINGS; } @Override public MetadataRewriter1_13To1_12_2 getEntityRewriter() { return entityRewriter; } @Override public InventoryPackets getItemRewriter() { return itemRewriter; } public ComponentRewriter1_13 getComponentRewriter() { return componentRewriter; } }