From fba50748eae018e21490ec921ddd834c5eb13234 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Aug 2026 06:21:30 +0400 Subject: [PATCH 1/3] fix: a null sub-packet loses its separating space A sub-packet serialized from null returns "-1" without the leading separator the non-null path gets from its discriminator, so the -1 ends up glued to the field before it and the client cannot split the packet into fields at all: sc_n 1 319 26719 50 1000 1536-1-1-1 -1 0 ... instead of sc_n 1 319 26719 50 1000 1536 -1 -1 -1 0 ... It shows anywhere a sub-packet property can be null, which today means sc_n's four equipment slots and gidx's family identifier. Co-Authored-By: Claude Opus 5 --- src/NosCore.Packets/Serializer.cs | 6 +++++- test/NosCore.Packets.Tests/SerializerTest.cs | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/NosCore.Packets/Serializer.cs b/src/NosCore.Packets/Serializer.cs index e53ce85c..dcbfafb5 100644 --- a/src/NosCore.Packets/Serializer.cs +++ b/src/NosCore.Packets/Serializer.cs @@ -271,9 +271,13 @@ private Expression PacketSerializer(Expression injectedPacket, PacketIndexAttrib incrementExpr = Expression.Constant(!isFromList || !isOptionalSerie); } + // A null sub-packet is written as -1, and it has to carry the same leading separator + // the non-null path gets from the discriminator. Without it the -1 is glued to the + // field before it and the client cannot split the packet into fields at all: + // sc_n ... 1536-1-1-1 -1 ... instead of sc_n ... 1536 -1 -1 -1 ... return Expression.Condition( Expression.Equal(specificTypeExpression, Expression.Constant(null, typeof(object))), - Expression.Constant(indexAttr.IsOptional ? null : "-1", typeof(object)), + Expression.Constant(indexAttr.IsOptional ? null : $"{discriminator}-1", typeof(object)), Expression.Convert(propExp, typeof(object)) ); } diff --git a/test/NosCore.Packets.Tests/SerializerTest.cs b/test/NosCore.Packets.Tests/SerializerTest.cs index db9843f4..36b370b7 100644 --- a/test/NosCore.Packets.Tests/SerializerTest.cs +++ b/test/NosCore.Packets.Tests/SerializerTest.cs @@ -518,6 +518,26 @@ public void SerializeWithNullFirstParam() packet); } + [TestMethod] + public void SerializeWithNullSubPacketKeepsTheSeparator() + { + var packet = Serializer.Serialize(new ScnPacket + { + PetId = 1, + NpcMonsterVNum = 319, + TransportId = 26719, + Level = 50, + Loyalty = 1000, + Experience = 1536, + WeaponInstanceDetails = new ScnPacket.ScEquipmentDetails { ItemId = 990 }, + Name = "Kliff", + MorphId = -1 + }); + + Assert.IsTrue(packet.StartsWith("sc_n 1 319 26719 50 1000 1536 990.0.0 -1 -1 -1 "), + $"the -1 of a null sub-packet must not be glued to the field before it: {packet}"); + } + [TestMethod] public void SerializeWithSpecialSeparator() { From 8ea1796799a5d2bcd39ee83fe700e93a5b0c4ac1 Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Sun, 23 Aug 2026 17:17:49 +1200 Subject: [PATCH 2/3] test: register ScnPacket with the test serializer The new case built an ScnPacket but the serializer in this fixture only knows the types it is handed, so Serialize threw KeyNotFoundException before it could assert anything. Co-Authored-By: Claude Opus 5 (1M context) --- test/NosCore.Packets.Tests/SerializerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/NosCore.Packets.Tests/SerializerTest.cs b/test/NosCore.Packets.Tests/SerializerTest.cs index 36b370b7..3c2a5c77 100644 --- a/test/NosCore.Packets.Tests/SerializerTest.cs +++ b/test/NosCore.Packets.Tests/SerializerTest.cs @@ -80,6 +80,7 @@ public class SerializationTests typeof(MallPacket), typeof(FtptPacket), typeof(ScpIndicatorPacket), + typeof(ScnPacket), typeof(EsfPacket), typeof(SopenPacket), typeof(StbmPacket), From 2bc808fcc1f6535af2392c789948327d20278687 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Aug 2026 09:19:18 +0400 Subject: [PATCH 3/3] chore: drop the comment Co-Authored-By: Claude Opus 5 --- src/NosCore.Packets/Serializer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/NosCore.Packets/Serializer.cs b/src/NosCore.Packets/Serializer.cs index dcbfafb5..4d230542 100644 --- a/src/NosCore.Packets/Serializer.cs +++ b/src/NosCore.Packets/Serializer.cs @@ -271,10 +271,6 @@ private Expression PacketSerializer(Expression injectedPacket, PacketIndexAttrib incrementExpr = Expression.Constant(!isFromList || !isOptionalSerie); } - // A null sub-packet is written as -1, and it has to carry the same leading separator - // the non-null path gets from the discriminator. Without it the -1 is glued to the - // field before it and the client cannot split the packet into fields at all: - // sc_n ... 1536-1-1-1 -1 ... instead of sc_n ... 1536 -1 -1 -1 ... return Expression.Condition( Expression.Equal(specificTypeExpression, Expression.Constant(null, typeof(object))), Expression.Constant(indexAttr.IsOptional ? null : $"{discriminator}-1", typeof(object)),