Skip to content

Commit 8e581d8

Browse files
erwan-jolyclaude
andauthored
fix: Game18NArguments deserialize, sayitemt redesign, pinit unknown, rc (#454)
- **Game18NArguments**: the class is a custom non-generic IList<object>, so the generic list path crashed with IndexOutOfRange on GetGenericArguments()[0] and couldn't assign a List<object> to it anyway. Special-case it in DeserializeValue to consume the remaining tokens directly. Fixes sayi / msgi / msgi2. - **SayItemtPacket**: had four properties sharing [PacketIndex(3)] and [PacketIndex(4)], so deserialize put the character name into the Game18NConstString enum and threw "Requested value '<name>' was not found." Flattened to unique sequential indices and replaced the three typed sub-packet properties (IconInfo/EInfo/SlInfo) with a single string SubPacketRaw that captures the rest of the wire — only one of the four sub-packet shapes ever appears per packet so a typed poly field would need a real discriminator. - **PinitSubPacket**: real wire carries 11 tokens (2|id|pos|level|name|-1|319|1|0|0|0) but schema had only 10 fields and the value 319 was landing on a byte Gender field. Inserted an Unknown2 int at index 6 so everything downstream (Gender/Race/Morph/ HeroLevel) shifts one slot right. - **RcPacket**: new class for `rc 1 14477871 11468 0`. Fields named Unknown1/Unknown2 until their semantics are RE'd. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3bdfcfb commit 8e581d8

6 files changed

Lines changed: 68 additions & 18 deletions

File tree

documentation/DocumentationTest.PacketsDocumentation.verified.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
- [npinfo](../src/NosCore.Packets/ServerPackets/Player/NpInfoPacket.cs) *InGame*
413413
- [p_sex](../src/NosCore.Packets/ServerPackets/Player/PSexPacket.cs) *InGame*
414414
- [rage](../src/NosCore.Packets/ServerPackets/Player/RagePacket.cs) *InGame*
415+
- [rc](../src/NosCore.Packets/ServerPackets/Player/RcPacket.cs) *InGame*
415416
- [rsfi](../src/NosCore.Packets/ServerPackets/Player/RsfiPacket.cs) *InGame*
416417
- [sc](../src/NosCore.Packets/ServerPackets/Player/ScPacket.cs) *InGame*
417418
- [scr](../src/NosCore.Packets/ServerPackets/Player/ScrPacket.cs) *InGame*

src/NosCore.Packets/Deserializer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ public IPacket Deserialize(string packetContent)
284284
case var prop when (prop.BaseType?.Equals(typeof(Enum)) ?? false) ||
285285
(Nullable.GetUnderlyingType(prop)?.IsEnum ?? false):
286286
return DeserializeEnum(item1, matches[currentIndex++]);
287+
case var prop when prop == typeof(Game18NArguments):
288+
// Game18NArguments is a custom non-generic IList<object>. The generic
289+
// list path can't figure out its element type (GetGenericArguments() is
290+
// empty) and can't assign List<object> to it, so sayi/msgi/msgi2 all
291+
// crashed with IndexOutOfRange. Consume the remaining tokens directly
292+
// into a fresh Game18NArguments; numeric tokens go in as long, the rest
293+
// as string.
294+
var g18n = new Game18NArguments(Math.Max(1, matches.Length - currentIndex));
295+
while (currentIndex < matches.Length)
296+
{
297+
var token = matches[currentIndex++];
298+
g18n.Add(long.TryParse(token, out var longValue) ? (object)longValue : token);
299+
}
300+
return g18n;
287301
case var prop when typeof(ICollection).IsAssignableFrom(prop):
288302
return DeserializeList(packetBasePropertyInfo.Item1.GetElementType() ?? packetBasePropertyInfo.Item1.GetGenericArguments()[0], packetBasePropertyInfo.Item2, matches, ref currentIndex, isMaxIndex);
289303
case var prop when prop == typeof(IPacket):

src/NosCore.Packets/NosCore.Packets.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<RepositoryUrl>https://github.com/NosCoreIO/NosCore.Packets.git</RepositoryUrl>
1313
<PackageIconUrl></PackageIconUrl>
1414
<PackageTags>nostale, noscore, chickenapi, nostale private server source, nostale emulator</PackageTags>
15-
<Version>17.5.0</Version>
15+
<Version>17.6.0</Version>
1616
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
1717
<Description>NosCore's Packets (Nostale packets) defined over classes</Description>
1818
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
// __ _ __ __ ___ __ ___ ___
1+
// __ _ __ __ ___ __ ___ ___
22
// | \| |/__\ /' _/ / _//__\| _ \ __|
33
// | | ' | \/ |`._`.| \_| \/ | v / _|
44
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
55
// -----------------------------------
66

77
using NosCore.Packets.Attributes;
88
using NosCore.Packets.Enumerations;
9-
using NosCore.Packets.ServerPackets.Inventory;
109
using NosCore.Shared.Enumerations;
1110

1211
namespace NosCore.Packets.ServerPackets.Chats
1312
{
13+
/// <summary>
14+
/// Chat bubble advertising a specific item/character, observed on the wire as:
15+
/// <c>sayitemt 1 &lt;visualId&gt; &lt;oratorSlot&gt; &lt;type&gt; &lt;messageKey&gt; &lt;visualName&gt; &lt;argument&gt; &lt;subPacket...&gt;</c>
16+
/// where <c>subPacket</c> is one of <c>IconInfo …</c>, <c>e_info …</c>,
17+
/// <c>slinfo …</c> or <c>pslinfo …</c>. Because only one sub-packet shape
18+
/// appears per wire line and the dispatch depends on the header token,
19+
/// the whole tail is captured as a single raw string — consumers that need
20+
/// structure should deserialise <see cref="SubPacketRaw"/> separately.
21+
/// </summary>
1422
[PacketHeader("sayitemt", Scope.InGame)]
1523
public class SayItemtPacket : PacketBase
1624
{
@@ -29,20 +37,13 @@ public class SayItemtPacket : PacketBase
2937
[PacketIndex(4)]
3038
public Game18NConstString Message { get; set; }
3139

32-
[PacketIndex(4)]
40+
[PacketIndex(5)]
3341
public string? VisualName { get; set; }
3442

35-
[PacketIndex(3, IsOptional = true)]
43+
[PacketIndex(6, IsOptional = true)]
3644
public string? Argument { get; set; } = "{%s}";
3745

38-
[PacketIndex(4, IsOptional = true, RemoveHash = true)]
39-
public IconInfoPacket? IconInfo { get; set; }
40-
41-
[PacketIndex(5, IsOptional = true, RemoveHash = true)]
42-
public EInfoPacket? EquipmentInfo { get; set; }
43-
44-
[PacketIndex(6, IsOptional = true, RemoveHash = true)]
45-
public SlInfoPacket? SlInfo { get; set; }
46-
46+
[PacketIndex(7, IsOptional = true)]
47+
public string? SubPacketRaw { get; set; }
4748
}
48-
}
49+
}

