From efb49fd05d21df0f74be0e043db13c9d23cc499e Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 26 May 2020 11:20:12 +0200 Subject: [PATCH] Improve documentation, fix file encoding (#1026) --- MinecraftClient/ChatBot.cs | 43 +- MinecraftClient/Commands/Animation.cs | 2 +- MinecraftClient/McTcpClient.cs | 3164 ++++++++--------- .../Protocol/Handlers/Protocol16.cs | 2 + .../Protocol/Handlers/Protocol18.cs | 2848 +++++++-------- 5 files changed, 3037 insertions(+), 3022 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index b68c6157..99306882 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -660,15 +660,6 @@ namespace MinecraftClient return Handler.GetEntityHandlingEnabled(); } - public bool GetInventoryEnabled() - { - return Handler.GetInventoryEnabled(); - } - public Dictionary GetInventories() - { - return Handler.GetInventories(); - } - /// /// start Sneaking /// @@ -900,10 +891,10 @@ namespace MinecraftClient } /// - /// Plays animation + /// Plays animation (Player arm swing) /// - /// <0|1> - /// TRUE animation done + /// 0 for left arm, 1 for right arm + /// TRUE if animation successfully done protected bool SendAnimation(int animation) { return Handler.DoAnimation(animation); @@ -919,7 +910,16 @@ namespace MinecraftClient } /// - /// Get a copy of the player's inventory + /// Check inventory handling enable status + /// + /// + public bool GetInventoryEnabled() + { + return Handler.GetInventoryEnabled(); + } + + /// + /// Get the player's inventory. Do not write to it, will not have any effect server-side. /// /// Player inventory protected Container GetPlayerInventory() @@ -929,15 +929,28 @@ namespace MinecraftClient } /// - /// Change player selected hotbar + /// Get all inventories, player and container(s). Do not write to them. Will not have any effect server-side. /// - /// + /// All inventories + public Dictionary GetInventories() + { + return Handler.GetInventories(); + } + + /// + /// Change player selected hotbar slot + /// + /// 0-8 /// True if success protected bool ChangeSlot(short slot) { return Handler.ChangeSlot(slot); } + /// + /// Get current player selected hotbar slot + /// + /// 0-8 protected byte GetCurrentSlot() { return Handler.GetCurrentSlot(); diff --git a/MinecraftClient/Commands/Animation.cs b/MinecraftClient/Commands/Animation.cs index 8ee3c195..ab777f01 100644 --- a/MinecraftClient/Commands/Animation.cs +++ b/MinecraftClient/Commands/Animation.cs @@ -8,7 +8,7 @@ namespace MinecraftClient.Commands public class Animation : Command { public override string CMDName { get { return "animation"; } } - public override string CMDDesc { get { return "animation <|<0|1>>"; } } + public override string CMDDesc { get { return "animation "; } } public override string Run(McTcpClient handler, string command, Dictionary localVars) { diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 742005e9..b4be8f42 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -1,1584 +1,1584 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Net.Sockets; -using System.Threading; -using System.IO; -using System.Net; -using MinecraftClient.ChatBots; -using MinecraftClient.Protocol; -using MinecraftClient.Proxy; -using MinecraftClient.Protocol.Handlers.Forge; -using MinecraftClient.Mapping; -using MinecraftClient.Inventory; - -namespace MinecraftClient -{ - /// - /// The main client class, used to connect to a Minecraft server. - /// - public class McTcpClient : IMinecraftComHandler - { - public static int ReconnectionAttemptsLeft = 0; - - private static readonly List cmd_names = new List(); - private static readonly Dictionary cmds = new Dictionary(); - private readonly Dictionary onlinePlayers = new Dictionary(); - - private readonly List bots = new List(); - private static readonly List botsOnHold = new List(); - private static Dictionary inventories = new Dictionary(); - - private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); - private readonly List registeredServerPluginChannels = new List(); - - private bool terrainAndMovementsEnabled; - private bool terrainAndMovementsRequested = false; - private bool inventoryHandlingEnabled; - private bool inventoryHandlingRequested = false; - private bool entityHandlingEnabled; - - private object locationLock = new object(); - private bool locationReceived = false; - private World world = new World(); - private Queue steps; - private Queue path; - private Location location; - private float? yaw; - private float? pitch; - private double motionY; - - private string host; - private int port; - private string username; - private string uuid; - private string sessionid; - private DateTime lastKeepAlive; - private object lastKeepAliveLock = new object(); - private int respawnTicks = 0; - - private int playerEntityID; - - // player health and hunger - private float playerHealth; - private int playerFoodSaturation; - private byte CurrentSlot = 0; - - // Entity handling - private Dictionary entities = new Dictionary(); - - // server TPS - private long lastAge = 0; - private DateTime lastTime; - private Double serverTPS = 0; - - public int GetServerPort() { return port; } - public string GetServerHost() { return host; } - public string GetUsername() { return username; } - public string GetUserUUID() { return uuid; } - public string GetSessionID() { return sessionid; } - public Location GetCurrentLocation() { return location; } - public World GetWorld() { return world; } - public Double GetServerTPS() { return serverTPS; } - public float GetHealth() { return playerHealth; } - public int GetSaturation() { return playerFoodSaturation; } - public byte GetCurrentSlot() { return CurrentSlot; } - - // get bots list for unloading them by commands - public List GetLoadedChatBots() - { - return bots; - } - - TcpClient client; - IMinecraftCom handler; - Thread cmdprompt; - Thread timeoutdetector; - - /// - /// Starts the main chat client - /// - /// The chosen username of a premium Minecraft Account - /// The player's UUID for online-mode authentication - /// A valid sessionID obtained after logging in - /// The server IP - /// The server port to use - /// Minecraft protocol version to use - public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port) - { - StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, ""); - } - - /// - /// Starts the main chat client in single command sending mode - /// - /// The chosen username of a premium Minecraft Account - /// The player's UUID for online-mode authentication - /// A valid sessionID obtained after logging in - /// The server IP - /// The server port to use - /// Minecraft protocol version to use - /// The text or command to send. - public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command) - { - StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command); - } - - /// - /// Starts the main chat client, wich will login to the server using the MinecraftCom class. - /// - /// The chosen username of a premium Minecraft Account - /// A valid sessionID obtained with MinecraftCom.GetLogin() - /// The server IP - /// The server port to use - /// Minecraft protocol version to use - /// The player's UUID for online-mode authentication - /// If set to true, the client will send a single command and then disconnect from the server - /// The text or command to send. Will only be sent if singlecommand is set to true. - private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command) - { - terrainAndMovementsEnabled = Settings.TerrainAndMovements; - inventoryHandlingEnabled = Settings.InventoryHandling; - entityHandlingEnabled = Settings.EntityHandling; - - if (inventoryHandlingEnabled) - { - inventories.Clear(); - inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); - } - - bool retry = false; - this.sessionid = sessionID; - this.uuid = uuid; - this.username = user; - this.host = server_ip; - this.port = port; - - if (!singlecommand) - { - if (botsOnHold.Count == 0) - { - if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); } - if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); } - if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); } - if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.ExpandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } - if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.ExpandVars(Settings.PlayerLog_File))); } - if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } - if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ExpandVars(Settings.ScriptScheduler_TasksFile))); } - if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } - if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } - if (Settings.AutoAttack_Enabled) { BotLoad(new ChatBots.AutoAttack()); } - if (Settings.AutoFishing_Enabled) { BotLoad(new ChatBots.AutoFishing()); } - if (Settings.AutoEat_Enabled) { BotLoad(new ChatBots.AutoEat(Settings.AutoEat_hungerThreshold)); } - - //Add your ChatBot here by uncommenting and adapting - //BotLoad(new ChatBots.YourBot()); - } - } - - try - { - client = ProxyHandler.newTcpClient(host, port); - client.ReceiveBufferSize = 1024 * 1024; - handler = Protocol.ProtocolHandler.GetProtocolHandler(client, protocolversion, forgeInfo, this); - Console.WriteLine("Version is supported.\nLogging in..."); - - try - { - if (handler.Login()) - { - if (singlecommand) - { - handler.SendChatMessage(command); - ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); - Thread.Sleep(5000); - handler.Disconnect(); - Thread.Sleep(1000); - } - else - { - foreach (ChatBot bot in botsOnHold) - BotLoad(bot, false); - botsOnHold.Clear(); - - Console.WriteLine("Server was successfully joined.\nType '" - + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) - + "quit' to leave the server."); - - cmdprompt = new Thread(new ThreadStart(CommandPrompt)); - cmdprompt.Name = "MCC Command prompt"; - cmdprompt.Start(); - - timeoutdetector = new Thread(new ThreadStart(TimeoutDetector)); - timeoutdetector.Name = "MCC Connection timeout detector"; - timeoutdetector.Start(); - } - } - else - { - Console.WriteLine("Failed to login to this server."); - retry = true; - } - } - catch (Exception e) - { - ConsoleIO.WriteLineFormatted("§8" + e.GetType().Name + ": " + e.Message); - Console.WriteLine("Failed to join this server."); - retry = true; - } - } - catch (SocketException e) - { - ConsoleIO.WriteLineFormatted("§8" + e.Message); - Console.WriteLine("Failed to connect to this IP."); - retry = true; - } - - if (retry) - { - if (ReconnectionAttemptsLeft > 0) - { - ConsoleIO.WriteLogLine("Waiting 5 seconds (" + ReconnectionAttemptsLeft + " attempts left)..."); - Thread.Sleep(5000); - ReconnectionAttemptsLeft--; - Program.Restart(); - } - else if (!singlecommand && Settings.interactiveMode) - { - Program.HandleFailure(); - } - } - } - - /// - /// Allows the user to send chat messages, commands, and to leave the server. - /// - private void CommandPrompt() - { - try - { - string text = ""; - Thread.Sleep(500); - handler.SendRespawnPacket(); - - while (client.Client.Connected) - { - text = ConsoleIO.ReadLine(); - if (ConsoleIO.BasicIO && text.Length > 0 && text[0] == (char)0x00) - { - //Process a request from the GUI - string[] command = text.Substring(1).Split((char)0x00); - switch (command[0].ToLower()) - { - case "autocomplete": - if (command.Length > 1) { ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00 + handler.AutoComplete(command[1])); } - else Console.WriteLine((char)0x00 + "autocomplete" + (char)0x00); - break; - } - } - else - { - text = text.Trim(); - if (text.Length > 0) - { - if (Settings.internalCmdChar == ' ' || text[0] == Settings.internalCmdChar) - { - string response_msg = ""; - string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1); - if (!PerformInternalCommand(Settings.ExpandVars(command), ref response_msg) && Settings.internalCmdChar == '/') - { - SendText(text); - } - else if (response_msg.Length > 0) - { - ConsoleIO.WriteLogLine(response_msg); - } - } - else SendText(text); - } - } - } - } - catch (IOException) { } - catch (NullReferenceException) { } - } - - /// - /// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old. - /// - private void TimeoutDetector() - { - lock (lastKeepAliveLock) - { - lastKeepAlive = DateTime.Now; - } - do - { - Thread.Sleep(TimeSpan.FromSeconds(15)); - lock (lastKeepAliveLock) - { - if (lastKeepAlive.AddSeconds(30) < DateTime.Now) - { - OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection Timeout"); - } - } - } - while (true); - } - - - /// - /// Perform an internal MCC command (not a server command, use SendText() instead for that!) - /// - /// The command - /// May contain a confirmation or error message after processing the command, or "" otherwise. - /// Local variables passed along with the command - /// TRUE if the command was indeed an internal MCC command - public bool PerformInternalCommand(string command, ref string response_msg, Dictionary localVars = null) - { - - /* Load commands from the 'Commands' namespace */ - - if (cmds.Count == 0) - { - Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands"); - foreach (Type type in cmds_classes) - { - if (type.IsSubclassOf(typeof(Command))) - { - try - { - Command cmd = (Command)Activator.CreateInstance(type); - cmds[cmd.CMDName.ToLower()] = cmd; - cmd_names.Add(cmd.CMDName.ToLower()); - foreach (string alias in cmd.getCMDAliases()) - cmds[alias.ToLower()] = cmd; - } - catch (Exception e) - { - ConsoleIO.WriteLogLine(e.Message); - } - } - } - } - - /* Process the provided command */ - - string command_name = command.Split(' ')[0].ToLower(); - if (command_name == "help") - { - if (Command.hasArg(command)) - { - string help_cmdname = Command.getArgs(command)[0].ToLower(); - if (help_cmdname == "help") - { - response_msg = "help : show brief help about a command."; - } - else if (cmds.ContainsKey(help_cmdname)) - { - response_msg = cmds[help_cmdname].CMDDesc; - } - else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list."; - } - else response_msg = "help . Available commands: " + String.Join(", ", cmd_names.ToArray()) + ". For server help, use '" + Settings.internalCmdChar + "send /help' instead."; - } - else if (cmds.ContainsKey(command_name)) - { - response_msg = cmds[command_name].Run(this, command, localVars); - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnInternalCommand(command_name, string.Join(" ",Command.getArgs(command)),response_msg); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnInternalCommand: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - else - { - response_msg = "Unknown command '" + command_name + "'. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."; - return false; - } - - return true; - } - - /// - /// Disconnect the client from the server (initiated from MCC) - /// - public void Disconnect() - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - - botsOnHold.Clear(); - botsOnHold.AddRange(bots); - - if (handler != null) - { - handler.Disconnect(); - handler.Dispose(); - } - - if (cmdprompt != null) - cmdprompt.Abort(); - - if (timeoutdetector != null) - { - timeoutdetector.Abort(); - timeoutdetector = null; - } - - Thread.Sleep(1000); - - if (client != null) - client.Close(); - } - - /// - /// Load a new bot - /// - public void BotLoad(ChatBot b, bool init = true) - { - b.SetHandler(this); - bots.Add(b); - if (init) - b.Initialize(); - if (this.handler != null) - b.AfterGameJoined(); - Settings.SingleCommand = ""; - } - - /// - /// Unload a bot - /// - public void BotUnLoad(ChatBot b) - { - bots.RemoveAll(item => object.ReferenceEquals(item, b)); - - // ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon. - var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList(); - foreach (var entry in botRegistrations) - { - UnregisterPluginChannel(entry.Key, b); - } - } - - /// - /// Clear bots - /// - public void BotClear() - { - bots.Clear(); - } - - /// - /// Called when a server was successfully joined - /// - public void OnGameJoined() - { - if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) - handler.SendBrandInfo(Settings.BrandInfo.Trim()); - - if (Settings.MCSettings_Enabled) - handler.SendClientSettings( - Settings.MCSettings_Locale, - Settings.MCSettings_RenderDistance, - Settings.MCSettings_Difficulty, - Settings.MCSettings_ChatMode, - Settings.MCSettings_ChatColors, - Settings.MCSettings_Skin_All, - Settings.MCSettings_MainHand); - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.AfterGameJoined(); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("AfterGameJoined: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - - if (inventoryHandlingRequested) - { - inventoryHandlingRequested = false; - inventoryHandlingEnabled = true; - ConsoleIO.WriteLogLine("Inventory handling is now enabled."); - } - } - - /// - /// Called when the player respawns, which happens on login, respawn and world change. - /// - public void OnRespawn() - { - if (terrainAndMovementsRequested) - { - terrainAndMovementsEnabled = true; - terrainAndMovementsRequested = false; - ConsoleIO.WriteLogLine("Terrain and Movements is now enabled."); - } - - if (terrainAndMovementsEnabled) - { - world.Clear(); - } - } - - /// - /// Get Terrain and Movements status. - /// - public bool GetTerrainEnabled() - { - return terrainAndMovementsEnabled; - } - - /// - /// Get Inventory Handling Mode - /// - public bool GetInventoryEnabled() - { - return inventoryHandlingEnabled; - } - - /// - /// Enable or disable Terrain and Movements. - /// Please note that Enabling will be deferred until next relog, respawn or world change. - /// - /// Enabled - /// TRUE if the setting was applied immediately, FALSE if delayed. - public bool SetTerrainEnabled(bool enabled) - { - if (enabled) - { - if (!terrainAndMovementsEnabled) - { - terrainAndMovementsRequested = true; - return false; - } - } - else - { - terrainAndMovementsEnabled = false; - terrainAndMovementsRequested = false; - locationReceived = false; - world.Clear(); - } - return true; - } - - /// - /// Enable or disable Inventories. - /// Please note that Enabling will be deferred until next relog. - /// - /// Enabled - /// TRUE if the setting was applied immediately, FALSE if delayed. - public bool SetInventoryEnabled(bool enabled) - { - if (enabled) - { - if (!inventoryHandlingEnabled) - { - inventoryHandlingRequested = true; - return false; - } - } - else - { - inventoryHandlingEnabled = false; - inventoryHandlingRequested = false; - inventories.Clear(); - } - return true; - } - - /// - /// Get entity handling status - /// - /// - /// Entity Handling cannot be enabled in runtime (or after joining server) - public bool GetEntityHandlingEnabled() - { - return entityHandlingEnabled; - } - - /// - /// Enable or disable Entity handling. - /// Please note that Enabling will be deferred until next relog. - /// - /// Enabled - /// TRUE if the setting was applied immediately, FALSE if delayed. - public bool SetEntityHandlingEnabled(bool enabled) - { - if (!enabled) - { - if (entityHandlingEnabled) - { - entityHandlingEnabled = false; - return true; - } - else - { - return false; - } - } - else - { - // Entity Handling cannot be enabled in runtime (or after joining server) - return false; - } - } - - /// - /// Get all inventories. ID 0 is the player inventory. - /// - /// All inventories - public Dictionary GetInventories() - { - return inventories; - } - - /// - /// Get client player's inventory items - /// - /// Window ID of the requested inventory - /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) - public Container GetInventory(int inventoryID) - { - if (inventories.ContainsKey(inventoryID)) - return inventories[inventoryID]; - return null; - } - - /// - /// Get client player's inventory items - /// - /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) - public Container GetPlayerInventory() - { - return GetInventory(0); - } - - /// - /// Called when the server sends a new player location, - /// or if a ChatBot whishes to update the player's location. - /// - /// The new location - /// If true, the location is relative to the current location - public void UpdateLocation(Location location, bool relative) - { - lock (locationLock) - { - if (relative) - { - this.location += location; - } - else this.location = location; - locationReceived = true; - } - } - - /// - /// Called when the server sends a new player location, - /// or if a ChatBot whishes to update the player's location. - /// - /// The new location - /// Yaw to look at - /// Pitch to look at - public void UpdateLocation(Location location, float yaw, float pitch) - { - this.yaw = yaw; - this.pitch = pitch; - UpdateLocation(location, false); - } - - /// - /// Called when the server sends a new player location, - /// or if a ChatBot whishes to update the player's location. - /// - /// The new location - /// Block coordinates to look at - public void UpdateLocation(Location location, Location lookAtLocation) - { - double dx = lookAtLocation.X - (location.X - 0.5); - double dy = lookAtLocation.Y - (location.Y + 1); - double dz = lookAtLocation.Z - (location.Z - 0.5); - - double r = Math.Sqrt(dx * dx + dy * dy + dz * dz); - - float yaw = Convert.ToSingle(-Math.Atan2(dx, dz) / Math.PI * 180); - float pitch = Convert.ToSingle(-Math.Asin(dy / r) / Math.PI * 180); - if (yaw < 0) yaw += 360; - - UpdateLocation(location, yaw, pitch); - } - - /// - /// Called when the server sends a new player location, - /// or if a ChatBot whishes to update the player's location. - /// - /// The new location - /// Direction to look at - public void UpdateLocation(Location location, Direction direction) - { - float yaw = 0; - float pitch = 0; - - switch (direction) - { - case Direction.Up: - pitch = -90; - break; - case Direction.Down: - pitch = 90; - break; - case Direction.East: - yaw = 270; - break; - case Direction.West: - yaw = 90; - break; - case Direction.North: - yaw = 180; - break; - case Direction.South: - break; - default: - throw new ArgumentException("Unknown direction", "direction"); - } - - UpdateLocation(location, yaw, pitch); - } - - /// - /// Move to the specified location - /// - /// Location to reach - /// Allow possible but unsafe locations - /// True if a path has been found - public bool MoveTo(Location location, bool allowUnsafe = false) - { - lock (locationLock) - { - if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location)) - path = new Queue(new[] { location }); - else path = Movement.CalculatePath(world, this.location, location, allowUnsafe); - return path != null; - } - } - - /// - /// Received some text from the server - /// - /// Text received - /// TRUE if the text is JSON-Encoded - public void OnTextReceived(string text, bool isJson) - { - lock (lastKeepAliveLock) - { - lastKeepAlive = DateTime.Now; - } - List links = new List(); - string json = null; - if (isJson) - { - json = text; - text = ChatParser.ParseText(json, links); - } - ConsoleIO.WriteLineFormatted(text, true); - if (Settings.DisplayChatLinks) - foreach (string link in links) - ConsoleIO.WriteLogLine("Link: " + link, false); - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.GetText(text); - if (bots.Contains(bot)) - bot.GetText(text, json); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("GetText: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - /// - /// Received a connection keep-alive from the server - /// - public void OnServerKeepAlive() - { - lock (lastKeepAliveLock) - { - lastKeepAlive = DateTime.Now; - } - } - - /// - /// When an inventory is opened - /// - /// Location to reach - public void OnInventoryOpen(int inventoryID, Container inventory) - { - inventories[inventoryID] = inventory; - - if (inventoryID != 0) - { - ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " opened: " + inventory.Title); - ConsoleIO.WriteLogLine("Use /inventory to interact with it."); - } - } - - /// - /// When an inventory is close - /// - /// Location to reach - public void OnInventoryClose(int inventoryID) - { - if (inventories.ContainsKey(inventoryID)) - inventories.Remove(inventoryID); - - if (inventoryID != 0) - ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " closed."); - } - - /// - /// When received window items from server. - /// - /// Inventory ID - /// Item list, key = slot ID, value = Item information - public void OnWindowItems(byte inventoryID, Dictionary itemList) - { - if (inventories.ContainsKey(inventoryID)) - inventories[inventoryID].Items = itemList; - } - - /// - /// When a slot is set inside window items - /// - /// Window ID - /// Slot ID - /// Item (may be null for empty slot) - public void OnSetSlot(byte inventoryID, short slotID, Item item) - { - if (inventories.ContainsKey(inventoryID)) - { - if (item == null || item.IsEmpty) - { - if (inventories[inventoryID].Items.ContainsKey(slotID)) - inventories[inventoryID].Items.Remove(slotID); - } - else inventories[inventoryID].Items[slotID] = item; - } - } - - /// - /// When connection has been lost, login was denied or played was kicked from the server - /// - public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) - { - world.Clear(); - - if (timeoutdetector != null) - { - timeoutdetector.Abort(); - timeoutdetector = null; - } - - bool will_restart = false; - - switch (reason) - { - case ChatBot.DisconnectReason.ConnectionLost: - message = "Connection has been lost."; - ConsoleIO.WriteLine(message); - break; - - case ChatBot.DisconnectReason.InGameKick: - ConsoleIO.WriteLine("Disconnected by Server :"); - ConsoleIO.WriteLineFormatted(message); - break; - - case ChatBot.DisconnectReason.LoginRejected: - ConsoleIO.WriteLine("Login failed :"); - ConsoleIO.WriteLineFormatted(message); - break; - - case ChatBot.DisconnectReason.UserLogout: - throw new InvalidOperationException("User-initiated logout should be done by calling Disconnect()"); - } - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - will_restart |= bot.OnDisconnect(reason, message); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - - if (!will_restart) - Program.HandleFailure(); - } - - /// - /// Called ~10 times per second by the protocol handler - /// - public void OnUpdate() - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.Update(); - bot.ProcessQueuedText(); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("Update: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - - if (terrainAndMovementsEnabled && locationReceived) - { - lock (locationLock) - { - for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps - { - if (yaw == null || pitch == null) - { - if (steps != null && steps.Count > 0) - { - location = steps.Dequeue(); - } - else if (path != null && path.Count > 0) - { - Location next = path.Dequeue(); - steps = Movement.Move2Steps(location, next, ref motionY); - UpdateLocation(location, next + new Location(0, 1, 0)); // Update yaw and pitch to look at next step - } - else - { - location = Movement.HandleGravity(world, location, ref motionY); - } - } - handler.SendLocationUpdate(location, Movement.IsOnGround(world, location), yaw, pitch); - } - // First 2 updates must be player position AND look, and player must not move (to conform with vanilla) - // Once yaw and pitch have been sent, switch back to location-only updates (without yaw and pitch) - yaw = null; - pitch = null; - } - } - - if (Settings.AutoRespawn && respawnTicks > 0) - { - respawnTicks--; - if (respawnTicks == 0) - SendRespawnPacket(); - } - } - - /// - /// Send a chat message or command to the server - /// - /// Text to send to the server - /// True if the text was sent with no error - public bool SendText(string text) - { - int maxLength = handler.GetMaxChatMessageLength(); - if (text.Length > maxLength) //Message is too long? - { - if (text[0] == '/') - { - //Send the first 100/256 chars of the command - text = text.Substring(0, maxLength); - return handler.SendChatMessage(text); - } - else - { - //Send the message splitted into several messages - while (text.Length > maxLength) - { - handler.SendChatMessage(text.Substring(0, maxLength)); - text = text.Substring(maxLength, text.Length - maxLength); - if (Settings.splitMessageDelay.TotalSeconds > 0) - Thread.Sleep(Settings.splitMessageDelay); - } - return handler.SendChatMessage(text); - } - } - else return handler.SendChatMessage(text); - } - - /// - /// Allow to respawn after death - /// - /// True if packet successfully sent - public bool SendRespawnPacket() - { - return handler.SendRespawnPacket(); - } - - /// - /// Triggered when a new player joins the game - /// - /// UUID of the player - /// Name of the player - public void OnPlayerJoin(Guid uuid, string name) - { - //Ignore placeholders eg 0000tab# from TabListPlus - if (!ChatBot.IsValidName(name)) - return; - - lock (onlinePlayers) - { - onlinePlayers[uuid] = name; - } - } - - /// - /// Triggered when a player has left the game - /// - /// UUID of the player - public void OnPlayerLeave(Guid uuid) - { - lock (onlinePlayers) - { - onlinePlayers.Remove(uuid); - } - } - - /// - /// Get a set of online player names - /// - /// Online player names - public string[] GetOnlinePlayers() - { - lock (onlinePlayers) - { - return onlinePlayers.Values.Distinct().ToArray(); - } - } - - /// - /// Get a dictionary of online player names and their corresponding UUID - /// - /// Dictionay of online players, key is UUID, value is Player name - public Dictionary GetOnlinePlayersWithUUID() - { - Dictionary uuid2Player = new Dictionary(); - lock (onlinePlayers) - { - foreach (Guid key in onlinePlayers.Keys) - { - uuid2Player.Add(key.ToString(), onlinePlayers[key]); - } - } - return uuid2Player; - } - - /// - /// Registers the given plugin channel for the given bot. - /// - /// The channel to register. - /// The bot to register the channel for. - public void RegisterPluginChannel(string channel, ChatBot bot) - { - if (registeredBotPluginChannels.ContainsKey(channel)) - { - registeredBotPluginChannels[channel].Add(bot); - } - else - { - List bots = new List(); - bots.Add(bot); - registeredBotPluginChannels[channel] = bots; - SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true); - } - } - - /// - /// Unregisters the given plugin channel for the given bot. - /// - /// The channel to unregister. - /// The bot to unregister the channel for. - public void UnregisterPluginChannel(string channel, ChatBot bot) - { - if (registeredBotPluginChannels.ContainsKey(channel)) - { - List registeredBots = registeredBotPluginChannels[channel]; - registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot)); - if (registeredBots.Count == 0) - { - registeredBotPluginChannels.Remove(channel); - SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true); - } - } - } - - /// - /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information - /// about plugin channels. - /// - /// The channel to send the packet on. - /// The payload for the packet. - /// Whether the packet should be sent even if the server or the client hasn't registered it yet. - /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. - public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) - { - if (!sendEvenIfNotRegistered) - { - if (!registeredBotPluginChannels.ContainsKey(channel)) - { - return false; - } - if (!registeredServerPluginChannels.Contains(channel)) - { - return false; - } - } - return handler.SendPluginChannelPacket(channel, data); - } - - /// - /// Called when a plugin channel message was sent from the server. - /// - /// The channel the message was sent on - /// The data from the channel - public void OnPluginChannelMessage(string channel, byte[] data) - { - if (channel == "REGISTER") - { - string[] channels = Encoding.UTF8.GetString(data).Split('\0'); - foreach (string chan in channels) - { - if (!registeredServerPluginChannels.Contains(chan)) - { - registeredServerPluginChannels.Add(chan); - } - } - } - if (channel == "UNREGISTER") - { - string[] channels = Encoding.UTF8.GetString(data).Split('\0'); - foreach (string chan in channels) - { - registeredServerPluginChannels.Remove(chan); - } - } - - if (registeredBotPluginChannels.ContainsKey(channel)) - { - foreach (ChatBot bot in registeredBotPluginChannels[channel]) - { - bot.OnPluginMessage(channel, data); - } - } - } - - /// - /// Called when an entity spawned - /// - public void OnSpawnEntity(Entity entity) - { - // The entity should not already exist, but if it does, let's consider the previous one is being destroyed - if (entities.ContainsKey(entity.ID)) - OnDestroyEntities(new[] { entity.ID }); - - entities.Add(entity.ID, entity); - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnEntitySpawn(entity); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntitySpawn: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - /// - /// Called when a player spawns or enters the client's render distance - /// - public void OnSpawnPlayer(int entityID, Guid uuid, Location location, byte Yaw, byte Pitch) - { - string playerName = null; - if (onlinePlayers.ContainsKey(uuid)) - playerName = onlinePlayers[uuid]; - Entity playerEntity = new Entity(entityID, EntityType.Player, location, uuid, playerName); - OnSpawnEntity(playerEntity); - } - - /// - /// Called when entities dead/despawn. - /// - public void OnDestroyEntities(int[] Entities) - { - foreach (int a in Entities) - { - if (entities.ContainsKey(a)) - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnEntityDespawn(entities[a]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityDespawn: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - entities.Remove(a); - } - } - } - - /// - /// Called when an entity's position changed within 8 block of its previous position. - /// - /// - /// - /// - /// - /// - public void OnEntityPosition(int EntityID, Double Dx, Double Dy, Double Dz,bool onGround) - { - if (entities.ContainsKey(EntityID)) - { - Location L = entities[EntityID].Location; - L.X += Dx; - L.Y += Dy; - L.Z += Dz; - entities[EntityID].Location = L; - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnEntityMove(entities[EntityID]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - } - - /// - /// Called when an entity moved over 8 block. - /// - /// - /// - /// - /// - /// - public void OnEntityTeleport(int EntityID, Double X, Double Y, Double Z, bool onGround) - { - if (entities.ContainsKey(EntityID)) - { - Location location = new Location(X, Y, Z); - entities[EntityID].Location = location; - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnEntityMove(entities[EntityID]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - - /// - /// Called when received entity properties from server. - /// - /// - /// - public void OnEntityProperties(int EntityID, Dictionary prop) - { - if(EntityID == playerEntityID) - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnPlayerProperty(prop); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnPlayerProperty: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - - /// - /// Called when server sent a Time Update packet. - /// - /// - /// - public void OnTimeUpdate(long WorldAge, long TimeOfDay) - { - // calculate server tps - if (lastAge != 0) - { - DateTime currentTime = DateTime.Now; - long tickDiff = WorldAge - lastAge; - Double tps = tickDiff / (currentTime - lastTime).TotalSeconds; - lastAge = WorldAge; - lastTime = currentTime; - if (tps <= 20.0 && tps >= 0.0 && serverTPS != tps) - { - serverTPS = tps; - // invoke ChatBot - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnServerTpsUpdate(tps); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnServerTpsUpdate: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - else - { - lastAge = WorldAge; - lastTime = DateTime.Now; - } - - } - - /// - /// Set client player's ID for later receiving player's own properties - /// - /// Player Entity ID - public void SetPlayerEntityID(int EntityID) - { - playerEntityID = EntityID; - } - - /// - /// Send the Entity Action packet with the Specified ID - /// - /// TRUE if the item was successfully used - public bool sendEntityAction(EntityActionType entityAction) - { - return handler.SendEntityAction(playerEntityID, (int) entityAction); - } - /// - /// Use the item currently in the player's hand - /// - /// TRUE if the item was successfully used - public bool UseItemOnHand() - { - return handler.SendUseItem(0); - } - - /// - /// Click a slot in the specified window - /// - /// TRUE if the slot was successfully clicked - public bool DoWindowAction(int windowId, int slotId, WindowActionType action) - { - Item item = null; - if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) - item = inventories[windowId].Items[slotId]; - - return handler.SendWindowAction(windowId, slotId, action, item); - } - - /// - /// Give Creative Mode items into regular/survival Player Inventory - /// - /// (obviously) requires to be in creative mode - /// Destination inventory slot - /// Item type - /// Item count - /// TRUE if item given successfully - public bool DoCreativeGive(int slot, ItemType itemType, int count) - { - return handler.SendCreativeInventoryAction(slot, itemType, count); +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Threading; +using System.IO; +using System.Net; +using MinecraftClient.ChatBots; +using MinecraftClient.Protocol; +using MinecraftClient.Proxy; +using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Mapping; +using MinecraftClient.Inventory; + +namespace MinecraftClient +{ + /// + /// The main client class, used to connect to a Minecraft server. + /// + public class McTcpClient : IMinecraftComHandler + { + public static int ReconnectionAttemptsLeft = 0; + + private static readonly List cmd_names = new List(); + private static readonly Dictionary cmds = new Dictionary(); + private readonly Dictionary onlinePlayers = new Dictionary(); + + private readonly List bots = new List(); + private static readonly List botsOnHold = new List(); + private static Dictionary inventories = new Dictionary(); + + private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); + private readonly List registeredServerPluginChannels = new List(); + + private bool terrainAndMovementsEnabled; + private bool terrainAndMovementsRequested = false; + private bool inventoryHandlingEnabled; + private bool inventoryHandlingRequested = false; + private bool entityHandlingEnabled; + + private object locationLock = new object(); + private bool locationReceived = false; + private World world = new World(); + private Queue steps; + private Queue path; + private Location location; + private float? yaw; + private float? pitch; + private double motionY; + + private string host; + private int port; + private string username; + private string uuid; + private string sessionid; + private DateTime lastKeepAlive; + private object lastKeepAliveLock = new object(); + private int respawnTicks = 0; + + private int playerEntityID; + + // player health and hunger + private float playerHealth; + private int playerFoodSaturation; + private byte CurrentSlot = 0; + + // Entity handling + private Dictionary entities = new Dictionary(); + + // server TPS + private long lastAge = 0; + private DateTime lastTime; + private Double serverTPS = 0; + + public int GetServerPort() { return port; } + public string GetServerHost() { return host; } + public string GetUsername() { return username; } + public string GetUserUUID() { return uuid; } + public string GetSessionID() { return sessionid; } + public Location GetCurrentLocation() { return location; } + public World GetWorld() { return world; } + public Double GetServerTPS() { return serverTPS; } + public float GetHealth() { return playerHealth; } + public int GetSaturation() { return playerFoodSaturation; } + public byte GetCurrentSlot() { return CurrentSlot; } + + // get bots list for unloading them by commands + public List GetLoadedChatBots() + { + return bots; } - /// - /// Plays animation - /// - /// <0|1> - /// TRUE if item given successfully - public bool DoAnimation(int animation) - { - return handler.SendAnimation(animation); - } - - /// - /// Close the specified inventory window - /// - /// Window ID - /// TRUE if the window was successfully closed - public bool CloseInventory(int windowId) - { - if (windowId != 0 && inventories.ContainsKey(windowId)) - { - inventories.Remove(windowId); - return handler.SendCloseWindow(windowId); - } - return false; - } - - /// - /// Interact with an entity - /// - /// - /// 0: interact, 1: attack, 2: interact at - /// TRUE if interaction succeeded - public bool InteractEntity(int EntityID, int type) - { - return handler.SendInteractEntity(EntityID, type); - } - - /// - /// Place the block at hand in the Minecraft world - /// - /// Location to place block to - /// TRUE if successfully placed - public bool PlaceBlock(Location location) - { - //WORK IN PROGRESS. MAY NOT WORK YET - if (Settings.DebugMessages) - ConsoleIO.WriteLogLine(location.ToString()); - return handler.SendPlayerBlockPlacement(0, location, 1, 0.5f, 0.5f, 0.5f, false); - } - - /// - /// Change active slot in the player inventory - /// - /// Slot to activate (0 to 8) - /// TRUE if the slot was changed - public bool ChangeSlot(short slot) - { - if (slot >= 0 && slot <= 8) - { - CurrentSlot = Convert.ToByte(slot); - return handler.SendHeldItemChange(slot); - } - else - { - return false; - } - } - - /// - /// Called when client player's health changed, e.g. getting attack - /// - /// Player current health - public void OnUpdateHealth(float health, int food) - { - playerHealth = health; - playerFoodSaturation = food; - if (health <= 0) - { - if (Settings.AutoRespawn) - { - ConsoleIO.WriteLogLine("You are dead. Automatically respawning after 1 second."); - respawnTicks = 10; - } - else - { - ConsoleIO.WriteLogLine("You are dead. Type /respawn to respawn."); - } - } - foreach (ChatBot bot in bots.ToArray()) - bot.OnHealthUpdate(health, food); - } - - public void OnHeldItemChange(byte slot) - { - foreach (ChatBot bot in bots.ToArray()) - bot.OnHeldItemChange(slot); - CurrentSlot = slot; - } - } -} + TcpClient client; + IMinecraftCom handler; + Thread cmdprompt; + Thread timeoutdetector; + + /// + /// Starts the main chat client + /// + /// The chosen username of a premium Minecraft Account + /// The player's UUID for online-mode authentication + /// A valid sessionID obtained after logging in + /// The server IP + /// The server port to use + /// Minecraft protocol version to use + public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port) + { + StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, ""); + } + + /// + /// Starts the main chat client in single command sending mode + /// + /// The chosen username of a premium Minecraft Account + /// The player's UUID for online-mode authentication + /// A valid sessionID obtained after logging in + /// The server IP + /// The server port to use + /// Minecraft protocol version to use + /// The text or command to send. + public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command) + { + StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command); + } + + /// + /// Starts the main chat client, wich will login to the server using the MinecraftCom class. + /// + /// The chosen username of a premium Minecraft Account + /// A valid sessionID obtained with MinecraftCom.GetLogin() + /// The server IP + /// The server port to use + /// Minecraft protocol version to use + /// The player's UUID for online-mode authentication + /// If set to true, the client will send a single command and then disconnect from the server + /// The text or command to send. Will only be sent if singlecommand is set to true. + private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command) + { + terrainAndMovementsEnabled = Settings.TerrainAndMovements; + inventoryHandlingEnabled = Settings.InventoryHandling; + entityHandlingEnabled = Settings.EntityHandling; + + if (inventoryHandlingEnabled) + { + inventories.Clear(); + inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); + } + + bool retry = false; + this.sessionid = sessionID; + this.uuid = uuid; + this.username = user; + this.host = server_ip; + this.port = port; + + if (!singlecommand) + { + if (botsOnHold.Count == 0) + { + if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); } + if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); } + if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); } + if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.ExpandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } + if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.ExpandVars(Settings.PlayerLog_File))); } + if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } + if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ExpandVars(Settings.ScriptScheduler_TasksFile))); } + if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } + if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } + if (Settings.AutoAttack_Enabled) { BotLoad(new ChatBots.AutoAttack()); } + if (Settings.AutoFishing_Enabled) { BotLoad(new ChatBots.AutoFishing()); } + if (Settings.AutoEat_Enabled) { BotLoad(new ChatBots.AutoEat(Settings.AutoEat_hungerThreshold)); } + + //Add your ChatBot here by uncommenting and adapting + //BotLoad(new ChatBots.YourBot()); + } + } + + try + { + client = ProxyHandler.newTcpClient(host, port); + client.ReceiveBufferSize = 1024 * 1024; + handler = Protocol.ProtocolHandler.GetProtocolHandler(client, protocolversion, forgeInfo, this); + Console.WriteLine("Version is supported.\nLogging in..."); + + try + { + if (handler.Login()) + { + if (singlecommand) + { + handler.SendChatMessage(command); + ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); + Thread.Sleep(5000); + handler.Disconnect(); + Thread.Sleep(1000); + } + else + { + foreach (ChatBot bot in botsOnHold) + BotLoad(bot, false); + botsOnHold.Clear(); + + Console.WriteLine("Server was successfully joined.\nType '" + + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + + "quit' to leave the server."); + + cmdprompt = new Thread(new ThreadStart(CommandPrompt)); + cmdprompt.Name = "MCC Command prompt"; + cmdprompt.Start(); + + timeoutdetector = new Thread(new ThreadStart(TimeoutDetector)); + timeoutdetector.Name = "MCC Connection timeout detector"; + timeoutdetector.Start(); + } + } + else + { + Console.WriteLine("Failed to login to this server."); + retry = true; + } + } + catch (Exception e) + { + ConsoleIO.WriteLineFormatted("§8" + e.GetType().Name + ": " + e.Message); + Console.WriteLine("Failed to join this server."); + retry = true; + } + } + catch (SocketException e) + { + ConsoleIO.WriteLineFormatted("§8" + e.Message); + Console.WriteLine("Failed to connect to this IP."); + retry = true; + } + + if (retry) + { + if (ReconnectionAttemptsLeft > 0) + { + ConsoleIO.WriteLogLine("Waiting 5 seconds (" + ReconnectionAttemptsLeft + " attempts left)..."); + Thread.Sleep(5000); + ReconnectionAttemptsLeft--; + Program.Restart(); + } + else if (!singlecommand && Settings.interactiveMode) + { + Program.HandleFailure(); + } + } + } + + /// + /// Allows the user to send chat messages, commands, and to leave the server. + /// + private void CommandPrompt() + { + try + { + string text = ""; + Thread.Sleep(500); + handler.SendRespawnPacket(); + + while (client.Client.Connected) + { + text = ConsoleIO.ReadLine(); + if (ConsoleIO.BasicIO && text.Length > 0 && text[0] == (char)0x00) + { + //Process a request from the GUI + string[] command = text.Substring(1).Split((char)0x00); + switch (command[0].ToLower()) + { + case "autocomplete": + if (command.Length > 1) { ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00 + handler.AutoComplete(command[1])); } + else Console.WriteLine((char)0x00 + "autocomplete" + (char)0x00); + break; + } + } + else + { + text = text.Trim(); + if (text.Length > 0) + { + if (Settings.internalCmdChar == ' ' || text[0] == Settings.internalCmdChar) + { + string response_msg = ""; + string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1); + if (!PerformInternalCommand(Settings.ExpandVars(command), ref response_msg) && Settings.internalCmdChar == '/') + { + SendText(text); + } + else if (response_msg.Length > 0) + { + ConsoleIO.WriteLogLine(response_msg); + } + } + else SendText(text); + } + } + } + } + catch (IOException) { } + catch (NullReferenceException) { } + } + + /// + /// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old. + /// + private void TimeoutDetector() + { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } + do + { + Thread.Sleep(TimeSpan.FromSeconds(15)); + lock (lastKeepAliveLock) + { + if (lastKeepAlive.AddSeconds(30) < DateTime.Now) + { + OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection Timeout"); + } + } + } + while (true); + } + + + /// + /// Perform an internal MCC command (not a server command, use SendText() instead for that!) + /// + /// The command + /// May contain a confirmation or error message after processing the command, or "" otherwise. + /// Local variables passed along with the command + /// TRUE if the command was indeed an internal MCC command + public bool PerformInternalCommand(string command, ref string response_msg, Dictionary localVars = null) + { + + /* Load commands from the 'Commands' namespace */ + + if (cmds.Count == 0) + { + Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands"); + foreach (Type type in cmds_classes) + { + if (type.IsSubclassOf(typeof(Command))) + { + try + { + Command cmd = (Command)Activator.CreateInstance(type); + cmds[cmd.CMDName.ToLower()] = cmd; + cmd_names.Add(cmd.CMDName.ToLower()); + foreach (string alias in cmd.getCMDAliases()) + cmds[alias.ToLower()] = cmd; + } + catch (Exception e) + { + ConsoleIO.WriteLogLine(e.Message); + } + } + } + } + + /* Process the provided command */ + + string command_name = command.Split(' ')[0].ToLower(); + if (command_name == "help") + { + if (Command.hasArg(command)) + { + string help_cmdname = Command.getArgs(command)[0].ToLower(); + if (help_cmdname == "help") + { + response_msg = "help : show brief help about a command."; + } + else if (cmds.ContainsKey(help_cmdname)) + { + response_msg = cmds[help_cmdname].CMDDesc; + } + else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list."; + } + else response_msg = "help . Available commands: " + String.Join(", ", cmd_names.ToArray()) + ". For server help, use '" + Settings.internalCmdChar + "send /help' instead."; + } + else if (cmds.ContainsKey(command_name)) + { + response_msg = cmds[command_name].Run(this, command, localVars); + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnInternalCommand(command_name, string.Join(" ",Command.getArgs(command)),response_msg); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnInternalCommand: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + else + { + response_msg = "Unknown command '" + command_name + "'. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."; + return false; + } + + return true; + } + + /// + /// Disconnect the client from the server (initiated from MCC) + /// + public void Disconnect() + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + + botsOnHold.Clear(); + botsOnHold.AddRange(bots); + + if (handler != null) + { + handler.Disconnect(); + handler.Dispose(); + } + + if (cmdprompt != null) + cmdprompt.Abort(); + + if (timeoutdetector != null) + { + timeoutdetector.Abort(); + timeoutdetector = null; + } + + Thread.Sleep(1000); + + if (client != null) + client.Close(); + } + + /// + /// Load a new bot + /// + public void BotLoad(ChatBot b, bool init = true) + { + b.SetHandler(this); + bots.Add(b); + if (init) + b.Initialize(); + if (this.handler != null) + b.AfterGameJoined(); + Settings.SingleCommand = ""; + } + + /// + /// Unload a bot + /// + public void BotUnLoad(ChatBot b) + { + bots.RemoveAll(item => object.ReferenceEquals(item, b)); + + // ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon. + var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList(); + foreach (var entry in botRegistrations) + { + UnregisterPluginChannel(entry.Key, b); + } + } + + /// + /// Clear bots + /// + public void BotClear() + { + bots.Clear(); + } + + /// + /// Called when a server was successfully joined + /// + public void OnGameJoined() + { + if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) + handler.SendBrandInfo(Settings.BrandInfo.Trim()); + + if (Settings.MCSettings_Enabled) + handler.SendClientSettings( + Settings.MCSettings_Locale, + Settings.MCSettings_RenderDistance, + Settings.MCSettings_Difficulty, + Settings.MCSettings_ChatMode, + Settings.MCSettings_ChatColors, + Settings.MCSettings_Skin_All, + Settings.MCSettings_MainHand); + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.AfterGameJoined(); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("AfterGameJoined: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + + if (inventoryHandlingRequested) + { + inventoryHandlingRequested = false; + inventoryHandlingEnabled = true; + ConsoleIO.WriteLogLine("Inventory handling is now enabled."); + } + } + + /// + /// Called when the player respawns, which happens on login, respawn and world change. + /// + public void OnRespawn() + { + if (terrainAndMovementsRequested) + { + terrainAndMovementsEnabled = true; + terrainAndMovementsRequested = false; + ConsoleIO.WriteLogLine("Terrain and Movements is now enabled."); + } + + if (terrainAndMovementsEnabled) + { + world.Clear(); + } + } + + /// + /// Get Terrain and Movements status. + /// + public bool GetTerrainEnabled() + { + return terrainAndMovementsEnabled; + } + + /// + /// Get Inventory Handling Mode + /// + public bool GetInventoryEnabled() + { + return inventoryHandlingEnabled; + } + + /// + /// Enable or disable Terrain and Movements. + /// Please note that Enabling will be deferred until next relog, respawn or world change. + /// + /// Enabled + /// TRUE if the setting was applied immediately, FALSE if delayed. + public bool SetTerrainEnabled(bool enabled) + { + if (enabled) + { + if (!terrainAndMovementsEnabled) + { + terrainAndMovementsRequested = true; + return false; + } + } + else + { + terrainAndMovementsEnabled = false; + terrainAndMovementsRequested = false; + locationReceived = false; + world.Clear(); + } + return true; + } + + /// + /// Enable or disable Inventories. + /// Please note that Enabling will be deferred until next relog. + /// + /// Enabled + /// TRUE if the setting was applied immediately, FALSE if delayed. + public bool SetInventoryEnabled(bool enabled) + { + if (enabled) + { + if (!inventoryHandlingEnabled) + { + inventoryHandlingRequested = true; + return false; + } + } + else + { + inventoryHandlingEnabled = false; + inventoryHandlingRequested = false; + inventories.Clear(); + } + return true; + } + + /// + /// Get entity handling status + /// + /// + /// Entity Handling cannot be enabled in runtime (or after joining server) + public bool GetEntityHandlingEnabled() + { + return entityHandlingEnabled; + } + + /// + /// Enable or disable Entity handling. + /// Please note that Enabling will be deferred until next relog. + /// + /// Enabled + /// TRUE if the setting was applied immediately, FALSE if delayed. + public bool SetEntityHandlingEnabled(bool enabled) + { + if (!enabled) + { + if (entityHandlingEnabled) + { + entityHandlingEnabled = false; + return true; + } + else + { + return false; + } + } + else + { + // Entity Handling cannot be enabled in runtime (or after joining server) + return false; + } + } + + /// + /// Get all inventories. ID 0 is the player inventory. + /// + /// All inventories + public Dictionary GetInventories() + { + return inventories; + } + + /// + /// Get client player's inventory items + /// + /// Window ID of the requested inventory + /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) + public Container GetInventory(int inventoryID) + { + if (inventories.ContainsKey(inventoryID)) + return inventories[inventoryID]; + return null; + } + + /// + /// Get client player's inventory items + /// + /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) + public Container GetPlayerInventory() + { + return GetInventory(0); + } + + /// + /// Called when the server sends a new player location, + /// or if a ChatBot whishes to update the player's location. + /// + /// The new location + /// If true, the location is relative to the current location + public void UpdateLocation(Location location, bool relative) + { + lock (locationLock) + { + if (relative) + { + this.location += location; + } + else this.location = location; + locationReceived = true; + } + } + + /// + /// Called when the server sends a new player location, + /// or if a ChatBot whishes to update the player's location. + /// + /// The new location + /// Yaw to look at + /// Pitch to look at + public void UpdateLocation(Location location, float yaw, float pitch) + { + this.yaw = yaw; + this.pitch = pitch; + UpdateLocation(location, false); + } + + /// + /// Called when the server sends a new player location, + /// or if a ChatBot whishes to update the player's location. + /// + /// The new location + /// Block coordinates to look at + public void UpdateLocation(Location location, Location lookAtLocation) + { + double dx = lookAtLocation.X - (location.X - 0.5); + double dy = lookAtLocation.Y - (location.Y + 1); + double dz = lookAtLocation.Z - (location.Z - 0.5); + + double r = Math.Sqrt(dx * dx + dy * dy + dz * dz); + + float yaw = Convert.ToSingle(-Math.Atan2(dx, dz) / Math.PI * 180); + float pitch = Convert.ToSingle(-Math.Asin(dy / r) / Math.PI * 180); + if (yaw < 0) yaw += 360; + + UpdateLocation(location, yaw, pitch); + } + + /// + /// Called when the server sends a new player location, + /// or if a ChatBot whishes to update the player's location. + /// + /// The new location + /// Direction to look at + public void UpdateLocation(Location location, Direction direction) + { + float yaw = 0; + float pitch = 0; + + switch (direction) + { + case Direction.Up: + pitch = -90; + break; + case Direction.Down: + pitch = 90; + break; + case Direction.East: + yaw = 270; + break; + case Direction.West: + yaw = 90; + break; + case Direction.North: + yaw = 180; + break; + case Direction.South: + break; + default: + throw new ArgumentException("Unknown direction", "direction"); + } + + UpdateLocation(location, yaw, pitch); + } + + /// + /// Move to the specified location + /// + /// Location to reach + /// Allow possible but unsafe locations + /// True if a path has been found + public bool MoveTo(Location location, bool allowUnsafe = false) + { + lock (locationLock) + { + if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location)) + path = new Queue(new[] { location }); + else path = Movement.CalculatePath(world, this.location, location, allowUnsafe); + return path != null; + } + } + + /// + /// Received some text from the server + /// + /// Text received + /// TRUE if the text is JSON-Encoded + public void OnTextReceived(string text, bool isJson) + { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } + List links = new List(); + string json = null; + if (isJson) + { + json = text; + text = ChatParser.ParseText(json, links); + } + ConsoleIO.WriteLineFormatted(text, true); + if (Settings.DisplayChatLinks) + foreach (string link in links) + ConsoleIO.WriteLogLine("Link: " + link, false); + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.GetText(text); + if (bots.Contains(bot)) + bot.GetText(text, json); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("GetText: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + /// + /// Received a connection keep-alive from the server + /// + public void OnServerKeepAlive() + { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } + } + + /// + /// When an inventory is opened + /// + /// Location to reach + public void OnInventoryOpen(int inventoryID, Container inventory) + { + inventories[inventoryID] = inventory; + + if (inventoryID != 0) + { + ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " opened: " + inventory.Title); + ConsoleIO.WriteLogLine("Use /inventory to interact with it."); + } + } + + /// + /// When an inventory is close + /// + /// Location to reach + public void OnInventoryClose(int inventoryID) + { + if (inventories.ContainsKey(inventoryID)) + inventories.Remove(inventoryID); + + if (inventoryID != 0) + ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " closed."); + } + + /// + /// When received window items from server. + /// + /// Inventory ID + /// Item list, key = slot ID, value = Item information + public void OnWindowItems(byte inventoryID, Dictionary itemList) + { + if (inventories.ContainsKey(inventoryID)) + inventories[inventoryID].Items = itemList; + } + + /// + /// When a slot is set inside window items + /// + /// Window ID + /// Slot ID + /// Item (may be null for empty slot) + public void OnSetSlot(byte inventoryID, short slotID, Item item) + { + if (inventories.ContainsKey(inventoryID)) + { + if (item == null || item.IsEmpty) + { + if (inventories[inventoryID].Items.ContainsKey(slotID)) + inventories[inventoryID].Items.Remove(slotID); + } + else inventories[inventoryID].Items[slotID] = item; + } + } + + /// + /// When connection has been lost, login was denied or played was kicked from the server + /// + public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) + { + world.Clear(); + + if (timeoutdetector != null) + { + timeoutdetector.Abort(); + timeoutdetector = null; + } + + bool will_restart = false; + + switch (reason) + { + case ChatBot.DisconnectReason.ConnectionLost: + message = "Connection has been lost."; + ConsoleIO.WriteLine(message); + break; + + case ChatBot.DisconnectReason.InGameKick: + ConsoleIO.WriteLine("Disconnected by Server :"); + ConsoleIO.WriteLineFormatted(message); + break; + + case ChatBot.DisconnectReason.LoginRejected: + ConsoleIO.WriteLine("Login failed :"); + ConsoleIO.WriteLineFormatted(message); + break; + + case ChatBot.DisconnectReason.UserLogout: + throw new InvalidOperationException("User-initiated logout should be done by calling Disconnect()"); + } + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + will_restart |= bot.OnDisconnect(reason, message); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + + if (!will_restart) + Program.HandleFailure(); + } + + /// + /// Called ~10 times per second by the protocol handler + /// + public void OnUpdate() + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.Update(); + bot.ProcessQueuedText(); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("Update: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + + if (terrainAndMovementsEnabled && locationReceived) + { + lock (locationLock) + { + for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps + { + if (yaw == null || pitch == null) + { + if (steps != null && steps.Count > 0) + { + location = steps.Dequeue(); + } + else if (path != null && path.Count > 0) + { + Location next = path.Dequeue(); + steps = Movement.Move2Steps(location, next, ref motionY); + UpdateLocation(location, next + new Location(0, 1, 0)); // Update yaw and pitch to look at next step + } + else + { + location = Movement.HandleGravity(world, location, ref motionY); + } + } + handler.SendLocationUpdate(location, Movement.IsOnGround(world, location), yaw, pitch); + } + // First 2 updates must be player position AND look, and player must not move (to conform with vanilla) + // Once yaw and pitch have been sent, switch back to location-only updates (without yaw and pitch) + yaw = null; + pitch = null; + } + } + + if (Settings.AutoRespawn && respawnTicks > 0) + { + respawnTicks--; + if (respawnTicks == 0) + SendRespawnPacket(); + } + } + + /// + /// Send a chat message or command to the server + /// + /// Text to send to the server + /// True if the text was sent with no error + public bool SendText(string text) + { + int maxLength = handler.GetMaxChatMessageLength(); + if (text.Length > maxLength) //Message is too long? + { + if (text[0] == '/') + { + //Send the first 100/256 chars of the command + text = text.Substring(0, maxLength); + return handler.SendChatMessage(text); + } + else + { + //Send the message splitted into several messages + while (text.Length > maxLength) + { + handler.SendChatMessage(text.Substring(0, maxLength)); + text = text.Substring(maxLength, text.Length - maxLength); + if (Settings.splitMessageDelay.TotalSeconds > 0) + Thread.Sleep(Settings.splitMessageDelay); + } + return handler.SendChatMessage(text); + } + } + else return handler.SendChatMessage(text); + } + + /// + /// Allow to respawn after death + /// + /// True if packet successfully sent + public bool SendRespawnPacket() + { + return handler.SendRespawnPacket(); + } + + /// + /// Triggered when a new player joins the game + /// + /// UUID of the player + /// Name of the player + public void OnPlayerJoin(Guid uuid, string name) + { + //Ignore placeholders eg 0000tab# from TabListPlus + if (!ChatBot.IsValidName(name)) + return; + + lock (onlinePlayers) + { + onlinePlayers[uuid] = name; + } + } + + /// + /// Triggered when a player has left the game + /// + /// UUID of the player + public void OnPlayerLeave(Guid uuid) + { + lock (onlinePlayers) + { + onlinePlayers.Remove(uuid); + } + } + + /// + /// Get a set of online player names + /// + /// Online player names + public string[] GetOnlinePlayers() + { + lock (onlinePlayers) + { + return onlinePlayers.Values.Distinct().ToArray(); + } + } + + /// + /// Get a dictionary of online player names and their corresponding UUID + /// + /// Dictionay of online players, key is UUID, value is Player name + public Dictionary GetOnlinePlayersWithUUID() + { + Dictionary uuid2Player = new Dictionary(); + lock (onlinePlayers) + { + foreach (Guid key in onlinePlayers.Keys) + { + uuid2Player.Add(key.ToString(), onlinePlayers[key]); + } + } + return uuid2Player; + } + + /// + /// Registers the given plugin channel for the given bot. + /// + /// The channel to register. + /// The bot to register the channel for. + public void RegisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + registeredBotPluginChannels[channel].Add(bot); + } + else + { + List bots = new List(); + bots.Add(bot); + registeredBotPluginChannels[channel] = bots; + SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + + /// + /// Unregisters the given plugin channel for the given bot. + /// + /// The channel to unregister. + /// The bot to unregister the channel for. + public void UnregisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + List registeredBots = registeredBotPluginChannels[channel]; + registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot)); + if (registeredBots.Count == 0) + { + registeredBotPluginChannels.Remove(channel); + SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + } + + /// + /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information + /// about plugin channels. + /// + /// The channel to send the packet on. + /// The payload for the packet. + /// Whether the packet should be sent even if the server or the client hasn't registered it yet. + /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. + public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) + { + if (!sendEvenIfNotRegistered) + { + if (!registeredBotPluginChannels.ContainsKey(channel)) + { + return false; + } + if (!registeredServerPluginChannels.Contains(channel)) + { + return false; + } + } + return handler.SendPluginChannelPacket(channel, data); + } + + /// + /// Called when a plugin channel message was sent from the server. + /// + /// The channel the message was sent on + /// The data from the channel + public void OnPluginChannelMessage(string channel, byte[] data) + { + if (channel == "REGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + if (!registeredServerPluginChannels.Contains(chan)) + { + registeredServerPluginChannels.Add(chan); + } + } + } + if (channel == "UNREGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + registeredServerPluginChannels.Remove(chan); + } + } + + if (registeredBotPluginChannels.ContainsKey(channel)) + { + foreach (ChatBot bot in registeredBotPluginChannels[channel]) + { + bot.OnPluginMessage(channel, data); + } + } + } + + /// + /// Called when an entity spawned + /// + public void OnSpawnEntity(Entity entity) + { + // The entity should not already exist, but if it does, let's consider the previous one is being destroyed + if (entities.ContainsKey(entity.ID)) + OnDestroyEntities(new[] { entity.ID }); + + entities.Add(entity.ID, entity); + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnEntitySpawn(entity); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntitySpawn: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + /// + /// Called when a player spawns or enters the client's render distance + /// + public void OnSpawnPlayer(int entityID, Guid uuid, Location location, byte Yaw, byte Pitch) + { + string playerName = null; + if (onlinePlayers.ContainsKey(uuid)) + playerName = onlinePlayers[uuid]; + Entity playerEntity = new Entity(entityID, EntityType.Player, location, uuid, playerName); + OnSpawnEntity(playerEntity); + } + + /// + /// Called when entities dead/despawn. + /// + public void OnDestroyEntities(int[] Entities) + { + foreach (int a in Entities) + { + if (entities.ContainsKey(a)) + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnEntityDespawn(entities[a]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityDespawn: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + entities.Remove(a); + } + } + } + + /// + /// Called when an entity's position changed within 8 block of its previous position. + /// + /// + /// + /// + /// + /// + public void OnEntityPosition(int EntityID, Double Dx, Double Dy, Double Dz,bool onGround) + { + if (entities.ContainsKey(EntityID)) + { + Location L = entities[EntityID].Location; + L.X += Dx; + L.Y += Dy; + L.Z += Dz; + entities[EntityID].Location = L; + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnEntityMove(entities[EntityID]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + } + + /// + /// Called when an entity moved over 8 block. + /// + /// + /// + /// + /// + /// + public void OnEntityTeleport(int EntityID, Double X, Double Y, Double Z, bool onGround) + { + if (entities.ContainsKey(EntityID)) + { + Location location = new Location(X, Y, Z); + entities[EntityID].Location = location; + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnEntityMove(entities[EntityID]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + + /// + /// Called when received entity properties from server. + /// + /// + /// + public void OnEntityProperties(int EntityID, Dictionary prop) + { + if(EntityID == playerEntityID) + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnPlayerProperty(prop); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnPlayerProperty: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + + /// + /// Called when server sent a Time Update packet. + /// + /// + /// + public void OnTimeUpdate(long WorldAge, long TimeOfDay) + { + // calculate server tps + if (lastAge != 0) + { + DateTime currentTime = DateTime.Now; + long tickDiff = WorldAge - lastAge; + Double tps = tickDiff / (currentTime - lastTime).TotalSeconds; + lastAge = WorldAge; + lastTime = currentTime; + if (tps <= 20.0 && tps >= 0.0 && serverTPS != tps) + { + serverTPS = tps; + // invoke ChatBot + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnServerTpsUpdate(tps); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnServerTpsUpdate: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + else + { + lastAge = WorldAge; + lastTime = DateTime.Now; + } + + } + + /// + /// Set client player's ID for later receiving player's own properties + /// + /// Player Entity ID + public void SetPlayerEntityID(int EntityID) + { + playerEntityID = EntityID; + } + + /// + /// Send the Entity Action packet with the Specified ID + /// + /// TRUE if the item was successfully used + public bool sendEntityAction(EntityActionType entityAction) + { + return handler.SendEntityAction(playerEntityID, (int) entityAction); + } + /// + /// Use the item currently in the player's hand + /// + /// TRUE if the item was successfully used + public bool UseItemOnHand() + { + return handler.SendUseItem(0); + } + + /// + /// Click a slot in the specified window + /// + /// TRUE if the slot was successfully clicked + public bool DoWindowAction(int windowId, int slotId, WindowActionType action) + { + Item item = null; + if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) + item = inventories[windowId].Items[slotId]; + + return handler.SendWindowAction(windowId, slotId, action, item); + } + + /// + /// Give Creative Mode items into regular/survival Player Inventory + /// + /// (obviously) requires to be in creative mode + /// Destination inventory slot + /// Item type + /// Item count + /// TRUE if item given successfully + public bool DoCreativeGive(int slot, ItemType itemType, int count) + { + return handler.SendCreativeInventoryAction(slot, itemType, count); + } + + /// + /// Plays animation (Player arm swing) + /// + /// 0 for left arm, 1 for right arm + /// TRUE if animation successfully done + public bool DoAnimation(int animation) + { + return handler.SendAnimation(animation); + } + + /// + /// Close the specified inventory window + /// + /// Window ID + /// TRUE if the window was successfully closed + public bool CloseInventory(int windowId) + { + if (windowId != 0 && inventories.ContainsKey(windowId)) + { + inventories.Remove(windowId); + return handler.SendCloseWindow(windowId); + } + return false; + } + + /// + /// Interact with an entity + /// + /// + /// 0: interact, 1: attack, 2: interact at + /// TRUE if interaction succeeded + public bool InteractEntity(int EntityID, int type) + { + return handler.SendInteractEntity(EntityID, type); + } + + /// + /// Place the block at hand in the Minecraft world + /// + /// Location to place block to + /// TRUE if successfully placed + public bool PlaceBlock(Location location) + { + //WORK IN PROGRESS. MAY NOT WORK YET + if (Settings.DebugMessages) + ConsoleIO.WriteLogLine(location.ToString()); + return handler.SendPlayerBlockPlacement(0, location, 1, 0.5f, 0.5f, 0.5f, false); + } + + /// + /// Change active slot in the player inventory + /// + /// Slot to activate (0 to 8) + /// TRUE if the slot was changed + public bool ChangeSlot(short slot) + { + if (slot >= 0 && slot <= 8) + { + CurrentSlot = Convert.ToByte(slot); + return handler.SendHeldItemChange(slot); + } + else + { + return false; + } + } + + /// + /// Called when client player's health changed, e.g. getting attack + /// + /// Player current health + public void OnUpdateHealth(float health, int food) + { + playerHealth = health; + playerFoodSaturation = food; + if (health <= 0) + { + if (Settings.AutoRespawn) + { + ConsoleIO.WriteLogLine("You are dead. Automatically respawning after 1 second."); + respawnTicks = 10; + } + else + { + ConsoleIO.WriteLogLine("You are dead. Type /respawn to respawn."); + } + } + foreach (ChatBot bot in bots.ToArray()) + bot.OnHealthUpdate(health, food); + } + + public void OnHeldItemChange(byte slot) + { + foreach (ChatBot bot in bots.ToArray()) + bot.OnHeldItemChange(slot); + CurrentSlot = slot; + } + } +} diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 8eda1b26..5d7e27ff 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -692,10 +692,12 @@ namespace MinecraftClient.Protocol.Handlers { return false; //Currently not implemented } + public bool SendAnimation(int animation) { return false; //Currently not implemented } + public bool SendCreativeInventoryAction(int slot, ItemType item, int count) { return false; //Currently not implemented diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 570db1a2..d0019c8e 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -1,1400 +1,1400 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Net.Sockets; -using System.Threading; -using MinecraftClient.Crypto; -using MinecraftClient.Proxy; -using System.Security.Cryptography; -using MinecraftClient.Mapping; -using MinecraftClient.Mapping.BlockPalettes; -using MinecraftClient.Mapping.EntityPalettes; -using MinecraftClient.Protocol.Handlers.Forge; -using MinecraftClient.Inventory; - -namespace MinecraftClient.Protocol.Handlers -{ - /// - /// Implementation for Minecraft 1.7.X+ Protocols - /// - /// - /// Typical update steps for implementing protocol changes for a new Minecraft version: - /// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol - /// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts - /// - If packet IDs were changed, also update getPacketIncomingType() and getPacketOutgoingID() inside Protocol18PacketTypes.cs - /// - Also see Material.cs and ItemType.cs for updating block and item data inside MCC - /// - class Protocol18Handler : IMinecraftCom - { - internal const int MC18Version = 47; - internal const int MC19Version = 107; - internal const int MC191Version = 108; - internal const int MC110Version = 210; - internal const int MC1112Version = 316; - internal const int MC112Version = 335; - internal const int MC1122Version = 340; - internal const int MC113Version = 393; - internal const int MC114Version = 477; - internal const int MC115Version = 573; - internal const int MC1152Version = 578; - - private int compression_treshold = 0; - private bool autocomplete_received = false; - private int autocomplete_transaction_id = 0; - private readonly List autocomplete_result = new List(); - private readonly Dictionary window_actions = new Dictionary(); - private bool login_phase = true; - private int protocolversion; - private int currentDimension; - - Protocol18Forge pForge; - Protocol18Terrain pTerrain; - IMinecraftComHandler handler; - EntityPalette entityPalette; - SocketWrapper socketWrapper; - DataTypes dataTypes; - Thread netRead; - - public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo forgeInfo) - { - ConsoleIO.SetAutoCompleteEngine(this); - ChatParser.InitTranslations(); - this.socketWrapper = new SocketWrapper(Client); - this.dataTypes = new DataTypes(protocolVersion); - this.protocolversion = protocolVersion; - this.handler = handler; - this.pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler); - this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); - - if (handler.GetTerrainEnabled() && protocolversion > MC1152Version) - { - ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); - handler.SetTerrainEnabled(false); - } - - if (handler.GetInventoryEnabled() && (protocolversion < MC110Version || protocolversion > MC1152Version)) - { - ConsoleIO.WriteLineFormatted("§8Inventories are currently not handled for that MC version."); - handler.SetInventoryEnabled(false); - } - - if (handler.GetEntityHandlingEnabled() && (protocolversion <= MC1122Version || protocolversion > MC1152Version)) - { - ConsoleIO.WriteLineFormatted("§8Entities are currently not handled for that MC version."); - handler.SetEntityHandlingEnabled(false); - } - - if (protocolversion >= MC113Version) - { - if (protocolVersion > MC1152Version && handler.GetTerrainEnabled()) - throw new NotImplementedException("Please update block types handling for this Minecraft version. See Material.cs"); - if (protocolVersion >= MC115Version) - Block.Palette = new Palette115(); - else if (protocolVersion >= MC114Version) - Block.Palette = new Palette114(); - else Block.Palette = new Palette113(); - } - else Block.Palette = new Palette112(); - - if (protocolversion >= MC114Version) - { - if (protocolversion > MC1152Version && handler.GetEntityHandlingEnabled()) - throw new NotImplementedException("Please update entity types handling for this Minecraft version. See EntityType.cs"); - if (protocolversion >= MC115Version) - entityPalette = new EntityPalette115(); - else entityPalette = new EntityPalette114(); - } - else entityPalette = new EntityPalette113(); - } - - /// - /// Separate thread. Network reading loop. - /// - private void Updater() - { - try - { - do - { - Thread.Sleep(100); - } - while (Update()); - } - catch (System.IO.IOException) { } - catch (SocketException) { } - catch (ObjectDisposedException) { } - - handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, ""); - } - - /// - /// Read data from the network. Should be called on a separate thread. - /// - /// FALSE if an error occured, TRUE otherwise. - private bool Update() - { - handler.OnUpdate(); - if (!socketWrapper.IsConnected()) - return false; - try - { - while (socketWrapper.HasDataAvailable()) - { - int packetID = 0; - Queue packetData = new Queue(); - ReadNextPacket(ref packetID, packetData); - HandlePacket(packetID, new Queue(packetData)); - } - } - catch (System.IO.IOException) { return false; } - catch (SocketException) { return false; } - catch (NullReferenceException) { return false; } - return true; - } - - /// - /// Read the next packet from the network - /// - /// will contain packet ID - /// will contain raw packet Data - internal void ReadNextPacket(ref int packetID, Queue packetData) - { - packetData.Clear(); - int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size - byte[] rawpacket = socketWrapper.ReadDataRAW(size);//Packet contents - for (int i = 0; i < rawpacket.Length; i++) - packetData.Enqueue(rawpacket[i]); - - //Handle packet decompression - if (protocolversion >= MC18Version - && compression_treshold > 0) - { - int sizeUncompressed = dataTypes.ReadNextVarInt(packetData); - if (sizeUncompressed != 0) // != 0 means compressed, let's decompress - { - byte[] toDecompress = packetData.ToArray(); - byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed); - packetData.Clear(); - for (int i = 0; i < uncompressed.Length; i++) - packetData.Enqueue(uncompressed[i]); - } - } - - packetID = dataTypes.ReadNextVarInt(packetData); //Packet ID - } - - /// - /// Handle the given packet - /// - /// Packet ID - /// Packet contents - /// TRUE if the packet was processed, FALSE if ignored or unknown - internal bool HandlePacket(int packetID, Queue packetData) - { - try - { - if (login_phase) - { - switch (packetID) //Packet IDs are different while logging in - { - case 0x03: - if (protocolversion >= MC18Version) - compression_treshold = dataTypes.ReadNextVarInt(packetData); - break; - default: - return false; //Ignored packet - } - } - // Regular in-game packets - else switch (Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion)) - { - case PacketIncomingType.KeepAlive: - SendPacket(PacketOutgoingType.KeepAlive, packetData); - handler.OnServerKeepAlive(); - break; - case PacketIncomingType.JoinGame: - handler.OnGameJoined(); - int playerEntityID = dataTypes.ReadNextInt(packetData); - handler.SetPlayerEntityID(playerEntityID); - dataTypes.ReadNextByte(packetData); - if (protocolversion >= MC191Version) - this.currentDimension = dataTypes.ReadNextInt(packetData); - else - this.currentDimension = (sbyte)dataTypes.ReadNextByte(packetData); - if (protocolversion < MC114Version) - dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below - if (protocolversion >= MC115Version) - dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above - dataTypes.ReadNextByte(packetData); - dataTypes.ReadNextString(packetData); - if (protocolversion >= MC114Version) - dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above - if (protocolversion >= MC18Version) - dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above - if (protocolversion >= MC115Version) - dataTypes.ReadNextBool(packetData); // Enable respawn screen - 1.15 and above - break; - case PacketIncomingType.ChatMessage: - string message = dataTypes.ReadNextString(packetData); - try - { - //Hide system messages or xp bar messages? - byte messageType = dataTypes.ReadNextByte(packetData); - if ((messageType == 1 && !Settings.DisplaySystemMessages) - || (messageType == 2 && !Settings.DisplayXPBarMessages)) - break; - } - catch (ArgumentOutOfRangeException) { /* No message type */ } - handler.OnTextReceived(message, true); - break; - case PacketIncomingType.Respawn: - this.currentDimension = dataTypes.ReadNextInt(packetData); - if (protocolversion < MC114Version) - dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below - if (protocolversion >= MC115Version) - dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above - dataTypes.ReadNextByte(packetData); - dataTypes.ReadNextString(packetData); - handler.OnRespawn(); - break; - case PacketIncomingType.PlayerPositionAndLook: - // These always need to be read, since we need the field after them for teleport confirm - double x = dataTypes.ReadNextDouble(packetData); - double y = dataTypes.ReadNextDouble(packetData); - double z = dataTypes.ReadNextDouble(packetData); - float yaw = dataTypes.ReadNextFloat(packetData); - float pitch = dataTypes.ReadNextFloat(packetData); - byte locMask = dataTypes.ReadNextByte(packetData); - - // entity handling require player pos for distance calculating - if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled()) - { - if (protocolversion >= MC18Version) - { - Location location = handler.GetCurrentLocation(); - location.X = (locMask & 1 << 0) != 0 ? location.X + x : x; - location.Y = (locMask & 1 << 1) != 0 ? location.Y + y : y; - location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z; - handler.UpdateLocation(location, yaw, pitch); - } - else handler.UpdateLocation(new Location(x, y, z), yaw, pitch); - } - - if (protocolversion >= MC19Version) - { - int teleportID = dataTypes.ReadNextVarInt(packetData); - // Teleport confirm packet - SendPacket(PacketOutgoingType.TeleportConfirm, dataTypes.GetVarInt(teleportID)); - } - break; - case PacketIncomingType.ChunkData: - if (handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - bool chunksContinuous = dataTypes.ReadNextBool(packetData); - ushort chunkMask = protocolversion >= MC19Version - ? (ushort)dataTypes.ReadNextVarInt(packetData) - : dataTypes.ReadNextUShort(packetData); - if (protocolversion < MC18Version) - { - ushort addBitmap = dataTypes.ReadNextUShort(packetData); - int compressedDataSize = dataTypes.ReadNextInt(packetData); - byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); - byte[] decompressed = ZlibUtils.Decompress(compressed); - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); - } - else - { - if (protocolversion >= MC114Version) - dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above - if (protocolversion >= MC115Version && chunksContinuous) - dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above - int dataSize = dataTypes.ReadNextVarInt(packetData); - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); - } - } - break; - case PacketIncomingType.MultiBlockChange: - if (handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - int recordCount = protocolversion < MC18Version - ? (int)dataTypes.ReadNextShort(packetData) - : dataTypes.ReadNextVarInt(packetData); - - for (int i = 0; i < recordCount; i++) - { - byte locationXZ; - ushort blockIdMeta; - int blockY; - - if (protocolversion < MC18Version) - { - blockIdMeta = dataTypes.ReadNextUShort(packetData); - blockY = (ushort)dataTypes.ReadNextByte(packetData); - locationXZ = dataTypes.ReadNextByte(packetData); - } - else - { - locationXZ = dataTypes.ReadNextByte(packetData); - blockY = (ushort)dataTypes.ReadNextByte(packetData); - blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData); - } - - int blockX = locationXZ >> 4; - int blockZ = locationXZ & 0x0F; - Block block = new Block(blockIdMeta); - handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); - } - } - break; - case PacketIncomingType.BlockChange: - if (handler.GetTerrainEnabled()) - { - if (protocolversion < MC18Version) - { - int blockX = dataTypes.ReadNextInt(packetData); - int blockY = dataTypes.ReadNextByte(packetData); - int blockZ = dataTypes.ReadNextInt(packetData); - short blockId = (short)dataTypes.ReadNextVarInt(packetData); - byte blockMeta = dataTypes.ReadNextByte(packetData); - handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta)); - } - else handler.GetWorld().SetBlock(dataTypes.ReadNextLocation(packetData), new Block((ushort)dataTypes.ReadNextVarInt(packetData))); - } - break; - case PacketIncomingType.MapChunkBulk: - if (protocolversion < MC19Version && handler.GetTerrainEnabled()) - { - int chunkCount; - bool hasSkyLight; - Queue chunkData = packetData; - - //Read global fields - if (protocolversion < MC18Version) - { - chunkCount = dataTypes.ReadNextShort(packetData); - int compressedDataSize = dataTypes.ReadNextInt(packetData); - hasSkyLight = dataTypes.ReadNextBool(packetData); - byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); - byte[] decompressed = ZlibUtils.Decompress(compressed); - chunkData = new Queue(decompressed); - } - else - { - hasSkyLight = dataTypes.ReadNextBool(packetData); - chunkCount = dataTypes.ReadNextVarInt(packetData); - } - - //Read chunk records - int[] chunkXs = new int[chunkCount]; - int[] chunkZs = new int[chunkCount]; - ushort[] chunkMasks = new ushort[chunkCount]; - ushort[] addBitmaps = new ushort[chunkCount]; - for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) - { - chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); - chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); - chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData); - addBitmaps[chunkColumnNo] = protocolversion < MC18Version - ? dataTypes.ReadNextUShort(packetData) - : (ushort)0; - } - - //Process chunk records - for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) - pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, currentDimension, chunkData); - } - break; - case PacketIncomingType.UnloadChunk: - if (protocolversion >= MC19Version && handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - handler.GetWorld()[chunkX, chunkZ] = null; - } - break; - case PacketIncomingType.PlayerListUpdate: - if (protocolversion >= MC18Version) - { - int action = dataTypes.ReadNextVarInt(packetData); - int numActions = dataTypes.ReadNextVarInt(packetData); - for (int i = 0; i < numActions; i++) - { - Guid uuid = dataTypes.ReadNextUUID(packetData); - switch (action) - { - case 0x00: //Player Join - string name = dataTypes.ReadNextString(packetData); - int propNum = dataTypes.ReadNextVarInt(packetData); - for (int p = 0; p < propNum; p++) - { - string key = dataTypes.ReadNextString(packetData); - string val = dataTypes.ReadNextString(packetData); - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - } - dataTypes.ReadNextVarInt(packetData); - dataTypes.ReadNextVarInt(packetData); - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - handler.OnPlayerJoin(uuid, name); - break; - case 0x01: //Update gamemode - case 0x02: //Update latency - dataTypes.ReadNextVarInt(packetData); - break; - case 0x03: //Update display name - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - break; - case 0x04: //Player Leave - handler.OnPlayerLeave(uuid); - break; - default: - //Unknown player list item type - break; - } - } - } - else //MC 1.7.X does not provide UUID in tab-list updates - { - string name = dataTypes.ReadNextString(packetData); - bool online = dataTypes.ReadNextBool(packetData); - short ping = dataTypes.ReadNextShort(packetData); - Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); - if (online) - handler.OnPlayerJoin(FakeUUID, name); - else handler.OnPlayerLeave(FakeUUID); - } - break; - case PacketIncomingType.TabCompleteResult: - if (protocolversion >= MC113Version) - { - autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData); - dataTypes.ReadNextVarInt(packetData); // Start of text to replace - dataTypes.ReadNextVarInt(packetData); // Length of text to replace - } - - int autocomplete_count = dataTypes.ReadNextVarInt(packetData); - autocomplete_result.Clear(); - - for (int i = 0; i < autocomplete_count; i++) - { - autocomplete_result.Add(dataTypes.ReadNextString(packetData)); - if (protocolversion >= MC113Version) - { - // Skip optional tooltip for each tab-complete result - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - } - } - - autocomplete_received = true; - break; - case PacketIncomingType.PluginMessage: - String channel = dataTypes.ReadNextString(packetData); - // Length is unneeded as the whole remaining packetData is the entire payload of the packet. - if (protocolversion < MC18Version) - pForge.ReadNextVarShort(packetData); - handler.OnPluginChannelMessage(channel, packetData.ToArray()); - return pForge.HandlePluginMessage(channel, packetData, ref currentDimension); - case PacketIncomingType.KickPacket: - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; - case PacketIncomingType.NetworkCompressionTreshold: - if (protocolversion >= MC18Version && protocolversion < MC19Version) - compression_treshold = dataTypes.ReadNextVarInt(packetData); - break; - case PacketIncomingType.OpenWindow: - if (handler.GetInventoryEnabled()) - { - if (protocolversion < MC114Version) - { - // MC 1.13 or lower - byte windowID = dataTypes.ReadNextByte(packetData); - string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper(); - ContainerTypeOld inventoryType = (ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type); - string title = dataTypes.ReadNextString(packetData); - byte slots = dataTypes.ReadNextByte(packetData); - Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title)); - handler.OnInventoryOpen(windowID, inventory); - } - else - { - // MC 1.14 or greater - int windowID = dataTypes.ReadNextVarInt(packetData); - int windowType = dataTypes.ReadNextVarInt(packetData); - string title = dataTypes.ReadNextString(packetData); - Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title)); - handler.OnInventoryOpen(windowID, inventory); - } - } - break; - case PacketIncomingType.CloseWindow: - if (handler.GetInventoryEnabled()) - { - byte windowID = dataTypes.ReadNextByte(packetData); - lock (window_actions) { window_actions[windowID] = 0; } - handler.OnInventoryClose(windowID); - } - break; - case PacketIncomingType.WindowItems: - if (handler.GetInventoryEnabled()) - { - byte windowId = dataTypes.ReadNextByte(packetData); - short elements = dataTypes.ReadNextShort(packetData); - Dictionary inventorySlots = new Dictionary(); - for (short slotId = 0; slotId < elements; slotId++) - { - Item item = dataTypes.ReadNextItemSlot(packetData); - if (item != null) - inventorySlots[slotId] = item; - } - handler.OnWindowItems(windowId, inventorySlots); - } - break; - case PacketIncomingType.SetSlot: - if (handler.GetInventoryEnabled()) - { - byte windowID = dataTypes.ReadNextByte(packetData); - short slotID = dataTypes.ReadNextShort(packetData); - Item item = dataTypes.ReadNextItemSlot(packetData); - handler.OnSetSlot(windowID, slotID, item); - } - break; - case PacketIncomingType.ResourcePackSend: - string url = dataTypes.ReadNextString(packetData); - string hash = dataTypes.ReadNextString(packetData); - //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory - byte[] responseHeader = new byte[0]; - if (protocolversion < MC110Version) //MC 1.10 does not include resource pack hash in responses - responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); - SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack - SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded - break; - case PacketIncomingType.SpawnEntity: - if (handler.GetEntityHandlingEnabled()) - { - Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, false); - handler.OnSpawnEntity(entity); - } - break; - case PacketIncomingType.SpawnLivingEntity: - if (handler.GetEntityHandlingEnabled()) - { - Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, true); - // packet before 1.15 has metadata at the end - // this is not handled in dataTypes.ReadNextEntity() - // we are simply ignoring leftover data in packet - handler.OnSpawnEntity(entity); - } - break; - case PacketIncomingType.SpawnPlayer: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Guid UUID = dataTypes.ReadNextUUID(packetData); - double X = dataTypes.ReadNextDouble(packetData); - double Y = dataTypes.ReadNextDouble(packetData); - double Z = dataTypes.ReadNextDouble(packetData); - byte Yaw = dataTypes.ReadNextByte(packetData); - byte Pitch = dataTypes.ReadNextByte(packetData); - - Location EntityLocation = new Location(X, Y, Z); - - handler.OnSpawnPlayer(EntityID, UUID, EntityLocation, Yaw, Pitch); - } - break; - case PacketIncomingType.DestroyEntities: - if (handler.GetEntityHandlingEnabled()) - { - int EntityCount = dataTypes.ReadNextVarInt(packetData); - int[] EntitiesList = new int[EntityCount]; - for (int i = 0; i < EntityCount; i++) - { - EntitiesList[i] = dataTypes.ReadNextVarInt(packetData); - } - handler.OnDestroyEntities(EntitiesList); - } - break; - case PacketIncomingType.EntityPosition: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - bool OnGround = dataTypes.ReadNextBool(packetData); - DeltaX = DeltaX / (128 * 32); - DeltaY = DeltaY / (128 * 32); - DeltaZ = DeltaZ / (128 * 32); - handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); - } - break; - case PacketIncomingType.EntityPositionAndRotation: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - byte _yaw = dataTypes.ReadNextByte(packetData); - byte _pitch = dataTypes.ReadNextByte(packetData); - bool OnGround = dataTypes.ReadNextBool(packetData); - DeltaX = DeltaX / (128 * 32); - DeltaY = DeltaY / (128 * 32); - DeltaZ = DeltaZ / (128 * 32); - handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); - } - break; - case PacketIncomingType.EntityProperties: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - int NumberOfProperties = dataTypes.ReadNextInt(packetData); - Dictionary keys = new Dictionary(); - for (int i = 0; i < NumberOfProperties; i++) - { - string _key = dataTypes.ReadNextString(packetData); - Double _value = dataTypes.ReadNextDouble(packetData); - - List op0 = new List(); - List op1 = new List(); - List op2 = new List(); - int NumberOfModifiers = dataTypes.ReadNextVarInt(packetData); - for (int j = 0; j < NumberOfModifiers; j++) - { - dataTypes.ReadNextUUID(packetData); - Double amount = dataTypes.ReadNextDouble(packetData); - byte operation = dataTypes.ReadNextByte(packetData); - switch (operation) - { - case 0: op0.Add(amount); break; - case 1: op1.Add(amount); break; - case 2: op2.Add(amount + 1); break; - } - } - if (op0.Count > 0) _value += op0.Sum(); - if (op1.Count > 0) _value *= 1 + op1.Sum(); - if (op2.Count > 0) _value *= op2.Aggregate((a, _x) => a * _x); - keys.Add(_key, _value); - } - handler.OnEntityProperties(EntityID, keys); - } - break; - case PacketIncomingType.TimeUpdate: - long WorldAge = dataTypes.ReadNextLong(packetData); - long TimeOfday = dataTypes.ReadNextLong(packetData); - handler.OnTimeUpdate(WorldAge, TimeOfday); - break; - case PacketIncomingType.EntityTeleport: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double X = dataTypes.ReadNextDouble(packetData); - Double Y = dataTypes.ReadNextDouble(packetData); - Double Z = dataTypes.ReadNextDouble(packetData); - byte EntityYaw = dataTypes.ReadNextByte(packetData); - byte EntityPitch = dataTypes.ReadNextByte(packetData); - bool OnGround = dataTypes.ReadNextBool(packetData); - handler.OnEntityTeleport(EntityID, X, Y, Z, OnGround); - } - break; - case PacketIncomingType.UpdateHealth: - float health = dataTypes.ReadNextFloat(packetData); - int food = dataTypes.ReadNextVarInt(packetData); - // Food Saturation, not useful - dataTypes.ReadNextFloat(packetData); - handler.OnUpdateHealth(health, food); - break; - case PacketIncomingType.HeldItemChange: - byte slot = dataTypes.ReadNextByte(packetData); - handler.OnHeldItemChange(slot); - break; - default: - return false; //Ignored packet - } - return true; //Packet processed - } - catch (Exception innerException) - { - if (innerException is SocketException || innerException.InnerException is SocketException) - throw; //Connection lost rather than invalid data - throw new System.IO.InvalidDataException( - String.Format("Failed to process incoming packet of type {0}. (PacketID: {1}, Protocol: {2}, LoginPhase: {3}, InnerException: {4}).", - Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion), - packetID, - protocolversion, - login_phase, - innerException.GetType()), - innerException); - } - } - - /// - /// Start the updating thread. Should be called after login success. - /// - private void StartUpdating() - { - netRead = new Thread(new ThreadStart(Updater)); - netRead.Name = "ProtocolPacketHandler"; - netRead.Start(); - } - - /// - /// Disconnect from the server, cancel network reading. - /// - public void Dispose() - { - try - { - if (netRead != null) - { - netRead.Abort(); - socketWrapper.Disconnect(); - } - } - catch { } - } - - /// - /// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically. - /// - /// packet type - /// packet Data - private void SendPacket(PacketOutgoingType packet, IEnumerable packetData) - { - SendPacket(Protocol18PacketTypes.GetPacketOutgoingID(packet, protocolversion), packetData); - } - - /// - /// Send a packet to the server. Compression and encryption will be handled automatically. - /// - /// packet ID - /// packet Data - private void SendPacket(int packetID, IEnumerable packetData) - { - //The inner packet - byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray()); - - if (compression_treshold > 0) //Compression enabled? - { - if (the_packet.Length >= compression_treshold) //Packet long enough for compressing? - { - byte[] compressed_packet = ZlibUtils.Compress(the_packet); - the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet); - } - else - { - byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet) - the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet); - } - } - - socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet)); - } - - /// - /// Do the Minecraft login. - /// - /// True if login successful - public bool Login() - { - byte[] protocol_version = dataTypes.GetVarInt(protocolversion); - string server_address = pForge.GetServerAddress(handler.GetServerHost()); - byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port); - byte[] next_state = dataTypes.GetVarInt(2); - byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state); - - SendPacket(0x00, handshake_packet); - - byte[] login_packet = dataTypes.GetString(handler.GetUsername()); - - SendPacket(0x00, login_packet); - - int packetID = -1; - Queue packetData = new Queue(); - while (true) - { - ReadNextPacket(ref packetID, packetData); - if (packetID == 0x00) //Login rejected - { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; - } - else if (packetID == 0x01) //Encryption request - { - string serverID = dataTypes.ReadNextString(packetData); - byte[] Serverkey = dataTypes.ReadNextByteArray(packetData); - byte[] token = dataTypes.ReadNextByteArray(packetData); - return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); - } - else if (packetID == 0x02) //Login successful - { - ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); - login_phase = false; - - if (!pForge.CompleteForgeHandshake()) - { - ConsoleIO.WriteLineFormatted("§8Forge Login Handshake did not complete successfully"); - return false; - } - - StartUpdating(); - return true; //No need to check session or start encryption - } - else HandlePacket(packetID, packetData); - } - } - - /// - /// Start network encryption. Automatically called by Login() if the server requests encryption. - /// - /// True if encryption was successful - private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey) - { - System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey); - byte[] secretKey = CryptoHandler.GenerateAESPrivateKey(); - - if (Settings.DebugMessages) - ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); - - if (serverIDhash != "-") - { - Console.WriteLine("Checking Session..."); - if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey))) - { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session."); - return false; - } - } - - //Encrypt the data - byte[] key_enc = dataTypes.GetArray(RSAService.Encrypt(secretKey, false)); - byte[] token_enc = dataTypes.GetArray(RSAService.Encrypt(token, false)); - - //Encryption Response packet - SendPacket(0x01, dataTypes.ConcatBytes(key_enc, token_enc)); - - //Start client-side encryption - socketWrapper.SwitchToEncrypted(secretKey); - - //Process the next packet - int packetID = -1; - Queue packetData = new Queue(); - while (true) - { - ReadNextPacket(ref packetID, packetData); - if (packetID == 0x00) //Login rejected - { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; - } - else if (packetID == 0x02) //Login successful - { - login_phase = false; - - if (!pForge.CompleteForgeHandshake()) - { - ConsoleIO.WriteLineFormatted("§8Forge StartEncryption Handshake did not complete successfully"); - return false; - } - - StartUpdating(); - return true; - } - else HandlePacket(packetID, packetData); - } - } - - /// - /// Get max length for chat messages - /// - /// Max length, in characters - public int GetMaxChatMessageLength() - { - return protocolversion > MC110Version - ? 256 - : 100; - } - - /// - /// Send a chat message to the server - /// - /// Message - /// True if properly sent - public bool SendChatMessage(string message) - { - if (String.IsNullOrEmpty(message)) - return true; - try - { - byte[] message_packet = dataTypes.GetString(message); - SendPacket(PacketOutgoingType.ChatMessage, message_packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendEntityAction(int PlayerEntityID, int ActionID) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(PlayerEntityID)); - fields.AddRange(dataTypes.GetVarInt(ActionID)); - fields.AddRange(dataTypes.GetVarInt(0)); - SendPacket(PacketOutgoingType.EntityAction, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - /// - /// Send a respawn packet to the server - /// - /// True if properly sent - public bool SendRespawnPacket() - { - try - { - SendPacket(PacketOutgoingType.ClientStatus, new byte[] { 0 }); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - /// - /// Tell the server what client is being used to connect to the server - /// - /// Client string describing the client - /// True if brand info was successfully sent - public bool SendBrandInfo(string brandInfo) - { - if (String.IsNullOrEmpty(brandInfo)) - return false; - // Plugin channels were significantly changed between Minecraft 1.12 and 1.13 - // https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels - if (protocolversion >= MC113Version) - { - return SendPluginChannelPacket("minecraft:brand", dataTypes.GetString(brandInfo)); - } - else - { - return SendPluginChannelPacket("MC|Brand", dataTypes.GetString(brandInfo)); - } - } - - /// - /// Inform the server of the client's Minecraft settings - /// - /// Client language eg en_US - /// View distance, in chunks - /// Game difficulty (client-side...) - /// Chat mode (allows muting yourself) - /// Show chat colors - /// Show skin layers - /// 1.9+ main hand - /// True if client settings were successfully sent - public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode, bool chatColors, byte skinParts, byte mainHand) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetString(language)); - fields.Add(viewDistance); - fields.AddRange(protocolversion >= MC19Version - ? dataTypes.GetVarInt(chatMode) - : new byte[] { chatMode }); - fields.Add(chatColors ? (byte)1 : (byte)0); - if (protocolversion < MC18Version) - { - fields.Add(difficulty); - fields.Add((byte)(skinParts & 0x1)); //show cape - } - else fields.Add(skinParts); - if (protocolversion >= MC19Version) - fields.AddRange(dataTypes.GetVarInt(mainHand)); - SendPacket(PacketOutgoingType.ClientSettings, fields); - } - catch (SocketException) { } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - return false; - } - - /// - /// Send a location update to the server - /// - /// The new location of the player - /// True if the player is on the ground - /// Optional new yaw for updating player look - /// Optional new pitch for updating player look - /// True if the location update was successfully sent - public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null) - { - if (handler.GetTerrainEnabled()) - { - byte[] yawpitch = new byte[0]; - PacketOutgoingType packetType = PacketOutgoingType.PlayerPosition; - - if (yaw.HasValue && pitch.HasValue) - { - yawpitch = dataTypes.ConcatBytes(dataTypes.GetFloat(yaw.Value), dataTypes.GetFloat(pitch.Value)); - packetType = PacketOutgoingType.PlayerPositionAndLook; - } - - try - { - SendPacket(packetType, dataTypes.ConcatBytes( - dataTypes.GetDouble(location.X), - dataTypes.GetDouble(location.Y), - protocolversion < MC18Version - ? dataTypes.GetDouble(location.Y + 1.62) - : new byte[0], - dataTypes.GetDouble(location.Z), - yawpitch, - new byte[] { onGround ? (byte)1 : (byte)0 })); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - else return false; - } - - /// - /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically - /// - /// Channel to send packet on - /// packet Data - public bool SendPluginChannelPacket(string channel, byte[] data) - { - try - { - // In 1.7, length needs to be included. - // In 1.8, it must not be. - if (protocolversion < MC18Version) - { - byte[] length = BitConverter.GetBytes((short)data.Length); - Array.Reverse(length); - - SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data)); - } - else - { - SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data)); - } - - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - /// - /// Disconnect from the server - /// - public void Disconnect() - { - socketWrapper.Disconnect(); - } - - /// - /// Autocomplete text while typing username or command - /// - /// Text behind cursor - /// Completed text - IEnumerable IAutoComplete.AutoComplete(string BehindCursor) - { - if (String.IsNullOrEmpty(BehindCursor)) - return new string[] { }; - - byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); - byte[] assume_command = new byte[] { 0x00 }; - byte[] has_position = new byte[] { 0x00 }; - - byte[] tabcomplete_packet = new byte[] { }; - - if (protocolversion >= MC18Version) - { - if (protocolversion >= MC113Version) - { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id); - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); - } - else - { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); - - if (protocolversion >= MC19Version) - { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command); - } - - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position); - } - } - else - { - tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor)); - } - - autocomplete_received = false; - autocomplete_result.Clear(); - autocomplete_result.Add(BehindCursor); - SendPacket(PacketOutgoingType.TabComplete, tabcomplete_packet); - - int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) - while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; } - if (autocomplete_result.Count > 0) - ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false); - return autocomplete_result; - } - - /// - /// Ping a Minecraft server to get information about the server - /// - /// True if ping was successful - public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo) - { - string version = ""; - TcpClient tcp = ProxyHandler.newTcpClient(host, port); - tcp.ReceiveBufferSize = 1024 * 1024; - SocketWrapper socketWrapper = new SocketWrapper(tcp); - DataTypes dataTypes = new DataTypes(MC18Version); - - byte[] packet_id = dataTypes.GetVarInt(0); - byte[] protocol_version = dataTypes.GetVarInt(-1); - byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port); - byte[] next_state = dataTypes.GetVarInt(1); - byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state); - byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet); - - socketWrapper.SendDataRAW(tosend); - - byte[] status_request = dataTypes.GetVarInt(0); - byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request); - - socketWrapper.SendDataRAW(request_packet); - - int packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); - if (packetLength > 0) //Read Response length - { - Queue packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); - if (dataTypes.ReadNextVarInt(packetData) == 0x00) //Read Packet ID - { - string result = dataTypes.ReadNextString(packetData); //Get the Json data - - if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) - { - Json.JSONData jsonData = Json.ParseJson(result); - if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) - { - Json.JSONData versionData = jsonData.Properties["version"]; - - //Retrieve display name of the Minecraft version - if (versionData.Properties.ContainsKey("name")) - version = versionData.Properties["name"].StringValue; - - //Retrieve protocol version number for handling this server - if (versionData.Properties.ContainsKey("protocol")) - protocolversion = dataTypes.Atoi(versionData.Properties["protocol"].StringValue); - - // Check for forge on the server. - Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo); - - ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ").")); - - return true; - } - } - } - } - return false; - } - - /// - /// Send an Interact Entity Packet to server - /// - /// - /// - /// - public bool SendInteractEntity(int EntityID, int type) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); - SendPacket(PacketOutgoingType.InteractEntity, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - // TODO: Interact at block location (e.g. chest minecart) - public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); - fields.AddRange(dataTypes.GetFloat(X)); - fields.AddRange(dataTypes.GetFloat(Y)); - fields.AddRange(dataTypes.GetFloat(Z)); - fields.AddRange(dataTypes.GetVarInt(hand)); - SendPacket(PacketOutgoingType.InteractEntity, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) - { - return false; - } - - public bool SendUseItem(int hand) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetVarInt(hand)); - SendPacket(PacketOutgoingType.UseItem, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendPlayerBlockPlacement(int hand, Location location, int face, float CursorX, float CursorY, float CursorZ, bool insideBlock) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetVarInt(hand)); - packet.AddRange(dataTypes.GetLocation(location)); - packet.AddRange(dataTypes.GetVarInt(face)); - packet.AddRange(dataTypes.GetFloat(CursorX)); - packet.AddRange(dataTypes.GetFloat(CursorY)); - packet.AddRange(dataTypes.GetFloat(CursorZ)); - packet.Add(Convert.ToByte(insideBlock ? 1 : 0)); - SendPacket(PacketOutgoingType.PlayerBlockPlacement, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendHeldItemChange(short slot) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetShort(slot)); - SendPacket(PacketOutgoingType.HeldItemChange, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item) - { - try - { - short actionNumber; - lock (window_actions) - { - if (!window_actions.ContainsKey(windowId)) - window_actions[windowId] = 0; - actionNumber = (short)(window_actions[windowId] + 1); - window_actions[windowId] = actionNumber; - } - - byte button = 0; - byte mode = 0; - switch (action) - { - case WindowActionType.LeftClick: button = 0; break; - case WindowActionType.RightClick: button = 1; break; - case WindowActionType.MiddleClick: button = 2; mode = 3; break; - case WindowActionType.DropItem: - button = 0; - mode = 4; - item = new Item(-1, 0, null); - Container inventory = handler.GetInventory(windowId); - if (inventory.Items.ContainsKey(slotId)) - inventory.Items[slotId].Count--; // server won't update us after dropped - break; - case WindowActionType.DropItemStack: - button = 1; - mode = 4; - item = new Item(-1, 0, null); - inventory = handler.GetInventory(windowId); - inventory.Items.Remove(slotId); // server won't update us after dropped - break; - } - - List packet = new List(); - packet.Add((byte)windowId); - packet.AddRange(dataTypes.GetShort((short)slotId)); - packet.Add(button); - packet.AddRange(dataTypes.GetShort(actionNumber)); - - if (protocolversion >= MC19Version) - packet.AddRange(dataTypes.GetVarInt(mode)); - else packet.Add(mode); - - packet.AddRange(dataTypes.GetItemSlot(item)); - - SendPacket(PacketOutgoingType.ClickWindow, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendCreativeInventoryAction(int slot, ItemType itemType, int count) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetShort((short)slot)); - packet.AddRange(dataTypes.GetItemSlot(new Item((int)itemType, count, null))); - - SendPacket(PacketOutgoingType.CreativeInventoryAction, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendAnimation(int animation) - { - try - { +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Threading; +using MinecraftClient.Crypto; +using MinecraftClient.Proxy; +using System.Security.Cryptography; +using MinecraftClient.Mapping; +using MinecraftClient.Mapping.BlockPalettes; +using MinecraftClient.Mapping.EntityPalettes; +using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Inventory; + +namespace MinecraftClient.Protocol.Handlers +{ + /// + /// Implementation for Minecraft 1.7.X+ Protocols + /// + /// + /// Typical update steps for implementing protocol changes for a new Minecraft version: + /// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol + /// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts + /// - If packet IDs were changed, also update getPacketIncomingType() and getPacketOutgoingID() inside Protocol18PacketTypes.cs + /// - Also see Material.cs and ItemType.cs for updating block and item data inside MCC + /// + class Protocol18Handler : IMinecraftCom + { + internal const int MC18Version = 47; + internal const int MC19Version = 107; + internal const int MC191Version = 108; + internal const int MC110Version = 210; + internal const int MC1112Version = 316; + internal const int MC112Version = 335; + internal const int MC1122Version = 340; + internal const int MC113Version = 393; + internal const int MC114Version = 477; + internal const int MC115Version = 573; + internal const int MC1152Version = 578; + + private int compression_treshold = 0; + private bool autocomplete_received = false; + private int autocomplete_transaction_id = 0; + private readonly List autocomplete_result = new List(); + private readonly Dictionary window_actions = new Dictionary(); + private bool login_phase = true; + private int protocolversion; + private int currentDimension; + + Protocol18Forge pForge; + Protocol18Terrain pTerrain; + IMinecraftComHandler handler; + EntityPalette entityPalette; + SocketWrapper socketWrapper; + DataTypes dataTypes; + Thread netRead; + + public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo forgeInfo) + { + ConsoleIO.SetAutoCompleteEngine(this); + ChatParser.InitTranslations(); + this.socketWrapper = new SocketWrapper(Client); + this.dataTypes = new DataTypes(protocolVersion); + this.protocolversion = protocolVersion; + this.handler = handler; + this.pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler); + this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); + + if (handler.GetTerrainEnabled() && protocolversion > MC1152Version) + { + ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); + handler.SetTerrainEnabled(false); + } + + if (handler.GetInventoryEnabled() && (protocolversion < MC110Version || protocolversion > MC1152Version)) + { + ConsoleIO.WriteLineFormatted("§8Inventories are currently not handled for that MC version."); + handler.SetInventoryEnabled(false); + } + + if (handler.GetEntityHandlingEnabled() && (protocolversion <= MC1122Version || protocolversion > MC1152Version)) + { + ConsoleIO.WriteLineFormatted("§8Entities are currently not handled for that MC version."); + handler.SetEntityHandlingEnabled(false); + } + + if (protocolversion >= MC113Version) + { + if (protocolVersion > MC1152Version && handler.GetTerrainEnabled()) + throw new NotImplementedException("Please update block types handling for this Minecraft version. See Material.cs"); + if (protocolVersion >= MC115Version) + Block.Palette = new Palette115(); + else if (protocolVersion >= MC114Version) + Block.Palette = new Palette114(); + else Block.Palette = new Palette113(); + } + else Block.Palette = new Palette112(); + + if (protocolversion >= MC114Version) + { + if (protocolversion > MC1152Version && handler.GetEntityHandlingEnabled()) + throw new NotImplementedException("Please update entity types handling for this Minecraft version. See EntityType.cs"); + if (protocolversion >= MC115Version) + entityPalette = new EntityPalette115(); + else entityPalette = new EntityPalette114(); + } + else entityPalette = new EntityPalette113(); + } + + /// + /// Separate thread. Network reading loop. + /// + private void Updater() + { + try + { + do + { + Thread.Sleep(100); + } + while (Update()); + } + catch (System.IO.IOException) { } + catch (SocketException) { } + catch (ObjectDisposedException) { } + + handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, ""); + } + + /// + /// Read data from the network. Should be called on a separate thread. + /// + /// FALSE if an error occured, TRUE otherwise. + private bool Update() + { + handler.OnUpdate(); + if (!socketWrapper.IsConnected()) + return false; + try + { + while (socketWrapper.HasDataAvailable()) + { + int packetID = 0; + Queue packetData = new Queue(); + ReadNextPacket(ref packetID, packetData); + HandlePacket(packetID, new Queue(packetData)); + } + } + catch (System.IO.IOException) { return false; } + catch (SocketException) { return false; } + catch (NullReferenceException) { return false; } + return true; + } + + /// + /// Read the next packet from the network + /// + /// will contain packet ID + /// will contain raw packet Data + internal void ReadNextPacket(ref int packetID, Queue packetData) + { + packetData.Clear(); + int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size + byte[] rawpacket = socketWrapper.ReadDataRAW(size);//Packet contents + for (int i = 0; i < rawpacket.Length; i++) + packetData.Enqueue(rawpacket[i]); + + //Handle packet decompression + if (protocolversion >= MC18Version + && compression_treshold > 0) + { + int sizeUncompressed = dataTypes.ReadNextVarInt(packetData); + if (sizeUncompressed != 0) // != 0 means compressed, let's decompress + { + byte[] toDecompress = packetData.ToArray(); + byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed); + packetData.Clear(); + for (int i = 0; i < uncompressed.Length; i++) + packetData.Enqueue(uncompressed[i]); + } + } + + packetID = dataTypes.ReadNextVarInt(packetData); //Packet ID + } + + /// + /// Handle the given packet + /// + /// Packet ID + /// Packet contents + /// TRUE if the packet was processed, FALSE if ignored or unknown + internal bool HandlePacket(int packetID, Queue packetData) + { + try + { + if (login_phase) + { + switch (packetID) //Packet IDs are different while logging in + { + case 0x03: + if (protocolversion >= MC18Version) + compression_treshold = dataTypes.ReadNextVarInt(packetData); + break; + default: + return false; //Ignored packet + } + } + // Regular in-game packets + else switch (Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion)) + { + case PacketIncomingType.KeepAlive: + SendPacket(PacketOutgoingType.KeepAlive, packetData); + handler.OnServerKeepAlive(); + break; + case PacketIncomingType.JoinGame: + handler.OnGameJoined(); + int playerEntityID = dataTypes.ReadNextInt(packetData); + handler.SetPlayerEntityID(playerEntityID); + dataTypes.ReadNextByte(packetData); + if (protocolversion >= MC191Version) + this.currentDimension = dataTypes.ReadNextInt(packetData); + else + this.currentDimension = (sbyte)dataTypes.ReadNextByte(packetData); + if (protocolversion < MC114Version) + dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below + if (protocolversion >= MC115Version) + dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above + dataTypes.ReadNextByte(packetData); + dataTypes.ReadNextString(packetData); + if (protocolversion >= MC114Version) + dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above + if (protocolversion >= MC18Version) + dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above + if (protocolversion >= MC115Version) + dataTypes.ReadNextBool(packetData); // Enable respawn screen - 1.15 and above + break; + case PacketIncomingType.ChatMessage: + string message = dataTypes.ReadNextString(packetData); + try + { + //Hide system messages or xp bar messages? + byte messageType = dataTypes.ReadNextByte(packetData); + if ((messageType == 1 && !Settings.DisplaySystemMessages) + || (messageType == 2 && !Settings.DisplayXPBarMessages)) + break; + } + catch (ArgumentOutOfRangeException) { /* No message type */ } + handler.OnTextReceived(message, true); + break; + case PacketIncomingType.Respawn: + this.currentDimension = dataTypes.ReadNextInt(packetData); + if (protocolversion < MC114Version) + dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below + if (protocolversion >= MC115Version) + dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above + dataTypes.ReadNextByte(packetData); + dataTypes.ReadNextString(packetData); + handler.OnRespawn(); + break; + case PacketIncomingType.PlayerPositionAndLook: + // These always need to be read, since we need the field after them for teleport confirm + double x = dataTypes.ReadNextDouble(packetData); + double y = dataTypes.ReadNextDouble(packetData); + double z = dataTypes.ReadNextDouble(packetData); + float yaw = dataTypes.ReadNextFloat(packetData); + float pitch = dataTypes.ReadNextFloat(packetData); + byte locMask = dataTypes.ReadNextByte(packetData); + + // entity handling require player pos for distance calculating + if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled()) + { + if (protocolversion >= MC18Version) + { + Location location = handler.GetCurrentLocation(); + location.X = (locMask & 1 << 0) != 0 ? location.X + x : x; + location.Y = (locMask & 1 << 1) != 0 ? location.Y + y : y; + location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z; + handler.UpdateLocation(location, yaw, pitch); + } + else handler.UpdateLocation(new Location(x, y, z), yaw, pitch); + } + + if (protocolversion >= MC19Version) + { + int teleportID = dataTypes.ReadNextVarInt(packetData); + // Teleport confirm packet + SendPacket(PacketOutgoingType.TeleportConfirm, dataTypes.GetVarInt(teleportID)); + } + break; + case PacketIncomingType.ChunkData: + if (handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + bool chunksContinuous = dataTypes.ReadNextBool(packetData); + ushort chunkMask = protocolversion >= MC19Version + ? (ushort)dataTypes.ReadNextVarInt(packetData) + : dataTypes.ReadNextUShort(packetData); + if (protocolversion < MC18Version) + { + ushort addBitmap = dataTypes.ReadNextUShort(packetData); + int compressedDataSize = dataTypes.ReadNextInt(packetData); + byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); + } + else + { + if (protocolversion >= MC114Version) + dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above + if (protocolversion >= MC115Version && chunksContinuous) + dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above + int dataSize = dataTypes.ReadNextVarInt(packetData); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); + } + } + break; + case PacketIncomingType.MultiBlockChange: + if (handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + int recordCount = protocolversion < MC18Version + ? (int)dataTypes.ReadNextShort(packetData) + : dataTypes.ReadNextVarInt(packetData); + + for (int i = 0; i < recordCount; i++) + { + byte locationXZ; + ushort blockIdMeta; + int blockY; + + if (protocolversion < MC18Version) + { + blockIdMeta = dataTypes.ReadNextUShort(packetData); + blockY = (ushort)dataTypes.ReadNextByte(packetData); + locationXZ = dataTypes.ReadNextByte(packetData); + } + else + { + locationXZ = dataTypes.ReadNextByte(packetData); + blockY = (ushort)dataTypes.ReadNextByte(packetData); + blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData); + } + + int blockX = locationXZ >> 4; + int blockZ = locationXZ & 0x0F; + Block block = new Block(blockIdMeta); + handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); + } + } + break; + case PacketIncomingType.BlockChange: + if (handler.GetTerrainEnabled()) + { + if (protocolversion < MC18Version) + { + int blockX = dataTypes.ReadNextInt(packetData); + int blockY = dataTypes.ReadNextByte(packetData); + int blockZ = dataTypes.ReadNextInt(packetData); + short blockId = (short)dataTypes.ReadNextVarInt(packetData); + byte blockMeta = dataTypes.ReadNextByte(packetData); + handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta)); + } + else handler.GetWorld().SetBlock(dataTypes.ReadNextLocation(packetData), new Block((ushort)dataTypes.ReadNextVarInt(packetData))); + } + break; + case PacketIncomingType.MapChunkBulk: + if (protocolversion < MC19Version && handler.GetTerrainEnabled()) + { + int chunkCount; + bool hasSkyLight; + Queue chunkData = packetData; + + //Read global fields + if (protocolversion < MC18Version) + { + chunkCount = dataTypes.ReadNextShort(packetData); + int compressedDataSize = dataTypes.ReadNextInt(packetData); + hasSkyLight = dataTypes.ReadNextBool(packetData); + byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + chunkData = new Queue(decompressed); + } + else + { + hasSkyLight = dataTypes.ReadNextBool(packetData); + chunkCount = dataTypes.ReadNextVarInt(packetData); + } + + //Read chunk records + int[] chunkXs = new int[chunkCount]; + int[] chunkZs = new int[chunkCount]; + ushort[] chunkMasks = new ushort[chunkCount]; + ushort[] addBitmaps = new ushort[chunkCount]; + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + { + chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); + chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); + chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData); + addBitmaps[chunkColumnNo] = protocolversion < MC18Version + ? dataTypes.ReadNextUShort(packetData) + : (ushort)0; + } + + //Process chunk records + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, currentDimension, chunkData); + } + break; + case PacketIncomingType.UnloadChunk: + if (protocolversion >= MC19Version && handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + handler.GetWorld()[chunkX, chunkZ] = null; + } + break; + case PacketIncomingType.PlayerListUpdate: + if (protocolversion >= MC18Version) + { + int action = dataTypes.ReadNextVarInt(packetData); + int numActions = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < numActions; i++) + { + Guid uuid = dataTypes.ReadNextUUID(packetData); + switch (action) + { + case 0x00: //Player Join + string name = dataTypes.ReadNextString(packetData); + int propNum = dataTypes.ReadNextVarInt(packetData); + for (int p = 0; p < propNum; p++) + { + string key = dataTypes.ReadNextString(packetData); + string val = dataTypes.ReadNextString(packetData); + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + } + dataTypes.ReadNextVarInt(packetData); + dataTypes.ReadNextVarInt(packetData); + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + handler.OnPlayerJoin(uuid, name); + break; + case 0x01: //Update gamemode + case 0x02: //Update latency + dataTypes.ReadNextVarInt(packetData); + break; + case 0x03: //Update display name + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + break; + case 0x04: //Player Leave + handler.OnPlayerLeave(uuid); + break; + default: + //Unknown player list item type + break; + } + } + } + else //MC 1.7.X does not provide UUID in tab-list updates + { + string name = dataTypes.ReadNextString(packetData); + bool online = dataTypes.ReadNextBool(packetData); + short ping = dataTypes.ReadNextShort(packetData); + Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); + if (online) + handler.OnPlayerJoin(FakeUUID, name); + else handler.OnPlayerLeave(FakeUUID); + } + break; + case PacketIncomingType.TabCompleteResult: + if (protocolversion >= MC113Version) + { + autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData); + dataTypes.ReadNextVarInt(packetData); // Start of text to replace + dataTypes.ReadNextVarInt(packetData); // Length of text to replace + } + + int autocomplete_count = dataTypes.ReadNextVarInt(packetData); + autocomplete_result.Clear(); + + for (int i = 0; i < autocomplete_count; i++) + { + autocomplete_result.Add(dataTypes.ReadNextString(packetData)); + if (protocolversion >= MC113Version) + { + // Skip optional tooltip for each tab-complete result + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + } + } + + autocomplete_received = true; + break; + case PacketIncomingType.PluginMessage: + String channel = dataTypes.ReadNextString(packetData); + // Length is unneeded as the whole remaining packetData is the entire payload of the packet. + if (protocolversion < MC18Version) + pForge.ReadNextVarShort(packetData); + handler.OnPluginChannelMessage(channel, packetData.ToArray()); + return pForge.HandlePluginMessage(channel, packetData, ref currentDimension); + case PacketIncomingType.KickPacket: + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + case PacketIncomingType.NetworkCompressionTreshold: + if (protocolversion >= MC18Version && protocolversion < MC19Version) + compression_treshold = dataTypes.ReadNextVarInt(packetData); + break; + case PacketIncomingType.OpenWindow: + if (handler.GetInventoryEnabled()) + { + if (protocolversion < MC114Version) + { + // MC 1.13 or lower + byte windowID = dataTypes.ReadNextByte(packetData); + string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper(); + ContainerTypeOld inventoryType = (ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type); + string title = dataTypes.ReadNextString(packetData); + byte slots = dataTypes.ReadNextByte(packetData); + Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title)); + handler.OnInventoryOpen(windowID, inventory); + } + else + { + // MC 1.14 or greater + int windowID = dataTypes.ReadNextVarInt(packetData); + int windowType = dataTypes.ReadNextVarInt(packetData); + string title = dataTypes.ReadNextString(packetData); + Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title)); + handler.OnInventoryOpen(windowID, inventory); + } + } + break; + case PacketIncomingType.CloseWindow: + if (handler.GetInventoryEnabled()) + { + byte windowID = dataTypes.ReadNextByte(packetData); + lock (window_actions) { window_actions[windowID] = 0; } + handler.OnInventoryClose(windowID); + } + break; + case PacketIncomingType.WindowItems: + if (handler.GetInventoryEnabled()) + { + byte windowId = dataTypes.ReadNextByte(packetData); + short elements = dataTypes.ReadNextShort(packetData); + Dictionary inventorySlots = new Dictionary(); + for (short slotId = 0; slotId < elements; slotId++) + { + Item item = dataTypes.ReadNextItemSlot(packetData); + if (item != null) + inventorySlots[slotId] = item; + } + handler.OnWindowItems(windowId, inventorySlots); + } + break; + case PacketIncomingType.SetSlot: + if (handler.GetInventoryEnabled()) + { + byte windowID = dataTypes.ReadNextByte(packetData); + short slotID = dataTypes.ReadNextShort(packetData); + Item item = dataTypes.ReadNextItemSlot(packetData); + handler.OnSetSlot(windowID, slotID, item); + } + break; + case PacketIncomingType.ResourcePackSend: + string url = dataTypes.ReadNextString(packetData); + string hash = dataTypes.ReadNextString(packetData); + //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory + byte[] responseHeader = new byte[0]; + if (protocolversion < MC110Version) //MC 1.10 does not include resource pack hash in responses + responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); + SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack + SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded + break; + case PacketIncomingType.SpawnEntity: + if (handler.GetEntityHandlingEnabled()) + { + Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, false); + handler.OnSpawnEntity(entity); + } + break; + case PacketIncomingType.SpawnLivingEntity: + if (handler.GetEntityHandlingEnabled()) + { + Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, true); + // packet before 1.15 has metadata at the end + // this is not handled in dataTypes.ReadNextEntity() + // we are simply ignoring leftover data in packet + handler.OnSpawnEntity(entity); + } + break; + case PacketIncomingType.SpawnPlayer: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Guid UUID = dataTypes.ReadNextUUID(packetData); + double X = dataTypes.ReadNextDouble(packetData); + double Y = dataTypes.ReadNextDouble(packetData); + double Z = dataTypes.ReadNextDouble(packetData); + byte Yaw = dataTypes.ReadNextByte(packetData); + byte Pitch = dataTypes.ReadNextByte(packetData); + + Location EntityLocation = new Location(X, Y, Z); + + handler.OnSpawnPlayer(EntityID, UUID, EntityLocation, Yaw, Pitch); + } + break; + case PacketIncomingType.DestroyEntities: + if (handler.GetEntityHandlingEnabled()) + { + int EntityCount = dataTypes.ReadNextVarInt(packetData); + int[] EntitiesList = new int[EntityCount]; + for (int i = 0; i < EntityCount; i++) + { + EntitiesList[i] = dataTypes.ReadNextVarInt(packetData); + } + handler.OnDestroyEntities(EntitiesList); + } + break; + case PacketIncomingType.EntityPosition: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + bool OnGround = dataTypes.ReadNextBool(packetData); + DeltaX = DeltaX / (128 * 32); + DeltaY = DeltaY / (128 * 32); + DeltaZ = DeltaZ / (128 * 32); + handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); + } + break; + case PacketIncomingType.EntityPositionAndRotation: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + byte _yaw = dataTypes.ReadNextByte(packetData); + byte _pitch = dataTypes.ReadNextByte(packetData); + bool OnGround = dataTypes.ReadNextBool(packetData); + DeltaX = DeltaX / (128 * 32); + DeltaY = DeltaY / (128 * 32); + DeltaZ = DeltaZ / (128 * 32); + handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); + } + break; + case PacketIncomingType.EntityProperties: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + int NumberOfProperties = dataTypes.ReadNextInt(packetData); + Dictionary keys = new Dictionary(); + for (int i = 0; i < NumberOfProperties; i++) + { + string _key = dataTypes.ReadNextString(packetData); + Double _value = dataTypes.ReadNextDouble(packetData); + + List op0 = new List(); + List op1 = new List(); + List op2 = new List(); + int NumberOfModifiers = dataTypes.ReadNextVarInt(packetData); + for (int j = 0; j < NumberOfModifiers; j++) + { + dataTypes.ReadNextUUID(packetData); + Double amount = dataTypes.ReadNextDouble(packetData); + byte operation = dataTypes.ReadNextByte(packetData); + switch (operation) + { + case 0: op0.Add(amount); break; + case 1: op1.Add(amount); break; + case 2: op2.Add(amount + 1); break; + } + } + if (op0.Count > 0) _value += op0.Sum(); + if (op1.Count > 0) _value *= 1 + op1.Sum(); + if (op2.Count > 0) _value *= op2.Aggregate((a, _x) => a * _x); + keys.Add(_key, _value); + } + handler.OnEntityProperties(EntityID, keys); + } + break; + case PacketIncomingType.TimeUpdate: + long WorldAge = dataTypes.ReadNextLong(packetData); + long TimeOfday = dataTypes.ReadNextLong(packetData); + handler.OnTimeUpdate(WorldAge, TimeOfday); + break; + case PacketIncomingType.EntityTeleport: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double X = dataTypes.ReadNextDouble(packetData); + Double Y = dataTypes.ReadNextDouble(packetData); + Double Z = dataTypes.ReadNextDouble(packetData); + byte EntityYaw = dataTypes.ReadNextByte(packetData); + byte EntityPitch = dataTypes.ReadNextByte(packetData); + bool OnGround = dataTypes.ReadNextBool(packetData); + handler.OnEntityTeleport(EntityID, X, Y, Z, OnGround); + } + break; + case PacketIncomingType.UpdateHealth: + float health = dataTypes.ReadNextFloat(packetData); + int food = dataTypes.ReadNextVarInt(packetData); + // Food Saturation, not useful + dataTypes.ReadNextFloat(packetData); + handler.OnUpdateHealth(health, food); + break; + case PacketIncomingType.HeldItemChange: + byte slot = dataTypes.ReadNextByte(packetData); + handler.OnHeldItemChange(slot); + break; + default: + return false; //Ignored packet + } + return true; //Packet processed + } + catch (Exception innerException) + { + if (innerException is ThreadAbortException || innerException is SocketException || innerException.InnerException is SocketException) + throw; //Thread abort or Connection lost rather than invalid data + throw new System.IO.InvalidDataException( + String.Format("Failed to process incoming packet of type {0}. (PacketID: {1}, Protocol: {2}, LoginPhase: {3}, InnerException: {4}).", + Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion), + packetID, + protocolversion, + login_phase, + innerException.GetType()), + innerException); + } + } + + /// + /// Start the updating thread. Should be called after login success. + /// + private void StartUpdating() + { + netRead = new Thread(new ThreadStart(Updater)); + netRead.Name = "ProtocolPacketHandler"; + netRead.Start(); + } + + /// + /// Disconnect from the server, cancel network reading. + /// + public void Dispose() + { + try + { + if (netRead != null) + { + netRead.Abort(); + socketWrapper.Disconnect(); + } + } + catch { } + } + + /// + /// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically. + /// + /// packet type + /// packet Data + private void SendPacket(PacketOutgoingType packet, IEnumerable packetData) + { + SendPacket(Protocol18PacketTypes.GetPacketOutgoingID(packet, protocolversion), packetData); + } + + /// + /// Send a packet to the server. Compression and encryption will be handled automatically. + /// + /// packet ID + /// packet Data + private void SendPacket(int packetID, IEnumerable packetData) + { + //The inner packet + byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray()); + + if (compression_treshold > 0) //Compression enabled? + { + if (the_packet.Length >= compression_treshold) //Packet long enough for compressing? + { + byte[] compressed_packet = ZlibUtils.Compress(the_packet); + the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet); + } + else + { + byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet) + the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet); + } + } + + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet)); + } + + /// + /// Do the Minecraft login. + /// + /// True if login successful + public bool Login() + { + byte[] protocol_version = dataTypes.GetVarInt(protocolversion); + string server_address = pForge.GetServerAddress(handler.GetServerHost()); + byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port); + byte[] next_state = dataTypes.GetVarInt(2); + byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state); + + SendPacket(0x00, handshake_packet); + + byte[] login_packet = dataTypes.GetString(handler.GetUsername()); + + SendPacket(0x00, login_packet); + + int packetID = -1; + Queue packetData = new Queue(); + while (true) + { + ReadNextPacket(ref packetID, packetData); + if (packetID == 0x00) //Login rejected + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + } + else if (packetID == 0x01) //Encryption request + { + string serverID = dataTypes.ReadNextString(packetData); + byte[] Serverkey = dataTypes.ReadNextByteArray(packetData); + byte[] token = dataTypes.ReadNextByteArray(packetData); + return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); + } + else if (packetID == 0x02) //Login successful + { + ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); + login_phase = false; + + if (!pForge.CompleteForgeHandshake()) + { + ConsoleIO.WriteLineFormatted("§8Forge Login Handshake did not complete successfully"); + return false; + } + + StartUpdating(); + return true; //No need to check session or start encryption + } + else HandlePacket(packetID, packetData); + } + } + + /// + /// Start network encryption. Automatically called by Login() if the server requests encryption. + /// + /// True if encryption was successful + private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey) + { + System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey); + byte[] secretKey = CryptoHandler.GenerateAESPrivateKey(); + + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); + + if (serverIDhash != "-") + { + Console.WriteLine("Checking Session..."); + if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey))) + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session."); + return false; + } + } + + //Encrypt the data + byte[] key_enc = dataTypes.GetArray(RSAService.Encrypt(secretKey, false)); + byte[] token_enc = dataTypes.GetArray(RSAService.Encrypt(token, false)); + + //Encryption Response packet + SendPacket(0x01, dataTypes.ConcatBytes(key_enc, token_enc)); + + //Start client-side encryption + socketWrapper.SwitchToEncrypted(secretKey); + + //Process the next packet + int packetID = -1; + Queue packetData = new Queue(); + while (true) + { + ReadNextPacket(ref packetID, packetData); + if (packetID == 0x00) //Login rejected + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + } + else if (packetID == 0x02) //Login successful + { + login_phase = false; + + if (!pForge.CompleteForgeHandshake()) + { + ConsoleIO.WriteLineFormatted("§8Forge StartEncryption Handshake did not complete successfully"); + return false; + } + + StartUpdating(); + return true; + } + else HandlePacket(packetID, packetData); + } + } + + /// + /// Get max length for chat messages + /// + /// Max length, in characters + public int GetMaxChatMessageLength() + { + return protocolversion > MC110Version + ? 256 + : 100; + } + + /// + /// Send a chat message to the server + /// + /// Message + /// True if properly sent + public bool SendChatMessage(string message) + { + if (String.IsNullOrEmpty(message)) + return true; + try + { + byte[] message_packet = dataTypes.GetString(message); + SendPacket(PacketOutgoingType.ChatMessage, message_packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendEntityAction(int PlayerEntityID, int ActionID) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(PlayerEntityID)); + fields.AddRange(dataTypes.GetVarInt(ActionID)); + fields.AddRange(dataTypes.GetVarInt(0)); + SendPacket(PacketOutgoingType.EntityAction, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + /// + /// Send a respawn packet to the server + /// + /// True if properly sent + public bool SendRespawnPacket() + { + try + { + SendPacket(PacketOutgoingType.ClientStatus, new byte[] { 0 }); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + /// + /// Tell the server what client is being used to connect to the server + /// + /// Client string describing the client + /// True if brand info was successfully sent + public bool SendBrandInfo(string brandInfo) + { + if (String.IsNullOrEmpty(brandInfo)) + return false; + // Plugin channels were significantly changed between Minecraft 1.12 and 1.13 + // https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels + if (protocolversion >= MC113Version) + { + return SendPluginChannelPacket("minecraft:brand", dataTypes.GetString(brandInfo)); + } + else + { + return SendPluginChannelPacket("MC|Brand", dataTypes.GetString(brandInfo)); + } + } + + /// + /// Inform the server of the client's Minecraft settings + /// + /// Client language eg en_US + /// View distance, in chunks + /// Game difficulty (client-side...) + /// Chat mode (allows muting yourself) + /// Show chat colors + /// Show skin layers + /// 1.9+ main hand + /// True if client settings were successfully sent + public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode, bool chatColors, byte skinParts, byte mainHand) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetString(language)); + fields.Add(viewDistance); + fields.AddRange(protocolversion >= MC19Version + ? dataTypes.GetVarInt(chatMode) + : new byte[] { chatMode }); + fields.Add(chatColors ? (byte)1 : (byte)0); + if (protocolversion < MC18Version) + { + fields.Add(difficulty); + fields.Add((byte)(skinParts & 0x1)); //show cape + } + else fields.Add(skinParts); + if (protocolversion >= MC19Version) + fields.AddRange(dataTypes.GetVarInt(mainHand)); + SendPacket(PacketOutgoingType.ClientSettings, fields); + } + catch (SocketException) { } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + return false; + } + + /// + /// Send a location update to the server + /// + /// The new location of the player + /// True if the player is on the ground + /// Optional new yaw for updating player look + /// Optional new pitch for updating player look + /// True if the location update was successfully sent + public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null) + { + if (handler.GetTerrainEnabled()) + { + byte[] yawpitch = new byte[0]; + PacketOutgoingType packetType = PacketOutgoingType.PlayerPosition; + + if (yaw.HasValue && pitch.HasValue) + { + yawpitch = dataTypes.ConcatBytes(dataTypes.GetFloat(yaw.Value), dataTypes.GetFloat(pitch.Value)); + packetType = PacketOutgoingType.PlayerPositionAndLook; + } + + try + { + SendPacket(packetType, dataTypes.ConcatBytes( + dataTypes.GetDouble(location.X), + dataTypes.GetDouble(location.Y), + protocolversion < MC18Version + ? dataTypes.GetDouble(location.Y + 1.62) + : new byte[0], + dataTypes.GetDouble(location.Z), + yawpitch, + new byte[] { onGround ? (byte)1 : (byte)0 })); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + else return false; + } + + /// + /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically + /// + /// Channel to send packet on + /// packet Data + public bool SendPluginChannelPacket(string channel, byte[] data) + { + try + { + // In 1.7, length needs to be included. + // In 1.8, it must not be. + if (protocolversion < MC18Version) + { + byte[] length = BitConverter.GetBytes((short)data.Length); + Array.Reverse(length); + + SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data)); + } + else + { + SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data)); + } + + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + /// + /// Disconnect from the server + /// + public void Disconnect() + { + socketWrapper.Disconnect(); + } + + /// + /// Autocomplete text while typing username or command + /// + /// Text behind cursor + /// Completed text + IEnumerable IAutoComplete.AutoComplete(string BehindCursor) + { + if (String.IsNullOrEmpty(BehindCursor)) + return new string[] { }; + + byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); + byte[] assume_command = new byte[] { 0x00 }; + byte[] has_position = new byte[] { 0x00 }; + + byte[] tabcomplete_packet = new byte[] { }; + + if (protocolversion >= MC18Version) + { + if (protocolversion >= MC113Version) + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id); + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + } + else + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + + if (protocolversion >= MC19Version) + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command); + } + + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position); + } + } + else + { + tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor)); + } + + autocomplete_received = false; + autocomplete_result.Clear(); + autocomplete_result.Add(BehindCursor); + SendPacket(PacketOutgoingType.TabComplete, tabcomplete_packet); + + int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) + while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; } + if (autocomplete_result.Count > 0) + ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false); + return autocomplete_result; + } + + /// + /// Ping a Minecraft server to get information about the server + /// + /// True if ping was successful + public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo) + { + string version = ""; + TcpClient tcp = ProxyHandler.newTcpClient(host, port); + tcp.ReceiveBufferSize = 1024 * 1024; + SocketWrapper socketWrapper = new SocketWrapper(tcp); + DataTypes dataTypes = new DataTypes(MC18Version); + + byte[] packet_id = dataTypes.GetVarInt(0); + byte[] protocol_version = dataTypes.GetVarInt(-1); + byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port); + byte[] next_state = dataTypes.GetVarInt(1); + byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state); + byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet); + + socketWrapper.SendDataRAW(tosend); + + byte[] status_request = dataTypes.GetVarInt(0); + byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request); + + socketWrapper.SendDataRAW(request_packet); + + int packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); + if (packetLength > 0) //Read Response length + { + Queue packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); + if (dataTypes.ReadNextVarInt(packetData) == 0x00) //Read Packet ID + { + string result = dataTypes.ReadNextString(packetData); //Get the Json data + + if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) + { + Json.JSONData jsonData = Json.ParseJson(result); + if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) + { + Json.JSONData versionData = jsonData.Properties["version"]; + + //Retrieve display name of the Minecraft version + if (versionData.Properties.ContainsKey("name")) + version = versionData.Properties["name"].StringValue; + + //Retrieve protocol version number for handling this server + if (versionData.Properties.ContainsKey("protocol")) + protocolversion = dataTypes.Atoi(versionData.Properties["protocol"].StringValue); + + // Check for forge on the server. + Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo); + + ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ").")); + + return true; + } + } + } + } + return false; + } + + /// + /// Send an Interact Entity Packet to server + /// + /// + /// + /// + public bool SendInteractEntity(int EntityID, int type) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(EntityID)); + fields.AddRange(dataTypes.GetVarInt(type)); + SendPacket(PacketOutgoingType.InteractEntity, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + // TODO: Interact at block location (e.g. chest minecart) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(EntityID)); + fields.AddRange(dataTypes.GetVarInt(type)); + fields.AddRange(dataTypes.GetFloat(X)); + fields.AddRange(dataTypes.GetFloat(Y)); + fields.AddRange(dataTypes.GetFloat(Z)); + fields.AddRange(dataTypes.GetVarInt(hand)); + SendPacket(PacketOutgoingType.InteractEntity, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) + { + return false; + } + + public bool SendUseItem(int hand) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetVarInt(hand)); + SendPacket(PacketOutgoingType.UseItem, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendPlayerBlockPlacement(int hand, Location location, int face, float CursorX, float CursorY, float CursorZ, bool insideBlock) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetVarInt(hand)); + packet.AddRange(dataTypes.GetLocation(location)); + packet.AddRange(dataTypes.GetVarInt(face)); + packet.AddRange(dataTypes.GetFloat(CursorX)); + packet.AddRange(dataTypes.GetFloat(CursorY)); + packet.AddRange(dataTypes.GetFloat(CursorZ)); + packet.Add(Convert.ToByte(insideBlock ? 1 : 0)); + SendPacket(PacketOutgoingType.PlayerBlockPlacement, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendHeldItemChange(short slot) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetShort(slot)); + SendPacket(PacketOutgoingType.HeldItemChange, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item) + { + try + { + short actionNumber; + lock (window_actions) + { + if (!window_actions.ContainsKey(windowId)) + window_actions[windowId] = 0; + actionNumber = (short)(window_actions[windowId] + 1); + window_actions[windowId] = actionNumber; + } + + byte button = 0; + byte mode = 0; + switch (action) + { + case WindowActionType.LeftClick: button = 0; break; + case WindowActionType.RightClick: button = 1; break; + case WindowActionType.MiddleClick: button = 2; mode = 3; break; + case WindowActionType.DropItem: + button = 0; + mode = 4; + item = new Item(-1, 0, null); + Container inventory = handler.GetInventory(windowId); + if (inventory.Items.ContainsKey(slotId)) + inventory.Items[slotId].Count--; // server won't update us after dropped + break; + case WindowActionType.DropItemStack: + button = 1; + mode = 4; + item = new Item(-1, 0, null); + inventory = handler.GetInventory(windowId); + inventory.Items.Remove(slotId); // server won't update us after dropped + break; + } + + List packet = new List(); + packet.Add((byte)windowId); + packet.AddRange(dataTypes.GetShort((short)slotId)); + packet.Add(button); + packet.AddRange(dataTypes.GetShort(actionNumber)); + + if (protocolversion >= MC19Version) + packet.AddRange(dataTypes.GetVarInt(mode)); + else packet.Add(mode); + + packet.AddRange(dataTypes.GetItemSlot(item)); + + SendPacket(PacketOutgoingType.ClickWindow, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendCreativeInventoryAction(int slot, ItemType itemType, int count) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetShort((short)slot)); + packet.AddRange(dataTypes.GetItemSlot(new Item((int)itemType, count, null))); + + SendPacket(PacketOutgoingType.CreativeInventoryAction, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendAnimation(int animation) + { + try + { if (animation == 0 || animation == 1) { List packet = new List(); @@ -1402,32 +1402,32 @@ namespace MinecraftClient.Protocol.Handlers SendPacket(PacketOutgoingType.Animation, packet); return true; - } - else; + } + else; { return false; - } - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendCloseWindow(int windowId) - { - try - { - lock (window_actions) - { - if (window_actions.ContainsKey(windowId)) - window_actions[windowId] = 0; - } - SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId }); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - } -} + } + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendCloseWindow(int windowId) + { + try + { + lock (window_actions) + { + if (window_actions.ContainsKey(windowId)) + window_actions[windowId] = 0; + } + SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId }); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + } +}