From 3b1c9fadbbee9e40f428cd33b883d7e7f743cfdc Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Sun, 30 Aug 2026 13:53:46 +1200 Subject: [PATCH] fix(serializer): a sub-packet's last field is its own, not the parent's PacketSerializer passed the parent's maxIndex down when recursing into a sub-packet, so whichever nested field happened to share that number inherited the last-field exemption and kept its spaces. InPacket ends at index 9 and InNonPlayerSubPacket.Name is index 9, so a spaced mate name went out as in 2 333 2000001 ... -1 Joyeux Mouton 0 -1 ... splitting the field. Each sub-packet now gets its own maxIndex, so the exemption applies where a field really does end the line and nowhere else. --- src/NosCore.Packets/NosCore.Packets.csproj | 2 +- src/NosCore.Packets/Serializer.cs | 8 +++++++- .../StringFieldSeparatorTests.cs | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/NosCore.Packets/NosCore.Packets.csproj b/src/NosCore.Packets/NosCore.Packets.csproj index 2c30ec1..08e8e3f 100644 --- a/src/NosCore.Packets/NosCore.Packets.csproj +++ b/src/NosCore.Packets/NosCore.Packets.csproj @@ -12,7 +12,7 @@ https://github.com/NosCoreIO/NosCore.Packets.git nostale, noscore, chickenapi, nostale private server source, nostale emulator - 21.1.0 + 21.1.1 false NosCore's Packets (Nostale packets) defined over classes MIT diff --git a/src/NosCore.Packets/Serializer.cs b/src/NosCore.Packets/Serializer.cs index 1c443d6..e80fbfb 100644 --- a/src/NosCore.Packets/Serializer.cs +++ b/src/NosCore.Packets/Serializer.cs @@ -368,7 +368,13 @@ private Expression PropertySerializer(Expression injectedPacket, PacketIndexAttr header = " "; } - specificTypeExpression = PacketSerializer(injectedPacket, indexAttr, specificTypeExpression, t, maxIndex, + // A sub-packet's fields are numbered from its own zero, so the parent's + // maxIndex would mark whichever field happens to share that number as last. + var subMaxIndex = t.GetProperties() + .SelectMany(x => x.GetCustomAttributes(true).OfType()) + .Select(x => x.Index).DefaultIfEmpty(0).Max(); + + specificTypeExpression = PacketSerializer(injectedPacket, indexAttr, specificTypeExpression, t, subMaxIndex, propertySplitter, indexAttr.RemoveHeader ? "" : header ?? ""); break; case var t when t == typeof(IPacket): diff --git a/test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs b/test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs index 6cc59bf..80962b8 100644 --- a/test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs +++ b/test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs @@ -129,5 +129,21 @@ public void AFieldThatOptsInIsEscapedEvenThoughItIsLast() Serializer.Serialize(new MlInfoBrPacket { Name = "Bob", MinilandMessage = "hello there" }), "hello^there"); } + + [TestMethod] + public void ASubPacketFieldIsNotTreatedAsLastBecauseTheParentEndsThere() + { + // InNonPlayerSubPacket.Name is index 9, and so is the sub-packet on InPacket, so + // the name used to inherit the parent's last-field exemption and keep its spaces. + var wire = Serializer.Serialize(new InPacket + { + VisualType = VisualType.Npc, + VNum = "333", + VisualId = 2000001, + InNonPlayerSubPacket = new InNonPlayerSubPacket { Name = "Joyeux Mouton" } + }); + + StringAssert.Contains(wire, "Joyeux^Mouton"); + } } }