src/NosCore.Packets/ServerPackets/Groups/PinitSubPacket.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@ public class PinitSubPacket : PacketBase
3434
public int Unknown { get; set; } //TODO: Find what this is made for
3535

3636
[PacketIndex(6)]
37-
public GenderType Gender { get; set; }
37+
public int Unknown2 { get; set; } //TODO: Find what this is made for. Observed 319 on live wire, shifts Gender/Race/Morph/HeroLevel by one.
3838

3939
[PacketIndex(7)]
40-
public short Race { get; set; }
40+
public GenderType Gender { get; set; }
4141

4242
[PacketIndex(8)]
43-
public short Morph { get; set; }
43+
public short Race { get; set; }
4444

4545
[PacketIndex(9)]
46+
public short Morph { get; set; }
47+
48+
[PacketIndex(10)]
4649
public byte HeroLevel { get; set; }
4750
}
4851
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// __ _ __ __ ___ __ ___ ___
2+
// | \| |/__\ /' _/ / _//__\| _ \ __|
3+
// | | ' | \/ |`._`.| \_| \/ | v / _|
4+
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
5+
// -----------------------------------
6+
7+
using NosCore.Packets.Attributes;
8+
using NosCore.Packets.Enumerations;
9+
10+
namespace NosCore.Packets.ServerPackets.Player
11+
{
12+
/// <summary>
13+
/// Server-side compact packet observed as <c>rc 1 14477871 11468 0</c>.
14+
/// Fields are unnamed until the semantics are reverse-engineered.
15+
/// </summary>
16+
[PacketHeader("rc", Scope.InGame)]
17+
public class RcPacket : PacketBase
18+
{
19+
[PacketIndex(0)]
20+
public byte Type { get; set; }
21+
22+
[PacketIndex(1)]
23+
public long VisualId { get; set; }
24+
25+
[PacketIndex(2)]
26+
public int Unknown1 { get; set; }
27+
28+
[PacketIndex(3)]
29+
public int Unknown2 { get; set; }
30+
}
31+
}

0 commit comments

Comments
 (0)