From 63b027d84aefec55528b02a2fe247ea3a8ac414d Mon Sep 17 00:00:00 2001 From: Anon Date: Sun, 1 Sep 2024 20:42:39 +0200 Subject: [PATCH] First Version of Structured Components --- .../Protocol/Handlers/DataTypes.cs | 107 ++++++++++++++---- .../Protocol/Handlers/SocketWrapper.cs | 2 +- .../Subcomponents/TestSubComonent.cs | 19 ++++ .../Components/TestComponent.cs | 29 +++++ .../Core/StructuredComponent.cs | 12 ++ .../Core/StructuredComponentRegistry.cs | 63 +++++++++++ .../StructuredComponents/Core/SubComponent.cs | 11 ++ .../Core/SubComponentRegistry.cs | 29 +++++ .../StructuredComponentsRegistry1206.cs | 13 +++ .../Subcomponents/TestSubComponentRegistry.cs | 12 ++ .../StructuredComponentsHandler.cs | 43 +++++++ 11 files changed, 314 insertions(+), 26 deletions(-) create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index d3b77945..b62d511c 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -6,6 +6,8 @@ using MinecraftClient.Inventory; using MinecraftClient.Inventory.ItemPalettes; using MinecraftClient.Mapping; using MinecraftClient.Mapping.EntityPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Handlers @@ -13,7 +15,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Handle data types encoding / decoding /// - class DataTypes + public class DataTypes { /// /// Protocol version for adjusting data types @@ -419,37 +421,92 @@ namespace MinecraftClient.Protocol.Handlers /// The item that was read or NULL for an empty slot public Item? ReadNextItemSlot(Queue cache, ItemPalette itemPalette) { - // MC 1.13.2 and greater - if (protocolversion >= Protocol18Handler.MC_1_13_Version) + var itemId = -1; + var itemCount = 0; + var nbt = null as Dictionary; + var item = null as Item; + var strcturedComponentsToAdd = new List(); + + switch (protocolversion) { - var itemPresent = ReadNextBool(cache); + // MC 1.13.2 and greater + case >= Protocol18Handler.MC_1_20_6_Version: + itemCount = ReadNextVarInt(cache); - if (!itemPresent) - return null; + if (itemCount <= 0) return null; + + itemId = ReadNextVarInt(cache); + item = new Item(itemPalette.FromId(itemId), itemCount, null); + + var numberOfComponentsToAdd = ReadNextVarInt(cache); + var numberofComponentsToRemove = ReadNextVarInt(cache); - var itemId = ReadNextVarInt(cache); + for (var i = 0; i < numberOfComponentsToAdd; i++) + { + var componentTypeId = ReadNextVarInt(cache); - if (itemId == -1) - return null; + var strcuturedComponentHandler = new StructuredComponentsHandler(protocolversion, this); + strcturedComponentsToAdd.Add(strcuturedComponentHandler.Parse(componentTypeId, cache)); + } - var type = itemPalette.FromId(itemId); - var itemCount = ReadNextByte(cache); - var nbt = ReadNextNbt(cache); - return new Item(type, itemCount, nbt); + for (var i = 0; i < numberofComponentsToRemove; i++) + { + // TODO + } + + // TODO: Wire up the strctured components in the Item class (extract info, update fields, etc..) + // Look at: https://wiki.vg/index.php?title=Slot_Data&oldid=19350#Structured_components + + return item; + case >= Protocol18Handler.MC_1_13_Version: + { + var itemPresent = ReadNextBool(cache); + + if (!itemPresent) + return null; + + itemId = ReadNextVarInt(cache); + + if (itemId == -1) + return null; + + var type = itemPalette.FromId(itemId); + itemCount = ReadNextByte(cache); + nbt = ReadNextNbt(cache); + return new Item(type, itemCount, nbt); + } + default: + { + itemId = ReadNextShort(cache); + + if (itemId == -1) + return null; + + itemCount = ReadNextByte(cache); + var data = ReadNextShort(cache); + nbt = ReadNextNbt(cache); + + // For 1.8 - 1.12.2 we combine Item Id and Item Data/Damage to a single value using: (id << 16) | data + return new Item(itemPalette.FromId((itemId << 16) | (ushort)data), itemCount, data, nbt); + } } - else + } + + private void ReadNextDetail(Queue cache) + { + var potionEffectId = ReadNextVarInt(cache); + + // Details + var potionEffectAmplifier = ReadNextVarInt(cache); + var duration = ReadNextVarInt(cache); // -1 for infinite + var ambient = ReadNextBool(cache); + var showParticles = ReadNextBool(cache); + var showIcon = ReadNextBool(cache); + var hasHiddenEffect = ReadNextBool(cache); + + if (hasHiddenEffect) { - var itemId = ReadNextShort(cache); - - if (itemId == -1) - return null; - - var itemCount = ReadNextByte(cache); - var data = ReadNextShort(cache); - var nbt = ReadNextNbt(cache); - - // For 1.8 - 1.12.2 we combine Item Id and Item Data/Damage to a single value using: (id << 16) | data - return new Item(itemPalette.FromId((itemId << 16) | (ushort)data), itemCount, data, nbt); + ReadNextDetail(cache); } } diff --git a/MinecraftClient/Protocol/Handlers/SocketWrapper.cs b/MinecraftClient/Protocol/Handlers/SocketWrapper.cs index d9793024..e74fc84a 100644 --- a/MinecraftClient/Protocol/Handlers/SocketWrapper.cs +++ b/MinecraftClient/Protocol/Handlers/SocketWrapper.cs @@ -7,7 +7,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Wrapper for handling unencrypted & encrypted socket /// - class SocketWrapper + public class SocketWrapper { readonly TcpClient c; AesCfb8Stream? s; diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs new file mode 100644 index 00000000..fbed4b70 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; + +public class TestSubComonent(DataTypes dataTypes) : SubComponent(dataTypes) +{ + public int Test { get; set; } + + public override void Parse(Queue data) + { + Test = DataTypes.ReadNextVarInt(data); + } + + public override Queue Serialize() + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs new file mode 100644 index 00000000..befa1217 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components; + +public class TestComponent(DataTypes dataTypes, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, subComponentRegistry) +{ + public int TestInt { get; set; } + public string TestString { get; set; } = null!; + + public TestSubComonent TestSubComonent { get; set; } = null!; + + public override void Parse(Queue data) + { + TestInt = dataTypes.ReadNextVarInt(data); + TestString = dataTypes.ReadNextString(data); + TestSubComonent = (SubComponentRegistry.ParseSubComponent("TestSubComponent", data) as TestSubComonent)!; + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(TestInt)); + data.AddRange(DataTypes.GetString(TestString)); + data.AddRange(TestSubComonent.Serialize()); + return new Queue(data); + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs new file mode 100644 index 00000000..01995dc2 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +public abstract class StructuredComponent(DataTypes dataTypes, SubComponentRegistry subComponentRegistry) +{ + protected DataTypes DataTypes { get; private set; } = dataTypes; + protected SubComponentRegistry SubComponentRegistry { get; private set; } = subComponentRegistry; + + public abstract void Parse(Queue data); + public abstract Queue Serialize(); +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs new file mode 100644 index 00000000..4dffe457 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +public abstract class StructuredComponentRegistry(SubComponentRegistry subComponentRegistry, DataTypes dataTypes) +{ + private Dictionary ComponentParsers { get; } = new(); + private Dictionary IdToComponent { get; } = new(); + private Dictionary ComponentToId { get; } = new(); + + protected void RegisterComponent(int id, string name) where T : StructuredComponent + { + if (string.IsNullOrEmpty(name) || string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + name = name.ToLower(); + + if (ComponentParsers.ContainsKey(name) || IdToComponent.ContainsValue(name) + || ComponentToId.ContainsKey(name) || IdToComponent.ContainsKey(id)) + throw new InvalidOperationException($"A component with name '{name}' or id '{id}' is already registered."); + + ComponentParsers[name] = typeof(T); + IdToComponent[id] = name; + ComponentToId[name] = id; + } + + public StructuredComponent ParseComponent(int id, Queue data) + { + if (IdToComponent.TryGetValue(id, out var name)) + { + if (ComponentParsers.TryGetValue(name, out var type)) + { + var component = + Activator.CreateInstance(type, dataTypes, subComponentRegistry) as StructuredComponent + ?? throw new InvalidOperationException($"Could not instantiate a parser for a structured component type {name}"); + + component.Parse(data); + return component; + } + } + + throw new Exception($"No parser found for component with ID {id}"); + } + + public string GetComponentNameById(int id) + { + if (IdToComponent.TryGetValue(id, out var value)) + return value; + + throw new Exception($"No component found for ID {id}"); + } + + public int GetComponentIdByName(string name) + { + name = name.ToLower(); + + if (ComponentToId.TryGetValue(name, out var value)) + return value; + + throw new Exception($"No ID found for component {name}"); + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs new file mode 100644 index 00000000..af63dce8 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +public abstract class SubComponent(DataTypes dataTypes) +{ + protected DataTypes DataTypes { get; private set; } = dataTypes; + + public abstract void Parse(Queue data); + public abstract Queue Serialize(); +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs new file mode 100644 index 00000000..844107c1 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +public abstract class SubComponentRegistry(DataTypes dataTypes) +{ + private readonly Dictionary _subComponentParsers = new(); + + protected void RegisterSubComponent(string name) where T : SubComponent + { + if(_subComponentParsers.TryGetValue(name, out _)) + throw new Exception($"Sub component {name} already registered!"); + + _subComponentParsers.Add(name, typeof(T)); + } + + public SubComponent ParseSubComponent(string name, Queue data) + { + if(!_subComponentParsers.TryGetValue(name, out var subComponentParserType)) + throw new Exception($"Sub component {name} not registered!"); + + var instance= Activator.CreateInstance(subComponentParserType, dataTypes) as SubComponent ?? + throw new InvalidOperationException($"Could not create instance of a sub component parser type: {subComponentParserType.Name}"); + + instance.Parse(data); + return instance; + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs new file mode 100644 index 00000000..d69507ab --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs @@ -0,0 +1,13 @@ +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries; + +public class StructuredComponentsRegistry1206 : StructuredComponentRegistry +{ + public StructuredComponentsRegistry1206(SubComponentRegistry subComponentRegistry, DataTypes dataTypes) : base( + subComponentRegistry, dataTypes) + { + RegisterComponent(0, "minecraft:test"); + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs new file mode 100644 index 00000000..c10bbc49 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs @@ -0,0 +1,12 @@ +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries.Subcomponents; + +public class TestSubComponentRegistry : SubComponentRegistry +{ + public TestSubComponentRegistry(DataTypes dataTypes) : base(dataTypes) + { + RegisterSubComponent("TestSubcomponent"); + } +} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs new file mode 100644 index 00000000..6a03e040 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Registries; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Registries.Subcomponents; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents; + +public class StructuredComponentsHandler +{ + private StructuredComponentRegistry ComponentRegistry { get; } + + public StructuredComponentsHandler( + int protocolVersion, + DataTypes dataTypes) + { + // Get the appropriate subcomponent registry type based on the protocol version and then instantiate it + var subcomponentRegistryType = protocolVersion switch + { + Protocol18Handler.MC_1_20_6_Version => typeof(TestSubComponentRegistry), + _ => throw new NotSupportedException($"Protocol version {protocolVersion} is not supported for subcomponent registries!") + }; + + var subcomponentRegistry = Activator.CreateInstance(subcomponentRegistryType, dataTypes) as SubComponentRegistry + ?? throw new InvalidOperationException($"Failed to instantiate a component registry for type {nameof(subcomponentRegistryType)}"); + + // Get the appropriate component registry type based on the protocol version and then instantiate it + var registryType = protocolVersion switch + { + Protocol18Handler.MC_1_20_6_Version => typeof(StructuredComponentsRegistry1206), + _ => throw new NotSupportedException($"Protocol version {protocolVersion} is not supported for structured component registries!") + }; + + ComponentRegistry = Activator.CreateInstance(registryType, subcomponentRegistry, dataTypes) as StructuredComponentRegistry + ?? throw new InvalidOperationException($"Failed to instantiate a component registry for type {nameof(registryType)}"); + } + + public StructuredComponent Parse(int componentId, Queue data) + { + return ComponentRegistry.ParseComponent(componentId, data); + } +} \ No newline at end of file