diff --git a/DebugTools/MinecraftClientProxy.suo b/DebugTools/MinecraftClientProxy.suo new file mode 100644 index 00000000..af892b2f Binary files /dev/null and b/DebugTools/MinecraftClientProxy.suo differ diff --git a/DebugTools/MinecraftClientProxy/PacketProxy.cs b/DebugTools/MinecraftClientProxy/PacketProxy.cs index 4a20ff53..fb098904 100644 --- a/DebugTools/MinecraftClientProxy/PacketProxy.cs +++ b/DebugTools/MinecraftClientProxy/PacketProxy.cs @@ -149,6 +149,22 @@ namespace MinecraftClient.Protocol.Handlers g = readNextBool(ref packetData); Console.WriteLine("[C -> S] Location: " + x + ", " + y + ", " + z + ", (look)" + ", " + g); break; + case 0x09: + byte window = readNextByte(ref packetData); + short slot = readNextShort(ref packetData); + byte button = readNextByte(ref packetData); + short action = readNextShort(ref packetData); + int mode = readNextVarInt(ref packetData); + bool slotPresent = readNextBool(ref packetData); + int itemId = -1; + byte itemCount = 0; + if (slotPresent) + { + itemId = readNextVarInt(ref packetData); + itemCount = readNextByte(ref packetData); + } + Console.WriteLine("[C -> S] Window #" + window + " click: #" + slot + " button " + button + " action " + action + " mode " + mode + " item " + itemId + " x" + itemCount); + break; } } } @@ -209,6 +225,19 @@ namespace MinecraftClient.Protocol.Handlers return rawValue[0] != 0; } + private byte readNextByte(ref byte[] cache) + { + byte[] rawValue = readData(1, ref cache); + return rawValue[0]; + } + + private short readNextShort(ref byte[] cache) + { + byte[] rawValue = readData(2, ref cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToInt16(rawValue, 0); + } + private double readNextDouble(ref byte[] cache) { byte[] rawValue = readData(8, ref cache); diff --git a/MinecraftClient/Commands/Inventory.cs b/MinecraftClient/Commands/Inventory.cs index b39f4945..3fba3856 100644 --- a/MinecraftClient/Commands/Inventory.cs +++ b/MinecraftClient/Commands/Inventory.cs @@ -6,23 +6,63 @@ using MinecraftClient.Inventory; namespace MinecraftClient.Commands { - class GetInventory : Command + class Inventory : Command { public override string CMDName { get { return "inventory"; } } - public override string CMDDesc { get { return "inventory: Show your inventory."; } } + public override string CMDDesc { get { return "inventory >: Interact with inventories"; } } public override string Run(McTcpClient handler, string command, Dictionary localVars) { - List response = new List(); - - response.Add("Inventory slots:"); - - foreach (KeyValuePair item in handler.GetPlayerInventory().Items) + if (handler.GetInventoryEnabled()) { - response.Add(String.Format(" #{0}: {1} x{2}", item.Key, item.Value.Type, item.Value.Count)); + string[] args = getArgs(command); + if (args.Length >= 1) + { + try + { + int inventoryId = int.Parse(args[0]); + string action = args.Length > 1 + ? args[1].ToLower() + : "list"; + switch (action) + { + case "close": + if (handler.CloseInventory(inventoryId)) + return "Closing Inventoy #" + inventoryId; + else return "Failed to close Inventory #" + inventoryId; + case "list": + Container inventory = handler.GetInventory(inventoryId); + List response = new List(); + response.Add("Inventory #" + inventoryId + " - " + inventory.Title + "§8"); + foreach (KeyValuePair item in inventory.Items) + response.Add(String.Format(" #{0}: {1} x{2}", item.Key, item.Value.Type, item.Value.Count)); + return String.Join("\n", response.ToArray()); + case "click": + if (args.Length == 3) + { + int slot = int.Parse(args[2]); + handler.ClickWindowSlot(inventoryId, slot); + return "Clicking slot " + slot + " in window #" + inventoryId; + } + else return CMDDesc; + default: + return CMDDesc; + } + } + catch (FormatException) { return CMDDesc; } + } + else + { + Dictionary inventories = handler.GetInventories(); + List response = new List(); + response.Add("Inventories:"); + foreach (var inventory in inventories) + response.Add(String.Format(" #{0}: {1}", inventory.Key, inventory.Value.Title + "§8")); + response.Add(CMDDesc); + return String.Join("\n", response); + } } - - return String.Join("\n", response.ToArray()); + else return "Please enable inventoryhandling in config to use this command."; } } } diff --git a/MinecraftClient/Commands/UseItem.cs b/MinecraftClient/Commands/UseItem.cs index fd23ba8f..a6e3fbc9 100644 --- a/MinecraftClient/Commands/UseItem.cs +++ b/MinecraftClient/Commands/UseItem.cs @@ -12,8 +12,12 @@ namespace MinecraftClient.Commands public override string Run(McTcpClient handler, string command, Dictionary localVars) { - handler.UseItemOnHand(); - return "Use an item"; + if (handler.GetInventoryEnabled()) + { + handler.UseItemOnHand(); + return "Used an item"; + } + else return "Please enable inventoryhandling in config to use this command."; } } } diff --git a/MinecraftClient/Inventory/Item.cs b/MinecraftClient/Inventory/Item.cs index 56c62b32..29c5e8a5 100644 --- a/MinecraftClient/Inventory/Item.cs +++ b/MinecraftClient/Inventory/Item.cs @@ -20,44 +20,34 @@ namespace MinecraftClient.Inventory /// public int Count; - /// - /// Slot ID in the parent inventory - /// - /// -1 means currently being dragged by mouse - public int SlotID; - /// /// Item Metadata /// public Dictionary NBT; /// - /// Create an item with Type ID, Count and Slot ID + /// Create an item with Type ID, Count and Metadata /// /// Item Type ID /// Item Count - /// Item Slot ID in parent inventory - public Item(int id, int count, int slotID) + /// Item Metadata + public Item(int id, int count, Dictionary nbt) { this.Type = (ItemType)id; this.Count = count; - this.SlotID = slotID; - this.NBT = new Dictionary(); + this.NBT = nbt; } /// - /// Create an item with Type ID, Count, Slot ID and Metadata + /// Check if the item slot is empty /// - /// Item Type ID - /// Item Count - /// Item Slot ID in parent inventory - /// Item Metadata - public Item(int id, int count, int slotID, Dictionary nbt) + /// TRUE if the item is empty + public bool IsEmpty { - this.Type = (ItemType)id; - this.Count = count; - this.SlotID = slotID; - this.NBT = nbt; + get + { + return Type == ItemType.Air || Count == 0; + } } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index e3d213ca..0aabe342 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -27,7 +27,7 @@ namespace MinecraftClient private readonly List bots = new List(); private static readonly List botsOnHold = new List(); - private static List inventories = new List(); + private static Dictionary inventories = new Dictionary(); private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); private readonly List registeredServerPluginChannels = new List(); @@ -53,7 +53,6 @@ namespace MinecraftClient private string username; private string uuid; private string sessionid; - private Container playerInventory = new Container(ContainerType.PlayerInventory); private DateTime lastKeepAlive; private object lastKeepAliveLock = new object(); @@ -136,6 +135,12 @@ namespace MinecraftClient 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; @@ -550,7 +555,6 @@ namespace MinecraftClient inventoryHandlingEnabled = false; inventoryHandlingRequested = false; inventories.Clear(); - playerInventory = null; } return true; } @@ -592,13 +596,34 @@ namespace MinecraftClient } } + /// + /// 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 playerInventory; + return GetInventory(0); } /// @@ -765,19 +790,14 @@ namespace MinecraftClient /// When an inventory is opened /// /// Location to reach - public void OnInventoryOpen(Container inventory) + public void OnInventoryOpen(int inventoryID, Container inventory) { - //TODO: Handle Inventory - if (!inventories.Contains(inventory)) - { - inventories.Add(inventory); - } + inventories[inventoryID] = inventory; - if (Settings.DebugMessages) + if (inventoryID != 0) { - ConsoleIO.WriteLineFormatted("§8An Inventory opened: " + inventory.Type + " - " + inventory.Title); - foreach (var item in inventory.Items) - ConsoleIO.WriteLineFormatted("§8 - Slot " + item.Key + ": " + item.Value.Type + " x" + item.Value.Count); + ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " opened: " + inventory.Title); + ConsoleIO.WriteLogLine("Use /inventory to interact with it."); } } @@ -785,64 +805,42 @@ namespace MinecraftClient /// When an inventory is close /// /// Location to reach - public void OnInventoryClose(byte inventoryID) + public void OnInventoryClose(int inventoryID) { - for (int i = 0; i < inventories.Count; i++) - { - Container inventory = inventories[i]; - if (inventory == null) continue; - if (inventory.Type == Container.GetContainerType(inventoryID)) - { - inventories.Remove(inventory); - return; - } - } + if (inventories.ContainsKey(inventoryID)) + inventories.Remove(inventoryID); + + if (inventoryID != 0) + ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " closed."); } /// /// When received window items from server. /// - /// Inventory type + /// Inventory ID /// Item list, key = slot ID, value = Item information - public void OnWindowItems(int type, Dictionary itemList) + public void OnWindowItems(byte inventoryID, Dictionary itemList) { - // 0 is player inventory - if (type == 0) - playerInventory.Items = itemList; - if (Settings.DebugMessages) - { - ConsoleIO.WriteLineFormatted("§8Received Window of type " + type); - foreach (var item in itemList) - ConsoleIO.WriteLineFormatted("§8 - Slot " + item.Key + ": " + item.Value.Type + " x" + item.Value.Count); - } - } - - /// - /// When a slot is cleared inside window items - /// - /// Window ID - /// Slot ID - public void OnSlotClear(byte WindowID, short SlotID) - { - if (WindowID == 0 && playerInventory.Items.ContainsKey(SlotID)) - { - playerInventory.Items.Remove(SlotID); - } + if (inventories.ContainsKey(inventoryID)) + inventories[inventoryID].Items = itemList; } /// /// When a slot is set inside window items /// - /// Window ID - /// Slot ID - /// Item ID - /// Item Count - /// Item Metadata - public void OnSetSlot(byte WindowID, short SlotID, int ItemID, byte Count, Dictionary NBT) + /// Window ID + /// Slot ID + /// Item (may be null for empty slot) + public void OnSetSlot(byte inventoryID, short slotID, Item item) { - if (WindowID == 0) + if (inventories.ContainsKey(inventoryID)) { - playerInventory.Items[SlotID] = new Inventory.Item(ItemID, Count, SlotID, NBT); + if (item == null || item.IsEmpty) + { + if (inventories[inventoryID].Items.ContainsKey(slotID)) + inventories[inventoryID].Items.Remove(slotID); + } + else inventories[inventoryID].Items[slotID] = item; } } @@ -1312,7 +1310,35 @@ namespace MinecraftClient /// TRUE if the item was successfully used public bool UseItemOnHand() { - return handler.SendUseItemPacket(0); + return handler.SendUseItem(0); + } + + /// + /// Click a slot in the specified window + /// + /// TRUE if the slot was successfully clicked + public bool ClickWindowSlot(int windowId, int slotId) + { + Item item = null; + if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) + item = inventories[windowId].Items[slotId]; + + return handler.SendClickWindow(windowId, slotId, item); + } + + /// + /// 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; } /// @@ -1320,10 +1346,10 @@ namespace MinecraftClient /// /// /// 0: interact, 1: attack, 2: interact at - /// + /// TRUE if interaction succeeded public bool InteractEntity(int EntityID, int type) { - return handler.SendInteractEntityPacket(EntityID, type); + return handler.SendInteractEntity(EntityID, type); } /// @@ -1342,7 +1368,7 @@ namespace MinecraftClient /// 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) diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index dc51c412..0523acdf 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -5,6 +5,7 @@ using System.Text; using System.Net.Sockets; using MinecraftClient.Mapping; using MinecraftClient.Crypto; +using MinecraftClient.Inventory; namespace MinecraftClient.Protocol.Handlers { @@ -319,6 +320,40 @@ namespace MinecraftClient.Protocol.Handlers return ReadNextNbt(cache, true); } + /// + /// Read a single item slot from a cache of byte and remove it from the cache + /// + /// Item + /// The item that was read or NULL for an empty slot + public Item ReadNextItemSlot(List cache) + { + List slotData = new List(); + if (protocolversion > Protocol18Handler.MC113Version) + { + // MC 1.13 and greater + bool itemPresent = ReadNextBool(cache); + if (itemPresent) + { + int itemID = ReadNextVarInt(cache); + byte itemCount = ReadNextByte(cache); + Dictionary NBT = ReadNextNbt(cache); + return new Item(itemID, itemCount, NBT); + } + else return null; + } + else + { + // MC 1.12.2 and lower + short itemID = ReadNextShort(cache); + if (itemID == -1) + return null; + byte itemCount = ReadNextByte(cache); + short itemDamage = ReadNextShort(cache); + Dictionary NBT = ReadNextNbt(cache); + return new Item(itemID, itemCount, NBT); + } + } + /// /// Read an uncompressed Named Binary Tag blob and remove it from the cache (internal) /// @@ -395,6 +430,163 @@ namespace MinecraftClient.Protocol.Handlers } } + /// + /// Build an uncompressed Named Binary Tag blob for sending over the network + /// + /// Dictionary to encode as Nbt + /// Byte array for this NBT tag + public byte[] GetNbt(Dictionary nbt) + { + return GetNbt(nbt, true); + } + + /// + /// Build an uncompressed Named Binary Tag blob for sending over the network (internal) + /// + /// Dictionary to encode as Nbt + /// TRUE if starting a new NBT tag, FALSE if processing a nested NBT tag + /// Byte array for this NBT tag + private byte[] GetNbt(Dictionary nbt, bool root) + { + if (nbt == null || nbt.Count == 0) + return new byte[] { 0 }; // TAG_End + + List bytes = new List(); + + if (root) + { + bytes.Add(10); // TAG_Compound + } + + foreach (var item in nbt) + { + byte fieldType; + byte[] fieldNameLength = GetUShort((ushort)item.Key.Length); + byte[] fieldName = Encoding.ASCII.GetBytes(item.Key); + byte[] fieldData = GetNbtField(item.Value, out fieldType); + bytes.AddRange(fieldNameLength); + bytes.AddRange(fieldName); + bytes.Add(fieldType); + bytes.AddRange(fieldData); + } + + bytes.Add(0); // TAG_End + return bytes.ToArray(); + } + + /// + /// Convert a single object into its NBT representation (internal) + /// + /// Object to convert + /// Field type for the passed object + /// Binary data for the passed object + private byte[] GetNbtField(object obj, out byte fieldType) + { + if (obj is byte) + { + fieldType = 1; // TAG_Byte + return new[] { (byte)obj }; + } + else if (obj is short) + { + fieldType = 2; // TAG_Short + return GetShort((short)obj); + } + else if (obj is int) + { + fieldType = 3; // TAG_Int + return GetInt((int)obj); + } + else if (obj is long) + { + fieldType = 4; // TAG_Long + return GetLong((long)obj); + } + else if (obj is float) + { + fieldType = 5; // TAG_Float + return GetFloat((float)obj); + } + else if (obj is double) + { + fieldType = 6; // TAG_Double + return GetDouble((double)obj); + } + else if (obj is byte[]) + { + fieldType = 7; // TAG_Byte_Array + return (byte[])obj; + } + else if (obj is string) + { + fieldType = 8; // TAG_String + byte[] stringBytes = Encoding.UTF8.GetBytes((string)obj); + return ConcatBytes(GetUShort((ushort)stringBytes.Length), stringBytes); + } + else if (obj is object[]) + { + fieldType = 9; // TAG_List + + List list = new List((object[])obj); + int arrayLengthTotal = list.Count; + + // Treat empty list as TAG_Byte, length 0 + if (arrayLengthTotal == 0) + return ConcatBytes(new[] { (byte)1 }, GetInt(0)); + + // Encode first list item, retain its type + byte firstItemType; + string firstItemTypeString = list[0].GetType().Name; + byte[] firstItemBytes = GetNbtField(list[0], out firstItemType); + list.RemoveAt(0); + + // Encode further list items, check they have the same type + byte subsequentItemType; + List subsequentItemsBytes = new List(); + foreach (object item in list) + { + subsequentItemsBytes.AddRange(GetNbtField(item, out subsequentItemType)); + if (subsequentItemType != firstItemType) + throw new System.IO.InvalidDataException( + "GetNbt: Cannot encode object[] list with mixed types: " + firstItemTypeString + ", " + item.GetType().Name + " into NBT!"); + } + + // Build NBT list: type, length, item array + return ConcatBytes(new[] { firstItemType }, GetInt(arrayLengthTotal), firstItemBytes, subsequentItemsBytes.ToArray()); + } + else if (obj is Dictionary) + { + fieldType = 10; // TAG_Compound + return GetNbt((Dictionary)obj, false); + } + else if (obj is int[]) + { + fieldType = 11; // TAG_Int_Array + + int[] srcIntList = (int[])obj; + List encIntList = new List(); + encIntList.AddRange(GetInt(srcIntList.Length)); + foreach (int item in srcIntList) + encIntList.AddRange(GetInt(item)); + return encIntList.ToArray(); + } + else if (obj is long[]) + { + fieldType = 12; // TAG_Long_Array + + long[] srcLongList = (long[])obj; + List encLongList = new List(); + encLongList.AddRange(GetInt(srcLongList.Length)); + foreach (long item in srcLongList) + encLongList.AddRange(GetLong(item)); + return encLongList.ToArray(); + } + else + { + throw new System.IO.InvalidDataException("GetNbt: Cannot encode data type " + obj.GetType().Name + " into NBT!"); + } + } + /// /// Build an integer for sending over the network /// @@ -412,6 +604,30 @@ namespace MinecraftClient.Protocol.Handlers return bytes.ToArray(); } + /// + /// Get byte array representing a long integer + /// + /// Long to process + /// Array ready to send + public byte[] GetLong(long number) + { + byte[] theLong = BitConverter.GetBytes(number); + Array.Reverse(theLong); + return theLong; + } + + /// + /// Get byte array representing an integer + /// + /// Integer to process + /// Array ready to send + public byte[] GetInt(int number) + { + byte[] theInt = BitConverter.GetBytes(number); + Array.Reverse(theInt); + return theInt; + } + /// /// Get byte array representing a short /// @@ -424,6 +640,18 @@ namespace MinecraftClient.Protocol.Handlers return theShort; } + /// + /// Get byte array representing an unsigned short + /// + /// Short to process + /// Array ready to send + public byte[] GetUShort(ushort number) + { + byte[] theShort = BitConverter.GetBytes(number); + Array.Reverse(theShort); + return theShort; + } + /// /// Get byte array representing a double /// @@ -448,7 +676,6 @@ namespace MinecraftClient.Protocol.Handlers return theFloat; } - /// /// Get byte array with length information prepended to it /// @@ -473,7 +700,6 @@ namespace MinecraftClient.Protocol.Handlers public byte[] GetString(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); - return ConcatBytes(GetVarInt(bytes.Length), bytes); } @@ -499,6 +725,42 @@ namespace MinecraftClient.Protocol.Handlers return locationBytes; } + /// + /// Get a byte array representing the given item as an item slot + /// + /// Item + /// Item slot representation + public byte[] GetItemSlot(Item item) + { + List slotData = new List(); + if (protocolversion > Protocol18Handler.MC113Version) + { + // MC 1.13 and greater + if (item == null || item.IsEmpty) + slotData.Add(0); // No item + else + { + slotData.Add(1); // Item is present + slotData.AddRange(GetVarInt((int)item.Type)); + slotData.Add((byte)item.Count); + slotData.AddRange(GetNbt(item.NBT)); + } + } + else + { + // MC 1.12.2 and lower + if (item == null || item.IsEmpty) + slotData.AddRange(GetShort(-1)); + else + { + slotData.AddRange(GetShort((short)item.Type)); + slotData.Add((byte)item.Count); + slotData.AddRange(GetNbt(item.NBT)); + } + } + return slotData.ToArray(); + } + /// /// Easily append several byte arrays /// diff --git a/MinecraftClient/Protocol/Handlers/PacketOutgoingType.cs b/MinecraftClient/Protocol/Handlers/PacketOutgoingType.cs index e1bbe3b2..753558fa 100644 --- a/MinecraftClient/Protocol/Handlers/PacketOutgoingType.cs +++ b/MinecraftClient/Protocol/Handlers/PacketOutgoingType.cs @@ -23,6 +23,8 @@ namespace MinecraftClient.Protocol.Handlers HeldItemChange, InteractEntity, UseItem, + ClickWindow, + CloseWindow, PlayerBlockPlacement } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 5c2eb0a4..30f83fb2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -8,6 +8,7 @@ using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; using MinecraftClient.Mapping; +using MinecraftClient.Inventory; namespace MinecraftClient.Protocol.Handlers { @@ -641,22 +642,32 @@ namespace MinecraftClient.Protocol.Handlers return false; //Currently not implemented } - public bool SendInteractEntityPacket(int EntityID, int type) + public bool SendInteractEntity(int EntityID, int type) { return false; //Currently not implemented } - public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) { return false; //Currently not implemented } - public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) { return false; //Currently not implemented } - public bool SendUseItemPacket(int hand) + public bool SendUseItem(int hand) + { + return false; //Currently not implemented + } + + public bool SendClickWindow(int windowId, int slotId, Item item) + { + return false; //Currently not implemented + } + + public bool SendCloseWindow(int windowId) { return false; //Currently not implemented } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index be0ce88a..603f66a5 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -41,6 +41,7 @@ namespace MinecraftClient.Protocol.Handlers 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; @@ -497,23 +498,27 @@ namespace MinecraftClient.Protocol.Handlers case PacketIncomingType.OpenWindow: if (handler.GetInventoryEnabled()) { - if (protocolversion < MC114Version) // packet changed at 1.14 + 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, title); - handler.OnInventoryOpen(inventory); + Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title)); + window_actions[windowID] = 0; + handler.OnInventoryOpen(windowID, inventory); } else { - int WindowID = dataTypes.ReadNextVarInt(packetData); - int WindowType = dataTypes.ReadNextVarInt(packetData); + // 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, title); - handler.OnInventoryOpen(inventory); + Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title)); + window_actions[windowID] = 0; + handler.OnInventoryOpen(windowID, inventory); } } break; @@ -521,103 +526,32 @@ namespace MinecraftClient.Protocol.Handlers if (handler.GetInventoryEnabled()) { byte windowID = dataTypes.ReadNextByte(packetData); + window_actions[windowID] = 0; handler.OnInventoryClose(windowID); } break; case PacketIncomingType.WindowItems: if (handler.GetInventoryEnabled()) { - // MC 1.12.2 or lower - if (protocolversion < MC113Version) + byte windowId = dataTypes.ReadNextByte(packetData); + short elements = dataTypes.ReadNextShort(packetData); + Dictionary inventorySlots = new Dictionary(); + for (short slotId = 0; slotId < elements; slotId++) { - byte id = dataTypes.ReadNextByte(packetData); - short elements = dataTypes.ReadNextShort(packetData); - Dictionary itemsList = new Dictionary(); // index is SlotID - - for (int i = 0; i < elements; i++) - { - short itemID = dataTypes.ReadNextShort(packetData); - if (itemID == -1) continue; - byte itemCount = dataTypes.ReadNextByte(packetData); - short itemDamage = dataTypes.ReadNextShort(packetData); - Dictionary NBT = new Dictionary(); - //TODO: Add to the dictionary for the inventory its in using the id - if (packetData.ToArray().Count() > 0) - { - NBT = dataTypes.ReadNextNbt(packetData); - } - Item item = new Item(itemID, itemCount, itemDamage, NBT); - itemsList.Add(i, item); - } - handler.OnWindowItems(id, itemsList); - } - else - { - // MC 1.13 after - byte id = dataTypes.ReadNextByte(packetData); - short elements = dataTypes.ReadNextShort(packetData); - Dictionary itemsList = new Dictionary(); // index is SlotID - for (int i = 0; i < elements; i++) - { - bool haveItem = dataTypes.ReadNextBool(packetData); - if (haveItem) - { - int itemID = dataTypes.ReadNextVarInt(packetData); - byte itemCount = dataTypes.ReadNextByte(packetData); - dataTypes.ReadNextNbt(packetData); - - Item item = new Item(itemID, itemCount, i); - itemsList.Add(i, item); - } - } - handler.OnWindowItems(id, itemsList); + Item item = dataTypes.ReadNextItemSlot(packetData); + if (item != null) + inventorySlots[slotId] = item; } + handler.OnWindowItems(windowId, inventorySlots); } break; case PacketIncomingType.SetSlot: - if(handler.GetInventoryEnabled()) + if (handler.GetInventoryEnabled()) { - // MC 1.12.2 or lower - if (protocolversion < MC113Version) - { - byte WindowID = dataTypes.ReadNextByte(packetData); - short SlotID = dataTypes.ReadNextShort(packetData); - short ItemID = dataTypes.ReadNextShort(packetData); - if (ItemID == -1) - { - handler.OnSlotClear(WindowID, SlotID); - } - else - { - byte Count = dataTypes.ReadNextByte(packetData); - short itemDamage = dataTypes.ReadNextShort(packetData); // useless so ignored - Dictionary NBT = new Dictionary(); - //TODO: Add to the dictionary for the inventory its in using the id - if (packetData.ToArray().Count() > 0) - { - NBT = dataTypes.ReadNextNbt(packetData); - } - handler.OnSetSlot(WindowID, SlotID, ItemID, Count, NBT); - } - } - else - { - // MC 1.13 after - byte WindowID = dataTypes.ReadNextByte(packetData); - short SlotID = dataTypes.ReadNextShort(packetData); - bool Present = dataTypes.ReadNextBool(packetData); - if (Present) - { - int ItemID = dataTypes.ReadNextVarInt(packetData); - byte Count = dataTypes.ReadNextByte(packetData); - Dictionary NBT = dataTypes.ReadNextNbt(packetData); - handler.OnSetSlot(WindowID, SlotID, ItemID, Count, NBT); - } - else - { - handler.OnSlotClear(WindowID, SlotID); - } - } + byte windowID = dataTypes.ReadNextByte(packetData); + short slotID = dataTypes.ReadNextShort(packetData); + Item item = dataTypes.ReadNextItemSlot(packetData); + handler.OnSetSlot(windowID, slotID, item); } break; case PacketIncomingType.ResourcePackSend: @@ -1285,7 +1219,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// /// - public bool SendInteractEntityPacket(int EntityID, int type) + public bool SendInteractEntity(int EntityID, int type) { try { @@ -1300,7 +1234,7 @@ namespace MinecraftClient.Protocol.Handlers catch (ObjectDisposedException) { return false; } } // TODO: Interact at block location (e.g. chest minecart) - public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) { try { @@ -1318,12 +1252,12 @@ namespace MinecraftClient.Protocol.Handlers catch (System.IO.IOException) { return false; } catch (ObjectDisposedException) { return false; } } - public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) { return false; } - public bool SendUseItemPacket(int hand) + public bool SendUseItem(int hand) { try { @@ -1370,5 +1304,45 @@ namespace MinecraftClient.Protocol.Handlers catch (System.IO.IOException) { return false; } catch (ObjectDisposedException) { return false; } } + + public bool SendClickWindow(int windowId, int slotId, Item item) + { + try + { + short actionNumber = (short)(window_actions[windowId] + 1); + window_actions[windowId] = actionNumber; + + List packet = new List(); + packet.Add((byte)windowId); + packet.AddRange(dataTypes.GetShort((short)slotId)); + packet.Add(0); // Left mouse click + packet.AddRange(dataTypes.GetShort(actionNumber)); + + // Operation mode = 0 (default) + if (protocolversion >= MC19Version) + packet.AddRange(dataTypes.GetVarInt(0)); + else packet.Add(0); + + 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 SendCloseWindow(int windowId) + { + try + { + SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId }); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18PacketTypes.cs b/MinecraftClient/Protocol/Handlers/Protocol18PacketTypes.cs index 8e6cf3ef..032bce34 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18PacketTypes.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18PacketTypes.cs @@ -306,6 +306,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.HeldItemChange: return 0x17; case PacketOutgoingType.InteractEntity: return 0x02; case PacketOutgoingType.TeleportConfirm: throw new InvalidOperationException("Teleport confirm is not supported in protocol " + protocol); + case PacketOutgoingType.ClickWindow: return 0x0E; + case PacketOutgoingType.CloseWindow: return 0x0D; } } else if (protocol <= Protocol18Handler.MC1112Version) // MC 1.9, 1,10 and 1.11 @@ -324,6 +326,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.TeleportConfirm: return 0x00; case PacketOutgoingType.HeldItemChange: return 0x17; case PacketOutgoingType.InteractEntity: return 0x0A; + case PacketOutgoingType.ClickWindow: return 0x07; + case PacketOutgoingType.CloseWindow: return 0x08; } } else if (protocol <= Protocol18Handler.MC112Version) // MC 1.12 @@ -342,6 +346,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.TeleportConfirm: return 0x00; case PacketOutgoingType.HeldItemChange: return 0x1A; case PacketOutgoingType.InteractEntity: return 0x0B; + case PacketOutgoingType.ClickWindow: return 0x07; + case PacketOutgoingType.CloseWindow: return 0x08; } } else if (protocol <= Protocol18Handler.MC1122Version) // 1.12.2 @@ -360,6 +366,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.TeleportConfirm: return 0x00; case PacketOutgoingType.HeldItemChange: return 0x1F; case PacketOutgoingType.InteractEntity: return 0x0A; + case PacketOutgoingType.ClickWindow: return 0x07; + case PacketOutgoingType.CloseWindow: return 0x08; } } else if (protocol < Protocol18Handler.MC114Version) // MC 1.13 to 1.13.2 @@ -378,6 +386,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.TeleportConfirm: return 0x00; case PacketOutgoingType.HeldItemChange: return 0x21; case PacketOutgoingType.InteractEntity: return 0x0D; + case PacketOutgoingType.ClickWindow: return 0x08; + case PacketOutgoingType.CloseWindow: return 0x09; } } else // MC 1.14 to 1.15 @@ -398,6 +408,8 @@ namespace MinecraftClient.Protocol.Handlers case PacketOutgoingType.InteractEntity: return 0x0E; case PacketOutgoingType.UseItem: return 0x2D; case PacketOutgoingType.PlayerBlockPlacement: return 0x2C; + case PacketOutgoingType.ClickWindow: return 0x09; + case PacketOutgoingType.CloseWindow: return 0x0A; } } diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 60aa7a33..1b876db1 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using MinecraftClient.Crypto; using MinecraftClient.Mapping; +using MinecraftClient.Inventory; namespace MinecraftClient.Protocol { @@ -98,7 +99,7 @@ namespace MinecraftClient.Protocol /// Entity ID to interact with /// Type of interaction (0: interact, 1: attack, 2: interact at) /// True if packet was successfully sent - bool SendInteractEntityPacket(int EntityID, int type); + bool SendInteractEntity(int EntityID, int type); /// /// Send an entity interaction packet to the server. @@ -110,7 +111,7 @@ namespace MinecraftClient.Protocol /// Z coordinate for "interact at" /// Player hand (0: main hand, 1: off hand) /// True if packet was successfully sent - bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand); + bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand); /// /// Send an entity interaction packet to the server. @@ -121,14 +122,29 @@ namespace MinecraftClient.Protocol /// Y coordinate for "interact at" /// Z coordinate for "interact at" /// True if packet was successfully sent - bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z); + bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z); /// /// Send a use item packet to the server /// /// 0: main hand, 1: off hand /// True if packet was successfully sent - bool SendUseItemPacket(int hand); + bool SendUseItem(int hand); + + /// + /// Send a click window slot packet to the server + /// + /// Id of the window being clicked + /// Id of the clicked slot + /// Item in the clicked slot + /// True if packet was successfully sent + bool SendClickWindow(int windowId, int slotId, Item item); + + /// + /// Send a close window packet to the server + /// + /// Id of the window being closed + bool SendCloseWindow(int windowId); /// /// Send player block placement packet to the server diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 2771a664..ecf87e7a 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -53,12 +53,12 @@ namespace MinecraftClient.Protocol /// /// Called when an inventory is opened /// - void OnInventoryOpen(Container inventory); + void OnInventoryOpen(int inventoryID, Container inventory); /// /// Called when an inventory is closed /// - void OnInventoryClose(byte inventoryID); + void OnInventoryClose(int inventoryID); /// /// Called when the player respawns, which happens on login, respawn and world change. @@ -199,26 +199,17 @@ namespace MinecraftClient.Protocol /// /// Called when inventory items have been received /// - /// Inventory type + /// Inventory ID /// Item list - void OnWindowItems(int type, Dictionary itemList); - - /// - /// Called when a single slot has been cleared inside an inventory - /// - /// Inventory ID - /// Slot ID - void OnSlotClear(byte WindowID, short SlotID); + void OnWindowItems(byte inventoryID, Dictionary itemList); /// /// Called when a single slot has been updated inside an inventory /// - /// Inventory ID - /// Slot ID - /// Item ID - /// Item Count - /// Item Metadata - void OnSetSlot(byte WindowID, short SlotID, int ItemID, byte Count, Dictionary NBT); + /// Window ID + /// Slot ID + /// Item (may be null for empty slot) + void OnSetSlot(byte inventoryID, short slotID, Item item); /// /// Called when the Player entity ID has been received from the server