Minecraft-Console-Client/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/ProfileComponent.cs

232 lines
7.8 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using MinecraftClient.Inventory.ItemPalettes;
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
public class ProfileComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
{
public bool HasName { get; set; }
public string? Name { get; set; } = null!;
public bool HasUniqueId { get; set; }
public Guid Uuid { get; set; }
public int NumberOfProperties { get; set; }
public List<ProfileProperty> ProfileProperties { get; set; } = [];
public bool IsFullProfile { get; set; }
public string? BodyAssetId { get; set; }
public string? CapeAssetId { get; set; }
public string? ElytraAssetId { get; set; }
public ProfileSkinModel? Model { get; set; }
public override void Parse(Queue<byte> data)
{
ResetState();
if (DataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_9_Version)
{
ParseResolvableProfile(data);
return;
}
ParseLegacyProfile(data);
}
public override Queue<byte> Serialize()
{
return DataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_9_Version
? SerializeResolvableProfile()
: SerializeLegacyProfile();
}
private void ResetState()
{
HasName = false;
Name = null;
HasUniqueId = false;
Uuid = Guid.Empty;
NumberOfProperties = 0;
ProfileProperties = [];
IsFullProfile = false;
BodyAssetId = null;
CapeAssetId = null;
ElytraAssetId = null;
Model = null;
}
private void ParseLegacyProfile(Queue<byte> data)
{
HasName = DataTypes.ReadNextBool(data);
if (HasName)
Name = DataTypes.ReadNextString(data);
HasUniqueId = DataTypes.ReadNextBool(data);
if (HasUniqueId)
Uuid = DataTypes.ReadNextUUID(data);
NumberOfProperties = DataTypes.ReadNextVarInt(data);
ProfileProperties = ReadProfileProperties(data, NumberOfProperties);
}
private void ParseResolvableProfile(Queue<byte> data)
{
IsFullProfile = DataTypes.ReadNextBool(data);
if (IsFullProfile)
{
HasUniqueId = true;
Uuid = DataTypes.ReadNextUUID(data);
HasName = true;
Name = DataTypes.ReadNextString(data);
NumberOfProperties = DataTypes.ReadNextVarInt(data);
ProfileProperties = ReadProfileProperties(data, NumberOfProperties);
}
else
{
HasName = DataTypes.ReadNextBool(data);
if (HasName)
Name = DataTypes.ReadNextString(data);
HasUniqueId = DataTypes.ReadNextBool(data);
if (HasUniqueId)
Uuid = DataTypes.ReadNextUUID(data);
NumberOfProperties = DataTypes.ReadNextVarInt(data);
ProfileProperties = ReadProfileProperties(data, NumberOfProperties);
}
BodyAssetId = ReadOptionalResourceLocation(data);
CapeAssetId = ReadOptionalResourceLocation(data);
ElytraAssetId = ReadOptionalResourceLocation(data);
if (DataTypes.ReadNextBool(data))
Model = DataTypes.ReadNextBool(data) ? ProfileSkinModel.Slim : ProfileSkinModel.Wide;
}
private Queue<byte> SerializeLegacyProfile()
{
var data = new List<byte>();
NumberOfProperties = ProfileProperties.Count;
data.AddRange(DataTypes.GetBool(HasName));
if (HasName)
{
if (string.IsNullOrEmpty(Name))
throw new NullReferenceException("Can't serialize the ProfileComponent because the Name is null/empty!");
data.AddRange(DataTypes.GetString(Name));
}
fix: StructuredComponents batch 1 audit — TrimComponent, ProfileComponent, WrittenBookContent, and NBT serialization Audited all 8 high-complexity structured components against official 1.20.6 decompiled STREAM_CODEC definitions. Found and fixed bugs in 3 components plus a systemic NBT serialization issue: TrimComponent (ID 35): - Serialize had TrimPatternType and ShowInTooltip incorrectly nested inside the TrimMaterialType==0 branch; moved them outside to match Parse logic - Description fields (TrimMaterial.description, TrimPattern.description) were read/written as String but official codec uses ComponentSerialization (NBT Tag format); changed to ReadNextNbt/GetNbt ProfileComponent (ID 46): - Serialize was missing the HasUniqueId Bool prefix before UUID - Serialize only wrote properties when count > 0 but omitted the VarInt count prefix entirely when empty; now always writes VarInt count WrittenBookContentComponent (ID 34): - Page content uses Filterable<Component> where Component is NBT-encoded via ComponentSerialization.STREAM_CODEC, not plain String; changed Parse to use ReadNextNbt and Serialize to use GetNbt - Added RawContentNbt/FilteredContentNbt fields to BookPage record for round-trip NBT preservation - Removed unnecessary ChatParser.ParseText on title (it's a plain string) DataTypes.GetNbt: - Added TAG_String root support for 1.20.4+ (chat components like "Page 1" are encoded as TAG_String, not TAG_Compound) - Fixed root name handling: versions >= 1.20.2 omit the root compound name, but GetNbt was unconditionally writing it Components confirmed correct (no changes needed): - FoodComponentComponent (ID 20), ToolComponent (ID 22), InstrumentComponent (ID 40), PotionContentsComponent (ID 31), AttributeModifiersComponent (ID 12) Made-with: Cursor
2026-03-20 00:09:05 +08:00
data.AddRange(DataTypes.GetBool(HasUniqueId));
if (HasUniqueId)
data.AddRange(DataTypes.GetUUID(Uuid));
data.AddRange(DataTypes.GetVarInt(NumberOfProperties));
SerializeProfileProperties(data);
return new Queue<byte>(data);
}
private Queue<byte> SerializeResolvableProfile()
{
var data = new List<byte>();
NumberOfProperties = ProfileProperties.Count;
data.AddRange(DataTypes.GetBool(IsFullProfile));
if (IsFullProfile)
{
if (!HasUniqueId)
throw new NullReferenceException("Can't serialize the ProfileComponent because a full profile requires a UUID!");
if (!HasName || string.IsNullOrEmpty(Name))
throw new NullReferenceException("Can't serialize the ProfileComponent because a full profile requires a name!");
data.AddRange(DataTypes.GetUUID(Uuid));
data.AddRange(DataTypes.GetString(Name));
}
else
{
data.AddRange(DataTypes.GetBool(HasName));
if (HasName)
{
if (string.IsNullOrEmpty(Name))
throw new NullReferenceException("Can't serialize the ProfileComponent because HasName is true, but the Name is null/empty!");
data.AddRange(DataTypes.GetString(Name));
}
data.AddRange(DataTypes.GetBool(HasUniqueId));
if (HasUniqueId)
data.AddRange(DataTypes.GetUUID(Uuid));
}
data.AddRange(DataTypes.GetVarInt(NumberOfProperties));
SerializeProfileProperties(data);
SerializeOptionalResourceLocation(data, BodyAssetId);
SerializeOptionalResourceLocation(data, CapeAssetId);
SerializeOptionalResourceLocation(data, ElytraAssetId);
data.AddRange(DataTypes.GetBool(Model.HasValue));
if (Model.HasValue)
data.AddRange(DataTypes.GetBool(Model.Value == ProfileSkinModel.Slim));
return new Queue<byte>(data);
}
private List<ProfileProperty> ReadProfileProperties(Queue<byte> data, int count)
{
var properties = new List<ProfileProperty>(count);
for (var i = 0; i < count; i++)
{
var propertyName = DataTypes.ReadNextString(data);
var propertyValue = DataTypes.ReadNextString(data);
var hasSignature = DataTypes.ReadNextBool(data);
var signature = hasSignature ? DataTypes.ReadNextString(data) : null;
properties.Add(new ProfileProperty(propertyName, propertyValue, hasSignature, signature));
}
return properties;
}
private void SerializeProfileProperties(List<byte> data)
{
fix: StructuredComponents batch 1 audit — TrimComponent, ProfileComponent, WrittenBookContent, and NBT serialization Audited all 8 high-complexity structured components against official 1.20.6 decompiled STREAM_CODEC definitions. Found and fixed bugs in 3 components plus a systemic NBT serialization issue: TrimComponent (ID 35): - Serialize had TrimPatternType and ShowInTooltip incorrectly nested inside the TrimMaterialType==0 branch; moved them outside to match Parse logic - Description fields (TrimMaterial.description, TrimPattern.description) were read/written as String but official codec uses ComponentSerialization (NBT Tag format); changed to ReadNextNbt/GetNbt ProfileComponent (ID 46): - Serialize was missing the HasUniqueId Bool prefix before UUID - Serialize only wrote properties when count > 0 but omitted the VarInt count prefix entirely when empty; now always writes VarInt count WrittenBookContentComponent (ID 34): - Page content uses Filterable<Component> where Component is NBT-encoded via ComponentSerialization.STREAM_CODEC, not plain String; changed Parse to use ReadNextNbt and Serialize to use GetNbt - Added RawContentNbt/FilteredContentNbt fields to BookPage record for round-trip NBT preservation - Removed unnecessary ChatParser.ParseText on title (it's a plain string) DataTypes.GetNbt: - Added TAG_String root support for 1.20.4+ (chat components like "Page 1" are encoded as TAG_String, not TAG_Compound) - Fixed root name handling: versions >= 1.20.2 omit the root compound name, but GetNbt was unconditionally writing it Components confirmed correct (no changes needed): - FoodComponentComponent (ID 20), ToolComponent (ID 22), InstrumentComponent (ID 40), PotionContentsComponent (ID 31), AttributeModifiersComponent (ID 12) Made-with: Cursor
2026-03-20 00:09:05 +08:00
foreach (var profileProperty in ProfileProperties)
{
fix: StructuredComponents batch 1 audit — TrimComponent, ProfileComponent, WrittenBookContent, and NBT serialization Audited all 8 high-complexity structured components against official 1.20.6 decompiled STREAM_CODEC definitions. Found and fixed bugs in 3 components plus a systemic NBT serialization issue: TrimComponent (ID 35): - Serialize had TrimPatternType and ShowInTooltip incorrectly nested inside the TrimMaterialType==0 branch; moved them outside to match Parse logic - Description fields (TrimMaterial.description, TrimPattern.description) were read/written as String but official codec uses ComponentSerialization (NBT Tag format); changed to ReadNextNbt/GetNbt ProfileComponent (ID 46): - Serialize was missing the HasUniqueId Bool prefix before UUID - Serialize only wrote properties when count > 0 but omitted the VarInt count prefix entirely when empty; now always writes VarInt count WrittenBookContentComponent (ID 34): - Page content uses Filterable<Component> where Component is NBT-encoded via ComponentSerialization.STREAM_CODEC, not plain String; changed Parse to use ReadNextNbt and Serialize to use GetNbt - Added RawContentNbt/FilteredContentNbt fields to BookPage record for round-trip NBT preservation - Removed unnecessary ChatParser.ParseText on title (it's a plain string) DataTypes.GetNbt: - Added TAG_String root support for 1.20.4+ (chat components like "Page 1" are encoded as TAG_String, not TAG_Compound) - Fixed root name handling: versions >= 1.20.2 omit the root compound name, but GetNbt was unconditionally writing it Components confirmed correct (no changes needed): - FoodComponentComponent (ID 20), ToolComponent (ID 22), InstrumentComponent (ID 40), PotionContentsComponent (ID 31), AttributeModifiersComponent (ID 12) Made-with: Cursor
2026-03-20 00:09:05 +08:00
data.AddRange(DataTypes.GetString(profileProperty.Name));
data.AddRange(DataTypes.GetString(profileProperty.Value));
data.AddRange(DataTypes.GetBool(profileProperty.HasSignature));
if (!profileProperty.HasSignature)
continue;
if (string.IsNullOrEmpty(profileProperty.Signature))
throw new NullReferenceException("Can't serialize the ProfileComponent because HasSignature is true, but the Signature is null/empty!");
data.AddRange(DataTypes.GetString(profileProperty.Signature));
}
}
private string? ReadOptionalResourceLocation(Queue<byte> data)
{
return DataTypes.ReadNextBool(data) ? DataTypes.ReadNextString(data) : null;
}
private void SerializeOptionalResourceLocation(List<byte> data, string? resourceLocation)
{
data.AddRange(DataTypes.GetBool(!string.IsNullOrEmpty(resourceLocation)));
if (!string.IsNullOrEmpty(resourceLocation))
data.AddRange(DataTypes.GetString(resourceLocation));
}
}
public record ProfileProperty(string Name, string Value, bool HasSignature, string? Signature);
public enum ProfileSkinModel
{
Wide,
Slim
}