mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Fix StructuredComponent serialization correctness for 1.20.6
Audited all 58 StructuredComponent subclasses against the official Minecraft
1.20.6 decompiled source to verify Parse()/Serialize() symmetry. Found and
fixed four bugs across four components:
1. ContainerComponent: Parse() skipped null item slots (empty slots in a
container) but Serialize() looped NumberOfItems times using Items[i],
causing IndexOutOfRangeException when any slot was empty. The official
ItemContainerContents uses OPTIONAL_STREAM_CODEC which serializes empty
slots as VarInt(0). Fixed: Parse now stores all slots including nulls,
Serialize uses Items.Count and iterates all entries. GetItemSlot(null)
correctly writes VarInt(0) for empty slots.
2. ChargedProjectilesComponent: Used Items.OfType<Item>() in Serialize()
which silently dropped null entries, causing the serialized count to
differ from the written VarInt header. The official ChargedProjectiles
uses STREAM_CODEC (non-optional, no empty slots allowed). Fixed: Items
list is now List<Item> (non-nullable), Parse defensively skips nulls,
Serialize writes Items.Count matching the actual list.
3. BundleContentsComponent: Same issue as ChargedProjectilesComponent.
Applied the same fix pattern.
4. FoodComponentComponent: Two type mismatches vs the official
FoodProperties.DIRECT_STREAM_CODEC:
- Saturation was declared as bool and read with ReadNextBool (1 byte),
but the protocol sends it as float (4 bytes). This caused all
subsequent fields in the component to be read at wrong offsets,
corrupting CanAlwaysEat, SecondsToEat, and the effects list.
- NumberOfEffects was serialized with GetFloat() instead of GetVarInt(),
writing 4 bytes of IEEE 754 float instead of a variable-length integer.
Fixed both Parse and Serialize to use correct types.
Also removed redundant NumberOfItems/NumberOfEffects fields from components
where the count is derivable from the list length, and replaced
ArgumentNullException with cleaner patterns.
Tested end-to-end on vanilla 1.20.6 server: item receiving (diamond_sword,
golden_apple, diamond_pickaxe), inventory slot movement (click to pick up
and place), and inventory listing all work correctly with no server-side
protocol errors.
Made-with: Cursor
This commit is contained in:
parent
b692b13bbc
commit
56f2426c1f
4 changed files with 36 additions and 54 deletions
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MinecraftClient.Inventory;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
|
@ -10,28 +8,28 @@ namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_2
|
|||
public class BundleContentsComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int NumberOfItems { get; set; }
|
||||
public List<Item?> Items { get; set; } = [];
|
||||
public List<Item> Items { get; set; } = [];
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
NumberOfItems = dataTypes.ReadNextVarInt(data);
|
||||
var count = dataTypes.ReadNextVarInt(data);
|
||||
|
||||
for (var i = 0; i < NumberOfItems; i++)
|
||||
Items.Add(dataTypes.ReadNextItemSlot(data, itemPalette));
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = dataTypes.ReadNextItemSlot(data, itemPalette);
|
||||
if (item != null)
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(NumberOfItems));
|
||||
data.AddRange(DataTypes.GetVarInt(Items.Count));
|
||||
|
||||
if (NumberOfItems != Items.Count)
|
||||
throw new ArgumentNullException($"Cannot serialize BundleContentsComponent1206 because NumberOfItems != Items.Count!");
|
||||
|
||||
foreach (var item in Items.OfType<Item>())
|
||||
foreach (var item in Items)
|
||||
data.AddRange(DataTypes.GetItemSlot(item, itemPalette));
|
||||
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MinecraftClient.Inventory;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
|
@ -10,28 +8,28 @@ namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_2
|
|||
public class ChargedProjectilesComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int NumberOfItems { get; set; }
|
||||
public List<Item?> Items { get; set; } = [];
|
||||
public List<Item> Items { get; set; } = [];
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
NumberOfItems = dataTypes.ReadNextVarInt(data);
|
||||
var count = dataTypes.ReadNextVarInt(data);
|
||||
|
||||
for (var i = 0; i < NumberOfItems; i++)
|
||||
Items.Add(dataTypes.ReadNextItemSlot(data, itemPalette));
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = dataTypes.ReadNextItemSlot(data, itemPalette);
|
||||
if (item != null)
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(NumberOfItems));
|
||||
data.AddRange(DataTypes.GetVarInt(Items.Count));
|
||||
|
||||
if (NumberOfItems != Items.Count)
|
||||
throw new ArgumentNullException($"Cannot serialize ChargedProjectilesComponent1206 because NumberOfItems != Items.Count!");
|
||||
|
||||
foreach (var item in Items.OfType<Item>())
|
||||
foreach (var item in Items)
|
||||
data.AddRange(DataTypes.GetItemSlot(item, itemPalette));
|
||||
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,29 +9,22 @@ public class ContainerComponent(DataTypes dataTypes, ItemPalette itemPalette, Su
|
|||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int NumberOfItems { get; set; }
|
||||
public List<Item> Items { get; set; } = [];
|
||||
public List<Item?> Items { get; set; } = [];
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
NumberOfItems = dataTypes.ReadNextVarInt(data);
|
||||
for (var i = 0; i < NumberOfItems; i++)
|
||||
{
|
||||
var item = dataTypes.ReadNextItemSlot(data, ItemPalette);
|
||||
|
||||
if (item is null)
|
||||
continue;
|
||||
|
||||
Items.Add(item);
|
||||
}
|
||||
Items.Add(dataTypes.ReadNextItemSlot(data, ItemPalette));
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(NumberOfItems));
|
||||
for (var i = 0; i < NumberOfItems; i++)
|
||||
data.AddRange(DataTypes.GetItemSlot(Items[i], itemPalette));
|
||||
data.AddRange(DataTypes.GetVarInt(Items.Count));
|
||||
foreach (var item in Items)
|
||||
data.AddRange(DataTypes.GetItemSlot(item, itemPalette));
|
||||
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,21 +11,20 @@ public class FoodComponentComponent(DataTypes dataTypes, ItemPalette itemPalette
|
|||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int Nutrition { get; set; }
|
||||
public bool Saturation { get; set; }
|
||||
public float Saturation { get; set; }
|
||||
public bool CanAlwaysEat { get; set; }
|
||||
public float SecondsToEat { get; set; }
|
||||
public int NumberOfEffects { get; set; }
|
||||
public List<EffectSubComponent> Effects { get; set; } = new();
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
Nutrition = dataTypes.ReadNextVarInt(data);
|
||||
Saturation = dataTypes.ReadNextBool(data);
|
||||
Saturation = dataTypes.ReadNextFloat(data);
|
||||
CanAlwaysEat = dataTypes.ReadNextBool(data);
|
||||
SecondsToEat = dataTypes.ReadNextFloat(data);
|
||||
NumberOfEffects = dataTypes.ReadNextVarInt(data);
|
||||
var numberOfEffects = dataTypes.ReadNextVarInt(data);
|
||||
|
||||
for(var i = 0; i < NumberOfEffects; i++)
|
||||
for(var i = 0; i < numberOfEffects; i++)
|
||||
Effects.Add((EffectSubComponent)subComponentRegistry.ParseSubComponent(SubComponents.Effect, data));
|
||||
}
|
||||
|
||||
|
|
@ -33,19 +32,13 @@ public class FoodComponentComponent(DataTypes dataTypes, ItemPalette itemPalette
|
|||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(Nutrition));
|
||||
data.AddRange(DataTypes.GetBool(Saturation));
|
||||
data.AddRange(DataTypes.GetFloat(Saturation));
|
||||
data.AddRange(DataTypes.GetBool(CanAlwaysEat));
|
||||
data.AddRange(DataTypes.GetFloat(SecondsToEat));
|
||||
data.AddRange(DataTypes.GetFloat(NumberOfEffects));
|
||||
data.AddRange(DataTypes.GetVarInt(Effects.Count));
|
||||
|
||||
if (NumberOfEffects > 0)
|
||||
{
|
||||
if(Effects.Count != NumberOfEffects)
|
||||
throw new ArgumentNullException($"Can not serialize FoodComponent1206 due to NumberOfEffcets being different from the count of elements in the Effects list!");
|
||||
|
||||
foreach(var effect in Effects)
|
||||
data.AddRange(effect.Serialize());
|
||||
}
|
||||
foreach(var effect in Effects)
|
||||
data.AddRange(effect.Serialize());
|
||||
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue