/* * Copyright (c) 2010 SimpleServer authors (see CONTRIBUTORS) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package simpleserver.stream; import static simpleserver.lang.Translations.t; import static simpleserver.util.Util.print; import static simpleserver.util.Util.println; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import simpleserver.Authenticator.AuthRequest; import simpleserver.Color; import simpleserver.Coordinate; import simpleserver.Coordinate.Dimension; import simpleserver.Main; import simpleserver.Player; import simpleserver.Server; import simpleserver.command.PlayerListCommand; import simpleserver.config.data.Chests.Chest; import simpleserver.config.xml.Config.BlockPermission; import simpleserver.message.Message; import simpleserver.message.MessagePacket; public class StreamTunnel { private static final boolean EXPENSIVE_DEBUG_LOGGING = Boolean.getBoolean("EXPENSIVE_DEBUG_LOGGING"); private static final int IDLE_TIME = 30000; private static final int BUFFER_SIZE = 1024; private static final byte BLOCK_DESTROYED_STATUS = 2; private static final Pattern MESSAGE_PATTERN = Pattern.compile("^<([^>]+)> (.*)$"); private static final Pattern COLOR_PATTERN = Pattern.compile("\u00a7[0-9a-z]"); private static final Pattern JOIN_PATTERN = Pattern.compile("\u00a7.((\\d|\\w|\\u00a7)*) (joined|left) the game."); private static final String CONSOLE_CHAT_PATTERN = "\\[Server:.*\\]"; private static final int MESSAGE_SIZE = 360; private static final int MAXIMUM_MESSAGE_SIZE = 119; private final boolean isServerTunnel; private final String streamType; private final Player player; private final Server server; private final byte[] buffer; private final Tunneler tunneler; private DataInput in; private DataOutput out; private InputStream inputStream; private OutputStream outputStream; private StreamDumper inputDumper; private StreamDumper outputDumper; private boolean inGame = false; private volatile long lastRead; private volatile boolean run = true; private Byte lastPacket; private char commandPrefix; public StreamTunnel(InputStream in, OutputStream out, boolean isServerTunnel, Player player) { this.isServerTunnel = isServerTunnel; if (isServerTunnel) { streamType = "ServerStream"; } else { streamType = "PlayerStream"; } this.player = player; server = player.getServer(); commandPrefix = server.options.getBoolean("useSlashes") ? '/' : '!'; inputStream = in; outputStream = out; DataInputStream dIn = new DataInputStream(in); DataOutputStream dOut = new DataOutputStream(out); if (EXPENSIVE_DEBUG_LOGGING) { try { OutputStream dump = new FileOutputStream(streamType + "Input.debug"); InputStreamDumper dumper = new InputStreamDumper(dIn, dump); inputDumper = dumper; this.in = dumper; } catch (FileNotFoundException e) { System.out.println("Unable to open input debug dump!"); throw new RuntimeException(e); } try { OutputStream dump = new FileOutputStream(streamType + "Output.debug"); OutputStreamDumper dumper = new OutputStreamDumper(dOut, dump); outputDumper = dumper; this.out = dumper; } catch (FileNotFoundException e) { System.out.println("Unable to open output debug dump!"); throw new RuntimeException(e); } } else { this.in = dIn; this.out = dOut; } buffer = new byte[BUFFER_SIZE]; tunneler = new Tunneler(); tunneler.start(); lastRead = System.currentTimeMillis(); } public void stop() { run = false; } public boolean isAlive() { return tunneler.isAlive(); } public boolean isActive() { return System.currentTimeMillis() - lastRead < IDLE_TIME || player.isRobot(); } private void handlePacket() throws IOException { Byte packetId = in.readByte(); // System.out.println((isServerTunnel ? "server " : "client ") + // String.format("%02x", packetId)); int x; byte y; int z; byte dimension; Coordinate coordinate; switch (packetId) { case 0x00: // Keep Alive write(packetId); write(in.readInt()); // random number that is returned from server break; case 0x01: // Login Request/Response write(packetId); if (!isServerTunnel) { write(in.readInt()); write(readUTF16()); copyNBytes(5); break; } player.setEntityId(write(in.readInt())); write(readUTF16()); write(in.readByte()); dimension = in.readByte(); if (isServerTunnel) { player.setDimension(Dimension.get(dimension)); } write(dimension); write(in.readByte()); write(in.readByte()); if (isServerTunnel) { in.readByte(); write((byte) server.config.properties.getInt("maxPlayers")); } else { write(in.readByte()); } break; case 0x02: // Handshake byte version = in.readByte(); String name = readUTF16(); boolean nameSet = false; if (name.contains(";")) { name = name.substring(0, name.indexOf(";")); } if (name.equals("Player") || !server.authenticator.isMinecraftUp) { AuthRequest req = server.authenticator.getAuthRequest(player.getIPAddress()); if (req != null) { name = req.playerName; nameSet = server.authenticator.completeLogin(req, player); } if (req == null || !nameSet) { if (!name.equals("Player")) { player.addTMessage(Color.RED, "Login verification failed."); player.addTMessage(Color.RED, "You were logged in as guest."); } name = server.authenticator.getFreeGuestName(); player.setGuest(true); nameSet = player.setName(name); } } else { nameSet = player.setName(name); if (nameSet) { player.updateRealName(name); } } if (player.isGuest() && !server.authenticator.allowGuestJoin()) { player.kick(t("Failed to login: User not authenticated")); nameSet = false; } tunneler.setName(streamType + "-" + player.getName()); write(packetId); write(version); write(player.getName()); write(readUTF16()); write(in.readInt()); break; case 0x03: // Chat Message String message = readUTF16(); MessagePacket messagePacket = new Message().decodeMessage(message); if (messagePacket == null) { // we are raw text, handle as such if (isServerTunnel && server.config.properties.getBoolean("useMsgFormats")) { if (server.config.properties.getBoolean("forwardChat") && server.getMessager().wasForwarded(message)) { break; } Matcher colorMatcher = COLOR_PATTERN.matcher(message); String cleanMessage = colorMatcher.replaceAll(""); Matcher messageMatcher = MESSAGE_PATTERN.matcher(cleanMessage); if (messageMatcher.find()) { } else if (cleanMessage.matches(CONSOLE_CHAT_PATTERN) && !server.config.properties.getBoolean("chatConsoleToOps")) { break; } if (server.config.properties.getBoolean("msgWrap")) { sendMessage(message); } else { if (message.length() > MAXIMUM_MESSAGE_SIZE) { //message = message.substring(0, MAXIMUM_MESSAGE_SIZE); } write(packetId); write(message); } } else if (!isServerTunnel) { if (player.isMuted() && !message.startsWith("/") && !message.startsWith("!")) { player.addTMessage(Color.RED, "You are muted! You may not send messages to all players."); break; } if (message.charAt(0) == commandPrefix) { message = player.parseCommand(message, false); if (message == null) { break; } write(packetId); write(message); return; } player.sendMessage(message); } } else { // we have a json object if (messagePacket.isJoinedPacket()) { String username = messagePacket.getJoinedUsername(); if (isServerTunnel) { if (server.bots.ninja(username)) { break; } if (message.contains("join")) { player.addTMessage(Color.YELLOW, "%s joined the game.", username); } else { player.addTMessage(Color.YELLOW, "%s left the game.", username); } break; } } else { write(packetId); write(message); } } break; case 0x04: // Time Update write(packetId); write(in.readLong()); long time = in.readLong(); server.setTime(time); write(time); break; case 0x05: // Player Inventory write(packetId); write(in.readInt()); write(in.readShort()); copyItem(); break; case 0x06: // Spawn Position write(packetId); copyNBytes(12); if (server.options.getBoolean("enableEvents")) { server.eventhost.execute(server.eventhost.findEvent("onPlayerConnect"), player, true, null); } break; case 0x07: // Use Entity int user = in.readInt(); int target = in.readInt(); Player targetPlayer = server.playerList.findPlayer(target); if (targetPlayer != null) { if (targetPlayer.godModeEnabled()) { in.readBoolean(); break; } } write(packetId); write(user); write(target); copyNBytes(1); break; case 0x08: // Update Health write(packetId); player.updateHealth(write(in.readFloat())); player.getHealth(); write(in.readShort()); write(in.readFloat()); break; case 0x09: // Respawn write(packetId); if (!isServerTunnel) { break; } player.setDimension(Dimension.get(write(in.readInt()))); write(in.readByte()); write(in.readByte()); write(in.readShort()); write(readUTF16()); // Added in 1.1 (level type) if (server.options.getBoolean("enableEvents") && isServerTunnel) { server.eventhost.execute(server.eventhost.findEvent("onPlayerRespawn"), player, true, null); } break; case 0x0a: // Player write(packetId); copyNBytes(1); if (!inGame && !isServerTunnel) { player.sendMOTD(); if (server.config.properties.getBoolean("showListOnConnect")) { // display player list if enabled in config player.execute(PlayerListCommand.class); } inGame = true; } break; case 0x0b: // Player Position write(packetId); copyPlayerLocation(); copyNBytes(1); break; case 0x0c: // Player Look write(packetId); copyPlayerLook(); copyNBytes(1); break; case 0x0d: // Player Position & Look write(packetId); copyPlayerLocation(); copyPlayerLook(); copyNBytes(1); break; case 0x0e: // Player Digging if (!isServerTunnel) { byte status = in.readByte(); x = in.readInt(); y = in.readByte(); z = in.readInt(); byte face = in.readByte(); coordinate = new Coordinate(x, y, z, player); if (!player.getGroup().ignoreAreas) { BlockPermission perm = server.config.blockPermission(player, coordinate); if (!perm.use && status == 0) { player.addTMessage(Color.RED, "You can not use this block here!"); break; } if (!perm.destroy && status == BLOCK_DESTROYED_STATUS) { player.addTMessage(Color.RED, "You can not destroy this block!"); break; } } boolean locked = server.data.chests.isLocked(coordinate); if (!locked || player.ignoresChestLocks() || server.data.chests.canOpen(player, coordinate)) { if (locked && status == BLOCK_DESTROYED_STATUS) { server.data.chests.releaseLock(coordinate); server.data.save(); } write(packetId); write(status); write(x); write(y); write(z); write(face); if (player.instantDestroyEnabled()) { packetFinished(); write(packetId); write(BLOCK_DESTROYED_STATUS); write(x); write(y); write(z); write(face); } if (status == BLOCK_DESTROYED_STATUS) { player.destroyedBlock(); } } } else { write(packetId); copyNBytes(11); } break; case 0x0f: // Player Block Placement x = in.readInt(); y = in.readByte(); z = in.readInt(); coordinate = new Coordinate(x, y, z, player); final byte direction = in.readByte(); final short dropItem = in.readShort(); byte itemCount = 0; short uses = 0; byte[] data = null; if (dropItem != -1) { itemCount = in.readByte(); uses = in.readShort(); short dataLength = in.readShort(); if (dataLength != -1) { data = new byte[dataLength]; in.readFully(data); } } byte blockX = in.readByte(); byte blockY = in.readByte(); byte blockZ = in.readByte(); boolean writePacket = true; boolean drop = false; BlockPermission perm = server.config.blockPermission(player, coordinate, dropItem); if (server.options.getBoolean("enableEvents")) { player.checkButtonEvents(new Coordinate(x + (x < 0 ? 1 : 0), y + 1, z + (z < 0 ? 1 : 0))); } if (isServerTunnel || server.data.chests.isChest(coordinate)) { // continue } else if (!player.getGroup().ignoreAreas && ((dropItem != -1 && !perm.place) || !perm.use)) { if (!perm.use) { player.addTMessage(Color.RED, "You can not use this block here!"); } else { player.addTMessage(Color.RED, "You can not place this block here!"); } writePacket = false; drop = true; } else if (dropItem == 54) { int xPosition = x; byte yPosition = y; int zPosition = z; switch (direction) { case 0: --yPosition; break; case 1: ++yPosition; break; case 2: --zPosition; break; case 3: ++zPosition; break; case 4: --xPosition; break; case 5: ++xPosition; break; } Coordinate targetBlock = new Coordinate(xPosition, yPosition, zPosition, player); Chest adjacentChest = server.data.chests.adjacentChest(targetBlock); if (adjacentChest != null && !adjacentChest.isOpen() && !adjacentChest.ownedBy(player)) { player.addTMessage(Color.RED, "The adjacent chest is locked!"); writePacket = false; drop = true; } else { player.placingChest(targetBlock); } } if (writePacket) { write(packetId); write(x); write(y); write(z); write(direction); write(dropItem); if (dropItem != -1) { write(itemCount); write(uses); if (data != null) { write((short) data.length); out.write(data); } else { write((short) -1); } if (dropItem <= 94 && direction >= 0) { player.placedBlock(); } } write(blockX); write(blockY); write(blockZ); player.openingChest(coordinate); } else if (drop) { // Drop the item in hand. This keeps the client state in-sync with the // server. This generally prevents empty-hand clicks by the client // from placing blocks the server thinks the client has in hand. write((byte) 0x0e); write((byte) 0x04); write(x); write(y); write(z); write(direction); } break; case 0x10: // Holding Change write(packetId); copyNBytes(2); break; case 0x11: // Use Bed write(packetId); copyNBytes(14); break; case 0x12: // Animation write(packetId); copyNBytes(5); break; case 0x13: // Entity Action write(packetId); write(in.readInt()); write(in.readByte()); write(in.readInt()); break; case 0x14: // Named Entity Spawn int eid = in.readInt(); name = readUTF16(); if (!server.bots.ninja(name)) { write(packetId); write(eid); write(name); copyNBytes(16); copyUnknownBlob(); } else { skipNBytes(16); skipUnknownBlob(); } break; case 0x16: // Collect Item write(packetId); copyNBytes(8); break; case 0x17: // Add Object/Vehicle write(packetId); write(in.readInt()); write(in.readByte()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readByte()); write(in.readByte()); int flag = in.readInt(); write(flag); if (flag > 0) { write(in.readShort()); write(in.readShort()); write(in.readShort()); } break; case 0x18: // Mob Spawn write(packetId); write(in.readInt()); write(in.readByte()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readByte()); write(in.readByte()); write(in.readByte()); write(in.readShort()); write(in.readShort()); write(in.readShort()); copyUnknownBlob(); break; case 0x19: // Entity: Painting write(packetId); write(in.readInt()); write(readUTF16()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readInt()); break; case 0x1a: // Experience Orb write(packetId); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readShort()); break; case 0x1b: // Steer Vehicle write(packetId); write(in.readFloat()); write(in.readFloat()); write(in.readBoolean()); write(in.readBoolean()); break; case 0x1c: // Entity Velocity write(packetId); copyNBytes(10); break; case 0x1d: // Destroy Entity write(packetId); byte destoryCount = write(in.readByte()); if (destoryCount > 0) { copyNBytes(destoryCount * 4); } break; case 0x1e: // Entity write(packetId); copyNBytes(4); break; case 0x1f: // Entity Relative Move write(packetId); copyNBytes(7); break; case 0x20: // Entity Look write(packetId); copyNBytes(6); break; case 0x21: // Entity Look and Relative Move write(packetId); copyNBytes(9); break; case 0x22: // Entity Teleport write(packetId); copyNBytes(18); break; case 0x23: // Entitiy Look write(packetId); write(in.readInt()); write(in.readByte()); break; case 0x26: // Entity Status write(packetId); copyNBytes(5); break; case 0x27: // Attach Entity write(packetId); write(in.readInt()); write(in.readInt()); write(in.readBoolean()); break; case 0x28: // Entity Metadata write(packetId); write(in.readInt()); copyUnknownBlob(); break; case 0x29: // Entity Effect write(packetId); write(in.readInt()); write(in.readByte()); write(in.readByte()); write(in.readShort()); break; case 0x2a: // Remove Entity Effect write(packetId); write(in.readInt()); write(in.readByte()); break; case 0x2b: // Experience write(packetId); player.updateExperience(write(in.readFloat()), write(in.readShort()), write(in.readShort())); break; case 0x2c: // Entity Properties write(packetId); write(in.readInt()); int properties_count = in.readInt(); short list_length = 0; write(properties_count); // loop for every property key/value pair for (int i = 0; i < properties_count; i++) { write(readUTF16()); write(in.readDouble()); // grab list elements list_length = in.readShort(); write(list_length); if (list_length > 0) { for (int k = 0; k < list_length; k++) { write(in.readLong()); write(in.readLong()); write(in.readDouble()); write(in.readByte()); } } } break; case 0x33: // Map Chunk write(packetId); write(in.readInt()); write(in.readInt()); write(in.readBoolean()); write(in.readShort()); write(in.readShort()); copyNBytes(write(in.readInt())); break; case 0x34: // Multi Block Change write(packetId); write(in.readInt()); write(in.readInt()); write(in.readShort()); copyNBytes(write(in.readInt())); break; case 0x35: // Block Change write(packetId); x = in.readInt(); y = in.readByte(); z = in.readInt(); short blockType = in.readShort(); byte metadata = in.readByte(); coordinate = new Coordinate(x, y, z, player); if (blockType == 54 && player.placedChest(coordinate)) { lockChest(coordinate); player.placingChest(null); } write(x); write(y); write(z); write(blockType); write(metadata); break; case 0x36: // Block Action write(packetId); copyNBytes(14); break; case 0x37: // Mining progress write(packetId); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readByte()); break; case 0x38: // Chunk Bulk write(packetId); short chunkCount = in.readShort(); int dataLength = in.readInt(); write(chunkCount); write(dataLength); write(in.readBoolean()); copyNBytes(chunkCount * 12 + dataLength); break; case 0x3c: // Explosion write(packetId); copyNBytes(28); int recordCount = in.readInt(); write(recordCount); copyNBytes(recordCount * 3); write(in.readFloat()); write(in.readFloat()); write(in.readFloat()); break; case 0x3d: // Sound/Particle Effect write(packetId); write(in.readInt()); write(in.readInt()); write(in.readByte()); write(in.readInt()); write(in.readInt()); write(in.readByte()); break; case 0x3e: // Named Sound/Particle Effect write(packetId); write(readUTF16()); write(in.readInt()); write(in.readInt()); write(in.readInt()); write(in.readFloat()); write(in.readByte()); break; case 0x3f: // particle write(packetId); write(readUTF16()); // name of particle write(in.readFloat()); // x write(in.readFloat()); // y write(in.readFloat()); // z write(in.readFloat()); // offset x write(in.readFloat()); // offset y write(in.readFloat()); // offset z write(in.readFloat()); // particle speed write(in.readInt()); // num of particles break; case 0x46: // New/Invalid State write(packetId); write(in.readByte()); write(in.readByte()); break; case 0x47: // Thunderbolt write(packetId); copyNBytes(17); break; case 0x64: // Open Window boolean allow = true; byte id = in.readByte(); byte invtype = in.readByte(); String title = readUTF16(); byte number = in.readByte(); boolean provided = in.readBoolean(); int unknown = 0; if (invtype == 11) { unknown = in.readInt(); } if (invtype == 0) { Chest adjacent = server.data.chests.adjacentChest(player.openedChest()); if (!server.data.chests.isChest(player.openedChest())) { if (adjacent == null) { server.data.chests.addOpenChest(player.openedChest()); } else { server.data.chests.giveLock(adjacent.owner, player.openedChest(), adjacent.name); } server.data.save(); } if (!player.getGroup().ignoreAreas && (!server.config.blockPermission(player, player.openedChest()).chest || (adjacent != null && !server.config.blockPermission(player, adjacent.coordinate).chest))) { player.addTMessage(Color.RED, "You can't use chests here"); allow = false; } else if (server.data.chests.canOpen(player, player.openedChest()) || player.ignoresChestLocks()) { if (server.data.chests.isLocked(player.openedChest())) { if (player.isAttemptingUnlock()) { server.data.chests.unlock(player.openedChest()); server.data.save(); player.setAttemptedAction(null); player.addTMessage(Color.RED, "This chest is no longer locked!"); title = t("Open Chest"); } else { title = server.data.chests.chestName(player.openedChest()); } } else { title = t("Open Chest"); if (player.isAttemptLock()) { lockChest(player.openedChest()); title = (player.nextChestName() == null) ? t("Locked Chest") : player.nextChestName(); } } } else { player.addTMessage(Color.RED, "This chest is locked!"); allow = false; } } if (!allow) { write((byte) 0x65); write(id); } else { write(packetId); write(id); write(invtype); write(title); write(number); write(provided); if (invtype == 11) { write(unknown); } } break; case 0x65: // Close Window write(packetId); write(in.readByte()); break; case 0x66: // Window Click write(packetId); write(in.readByte()); write(in.readShort()); write(in.readByte()); write(in.readShort()); write(in.readByte()); copyItem(); break; case 0x67: // Set Slot write(packetId); write(in.readByte()); write(in.readShort()); copyItem(); break; case 0x68: // Window Items write(packetId); write(in.readByte()); short count = write(in.readShort()); for (int c = 0; c < count; ++c) { copyItem(); } break; case 0x69: // Update Window Property write(packetId); write(in.readByte()); write(in.readShort()); write(in.readShort()); break; case 0x6a: // Transaction write(packetId); write(in.readByte()); write(in.readShort()); write(in.readByte()); break; case 0x6b: // Creative Inventory Action write(packetId); write(in.readShort()); copyItem(); break; case 0x6c: // Enchant Item write(packetId); write(in.readByte()); write(in.readByte()); break; case (byte) 0x82: // Update Sign write(packetId); write(in.readInt()); write(in.readShort()); write(in.readInt()); write(readUTF16()); write(readUTF16()); write(readUTF16()); write(readUTF16()); break; case (byte) 0x83: // Item Data write(packetId); write(in.readShort()); write(in.readShort()); short length = in.readShort(); write(length); copyNBytes(length); break; case (byte) 0x84: // added in 12w06a write(packetId); write(in.readInt()); write(in.readShort()); write(in.readInt()); write(in.readByte()); short nbtLength = write(in.readShort()); if (nbtLength > 0) { copyNBytes(nbtLength); } break; case (byte) 0x85: // Unknown (Signs?) write(packetId); write(in.readByte()); write(in.readInt()); write(in.readInt()); write(in.readInt()); break; case (byte) 0xc3: // BukkitContrib write(packetId); write(in.readInt()); copyNBytes(write(in.readInt())); break; case (byte) 0xc8: // Increment Statistic write(packetId); write(in.readInt()); write(in.readInt()); break; case (byte) 0xc9: // Player List Item write(packetId); write(readUTF16()); write(in.readByte()); write(in.readShort()); break; case (byte) 0xca: // Player Abilities write(packetId); byte flags = in.readByte(); int creative = flags & 1; int flying = flags & 2; int can_fly = flags & 4; int god_mode = flags & 8; write(flags); write(in.readFloat()); write(in.readFloat()); break; case (byte) 0xcb: // Tab-Completion write(packetId); write(readUTF16()); break; case (byte) 0xcc: // Locale and View Distance write(packetId); write(readUTF16()); write(in.readByte()); write(in.readByte()); write(in.readByte()); write(in.readBoolean()); break; case (byte) 0xcd: // Login & Respawn write(packetId); write(in.readByte()); break; case (byte) 0xce: // scoreboard objectives write(packetId); write(readUTF16()); write(readUTF16()); write(in.readByte()); break; case (byte) 0xcf: // update score write(packetId); write(readUTF16()); byte updateRemove = in.readByte(); write(updateRemove); if (updateRemove != 1) { write(readUTF16()); write(in.readInt()); } break; case (byte) 0xd0: // display scoreboard write(packetId); write(in.readByte()); write(readUTF16()); break; case (byte) 0xd1: // teams write(packetId); write(readUTF16()); byte mode = in.readByte(); short playerCount = -1; write(mode); if (mode == 0 || mode == 2) { write(readUTF16()); // team display name write(readUTF16()); // team prefix write(readUTF16()); // team suffix write(in.readByte()); // friendly fire } // only ran if 0,3,4 if (mode == 0 || mode == 3 || mode == 4) { playerCount = in.readShort(); write(playerCount); if (playerCount != -1) { for (int i = 0; i < playerCount; i++) { write(readUTF16()); } } } break; case (byte) 0xd3: // Red Power (mod by Eloraam) write(packetId); copyNBytes(1); copyVLC(); copyVLC(); copyVLC(); copyNBytes((int) copyVLC()); break; case (byte) 0xe6: // ModLoaderMP by SDK write(packetId); write(in.readInt()); // mod write(in.readInt()); // packet id copyNBytes(write(in.readInt()) * 4); // ints copyNBytes(write(in.readInt()) * 4); // floats copyNBytes(write(in.readInt()) * 8); // doubles int sizeString = write(in.readInt()); // strings for (int i = 0; i < sizeString; i++) { copyNBytes(write(in.readInt())); } break; case (byte) 0xfa: // Plugin Message write(packetId); write(readUTF16()); copyNBytes(write(in.readShort())); break; case (byte) 0xfc: // Encryption Key Response byte[] sharedKey = new byte[in.readShort()]; in.readFully(sharedKey); byte[] challengeTokenResponse = new byte[in.readShort()]; in.readFully(challengeTokenResponse); if (!isServerTunnel) { if (!player.clientEncryption.checkChallengeToken(challengeTokenResponse)) { player.kick("Invalid client response"); break; } player.clientEncryption.setEncryptedSharedKey(sharedKey); sharedKey = player.serverEncryption.getEncryptedSharedKey(); } if (!isServerTunnel && server.authenticator.useCustAuth(player) && !server.authenticator.onlineAuthenticate(player)) { player.kick(t("%s Failed to login: User not premium", "[CustAuth]")); break; } write(packetId); write((short) sharedKey.length); write(sharedKey); challengeTokenResponse = player.serverEncryption.encryptChallengeToken(); write((short) challengeTokenResponse.length); write(challengeTokenResponse); if (isServerTunnel) { in = new DataInputStream(new BufferedInputStream(player.serverEncryption.encryptedInputStream(inputStream))); out = new DataOutputStream(new BufferedOutputStream(player.clientEncryption.encryptedOutputStream(outputStream))); } else { in = new DataInputStream(new BufferedInputStream(player.clientEncryption.encryptedInputStream(inputStream))); out = new DataOutputStream(new BufferedOutputStream(player.serverEncryption.encryptedOutputStream(outputStream))); } break; case (byte) 0xfd: // Encryption Key Request (server -> client) tunneler.setName(streamType + "-" + player.getName()); write(packetId); String serverId = readUTF16(); if (!server.authenticator.useCustAuth(player)) { serverId = "-"; } else { serverId = player.getConnectionHash(); } write(serverId); byte[] keyBytes = new byte[in.readShort()]; in.readFully(keyBytes); byte[] challengeToken = new byte[in.readShort()]; in.readFully(challengeToken); player.serverEncryption.setPublicKey(keyBytes); byte[] key = player.clientEncryption.getPublicKey(); write((short) key.length); write(key); write((short) challengeToken.length); write(challengeToken); player.serverEncryption.setChallengeToken(challengeToken); player.clientEncryption.setChallengeToken(challengeToken); break; case (byte) 0xfe: // Server List Ping write(packetId); write(in.readByte()); break; case (byte) 0xff: // Disconnect/Kick write(packetId); String reason = readUTF16(); if (reason.startsWith("\u00a71")) { reason = String.format("\u00a71\0%s\0%s\0%s\0%s\0%s", Main.protocolVersion, Main.minecraftVersion, server.config.properties.get("serverDescription"), server.playerList.size(), server.config.properties.getInt("maxPlayers")); } write(reason); if (reason.startsWith("Took too long")) { server.addRobot(player); } player.close(); break; default: if (EXPENSIVE_DEBUG_LOGGING) { while (true) { skipNBytes(1); flushAll(); } } else { if (lastPacket != null) { throw new IOException("Unable to parse unknown " + streamType + " packet 0x" + Integer.toHexString(packetId) + " for player " + player.getName() + " (after 0x" + Integer.toHexString(lastPacket)); } else { throw new IOException("Unable to parse unknown " + streamType + " packet 0x" + Integer.toHexString(packetId) + " for player " + player.getName()); } } } packetFinished(); lastPacket = (packetId == 0x00) ? lastPacket : packetId; } private void copyItem() throws IOException { if (write(in.readShort()) > 0) { write(in.readByte()); write(in.readShort()); short length; if ((length = write(in.readShort())) > 0) { copyNBytes(length); } } } private void skipItem() throws IOException { if (in.readShort() > 0) { in.readByte(); in.readShort(); short length; if ((length = in.readShort()) > 0) { skipNBytes(length); } } } private long copyVLC() throws IOException { long value = 0; int shift = 0; while (true) { int i = write(in.readByte()); value |= (i & 0x7F) << shift; if ((i & 0x80) == 0) { break; } shift += 7; } return value; } private String readUTF16() throws IOException { short length = in.readShort(); StringBuilder string = new StringBuilder(); for (int i = 0; i < length; i++) { string.append(in.readChar()); } return string.toString(); } private void lockChest(Coordinate coordinate) { Chest adjacentChest = server.data.chests.adjacentChest(coordinate); if (player.isAttemptLock() || adjacentChest != null && !adjacentChest.isOpen()) { if (adjacentChest != null && !adjacentChest.isOpen()) { server.data.chests.giveLock(adjacentChest.owner, coordinate, adjacentChest.name); } else { if (adjacentChest != null) { adjacentChest.lock(player); adjacentChest.name = player.nextChestName(); } server.data.chests.giveLock(player, coordinate, player.nextChestName()); } player.setAttemptedAction(null); player.addTMessage(Color.GRAY, "This chest is now locked."); } else if (!server.data.chests.isChest(coordinate)) { server.data.chests.addOpenChest(coordinate); } server.data.save(); } private void copyPlayerLocation() throws IOException { double x = in.readDouble(); double y = in.readDouble(); double stance = in.readDouble(); double z = in.readDouble(); player.position.updatePosition(x, y, z, stance); if (server.options.getBoolean("enableEvents")) { player.checkLocationEvents(); } write(x); write(y); write(stance); write(z); } private void copyPlayerLook() throws IOException { float yaw = in.readFloat(); float pitch = in.readFloat(); player.position.updateLook(yaw, pitch); write(yaw); write(pitch); } private void copyUnknownBlob() throws IOException { byte item = in.readByte(); write(item); while (item != 0x7f) { int type = (item & 0xE0) >> 5; switch (type) { case 0: write(in.readByte()); break; case 1: write(in.readShort()); break; case 2: write(in.readInt()); break; case 3: write(in.readFloat()); break; case 4: write(readUTF16()); break; case 5: copyItem(); break; case 6: write(in.readInt()); write(in.readInt()); write(in.readInt()); } item = in.readByte(); write(item); } } private void skipUnknownBlob() throws IOException { byte item = in.readByte(); while (item != 0x7f) { int type = (item & 0xE0) >> 5; switch (type) { case 0: in.readByte(); break; case 1: in.readShort(); break; case 2: in.readInt(); break; case 3: in.readFloat(); break; case 4: readUTF16(); break; case 5: skipItem(); break; case 6: in.readInt(); in.readInt(); in.readInt(); } item = in.readByte(); } } private byte write(byte b) throws IOException { out.writeByte(b); return b; } private byte[] write(byte[] b) throws IOException { out.write(b); return b; } private short write(short s) throws IOException { out.writeShort(s); return s; } private int write(int i) throws IOException { out.writeInt(i); return i; } private long write(long l) throws IOException { out.writeLong(l); return l; } private float write(float f) throws IOException { out.writeFloat(f); return f; } private double write(double d) throws IOException { out.writeDouble(d); return d; } private String write(String s) throws IOException { write((short) s.length()); out.writeChars(s); return s; } private boolean write(boolean b) throws IOException { out.writeBoolean(b); return b; } private void skipNBytes(int bytes) throws IOException { int overflow = bytes / buffer.length; for (int c = 0; c < overflow; ++c) { in.readFully(buffer, 0, buffer.length); } in.readFully(buffer, 0, bytes % buffer.length); } private void copyNBytes(int bytes) throws IOException { int overflow = bytes / buffer.length; for (int c = 0; c < overflow; ++c) { in.readFully(buffer, 0, buffer.length); out.write(buffer, 0, buffer.length); } in.readFully(buffer, 0, bytes % buffer.length); out.write(buffer, 0, bytes % buffer.length); } private void kick(String reason) throws IOException { write((byte) 0xff); write(reason); packetFinished(); } private String getLastColorCode(String message) { String colorCode = ""; int lastIndex = message.lastIndexOf('\u00a7'); if (lastIndex != -1 && lastIndex + 1 < message.length()) { colorCode = message.substring(lastIndex, lastIndex + 2); } return colorCode; } private void sendMessage(String message) throws IOException { if (message.length() > 0) { int end = message.length(); if (message.charAt(end - 1) == '\u00a7') { end--; } sendMessagePacket(message.substring(0, end)); } } private void sendMessagePacket(String message) throws IOException { if (message.length() > 0) { write((byte) 0x03); write(message); packetFinished(); } } private void packetFinished() throws IOException { if (EXPENSIVE_DEBUG_LOGGING) { inputDumper.packetFinished(); outputDumper.packetFinished(); } } private void flushAll() throws IOException { try { ((OutputStream) out).flush(); } finally { if (EXPENSIVE_DEBUG_LOGGING) { inputDumper.flush(); } } } private final class Tunneler extends Thread { @Override public void run() { try { while (run) { lastRead = System.currentTimeMillis(); try { handlePacket(); if (isServerTunnel) { while (player.hasMessages()) { sendMessage(player.getMessage()); } } else { while (player.hasForwardMessages()) { sendMessage(player.getForwardMessage()); } } flushAll(); } catch (IOException e) { if (run && !player.isRobot()) { println(e); print(streamType + " error handling traffic for " + player.getIPAddress()); if (lastPacket != null) { System.out.print(" (" + Integer.toHexString(lastPacket) + ")"); } System.out.println(); } break; } } try { if (player.isKicked()) { kick(player.getKickMsg()); } flushAll(); } catch (IOException e) { } } finally { if (EXPENSIVE_DEBUG_LOGGING) { inputDumper.cleanup(); outputDumper.cleanup(); } } } } }