Package simpleserver.stream

Source Code of simpleserver.stream.StreamTunnel$Tunneler

/*
* 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();
        }
      }
    }
  }
}
TOP

Related Classes of simpleserver.stream.StreamTunnel$Tunneler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.