215 lines
10 KiB
C#
215 lines
10 KiB
C#
using Ananta.SDK.Serialization;
|
||
|
||
namespace Ananta.Server.Protocol.Client4229938;
|
||
|
||
/// <summary>One parsed StoryClientCommand from the C2S story channel.</summary>
|
||
internal sealed record StoryClientCommand4229938(
|
||
byte TypeMark,
|
||
uint Nid,
|
||
string Name,
|
||
long RpcId,
|
||
bool HasRpcId,
|
||
string? PayloadKind,
|
||
ulong? PayloadVehicleId,
|
||
List<byte>? PayloadSeatIndices,
|
||
bool? PayloadBool,
|
||
long? PayloadInt,
|
||
ulong? PayloadULong,
|
||
// ★ S002 里希盘查(2026-10-01 从捐赠端 DRMK-PS_09_7k-v0.5-2 移植):
|
||
// Police = 解出来的 S002 警用 payload;PayloadString = StringPayload(mark 42)。
|
||
// ⛑ 42 原来在本文件里**没有**,GATE Lua 回执曾经只能用 44(ULongPayload)。
|
||
PoliceStoryPayload4229938? Police = null,
|
||
string? PayloadString = null);
|
||
|
||
/// <summary>
|
||
/// S002 警用 payload(捐赠端同名 record 照搬)。
|
||
/// ⛑ Tag 是 **payload 的 mark**,不是 InteractionId:
|
||
/// 19=EnterExamRequest / 20=EscortFromVehicleRequest / 21=EscortRequest /
|
||
/// 18,22=带车 / 23=只带车 / 24=一次盘查选项(InteractionId + SectionId)。
|
||
/// </summary>
|
||
internal sealed record PoliceStoryPayload4229938(byte Tag, ulong AgentId = 0, bool StartInteract = false,
|
||
uint InteractionId = 0, uint SectionId = 0, bool IsStandUp = false,
|
||
ulong VehicleId = 0, int SeatIndex = -1, int[]? Fines = null);
|
||
|
||
/// <summary>
|
||
/// Parser for the 4229938 story channel (SyncStoryCoreClientInfo, 67130745):
|
||
/// an inline List7Bit of complex StoryClientCommand entries.
|
||
/// Command marks: ConfirmRpc 2, Message 4, SetObservable-shaped 5, reliable RpcId-only 3/6.
|
||
/// Payload marks: Bool 5, Byte 6, Int 12, S011EnterVehicleRequest 35 (NOT 36 — V2's value
|
||
/// is from another build; proven live 2026-09-12 on the sibling 08.05 build: hex
|
||
/// ...-23-<u64>-FF-<count>-<seats>), S011VehicleAndSeat 37, ULong 44. Shapes verified
|
||
/// against dump.cs (StoryClientCommand base, payload V fields,
|
||
/// S011EnterVehicleRequestPayload{VehicleId, SeatIndices}, S011VehicleAndSeatPayload).
|
||
/// An unknown command or payload aborts that message's parse; the prefix is still returned.
|
||
/// </summary>
|
||
internal static class StoryVehicleCodec4229938
|
||
{
|
||
internal static IReadOnlyList<StoryClientCommand4229938> Parse(ReadOnlySpan<byte> body, out bool fullyConsumed)
|
||
{
|
||
var commands = new List<StoryClientCommand4229938>();
|
||
var reader = new UxReader(body.ToArray());
|
||
fullyConsumed = false;
|
||
|
||
try
|
||
{
|
||
// Nullable list framing: 0x00 = null, 0xFF + Int7 count + items.
|
||
if (reader.U8() == 0x00)
|
||
{
|
||
fullyConsumed = true;
|
||
return commands;
|
||
}
|
||
|
||
int count = reader.Int7();
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
byte mark = reader.U8();
|
||
if (mark == 0x00)
|
||
continue;
|
||
|
||
switch (mark)
|
||
{
|
||
case 2: // ConfirmRpcCommand { ConfirmRpcId i64, RpcId i64, HasRpcId bool }
|
||
{
|
||
long confirmRpcId = reader.I64();
|
||
(long rpcId, bool hasRpcId) = ParseCommandBase(reader);
|
||
commands.Add(new StoryClientCommand4229938(2, 0, "ConfirmRpc", rpcId, hasRpcId, "Confirm", (ulong)confirmRpcId, null, null, null, null));
|
||
break;
|
||
}
|
||
|
||
case 4: // MessageCommand { Nid u32, Name string, Value StoryNetPayload, RpcId, HasRpcId }
|
||
{
|
||
uint nid = reader.U32();
|
||
string name = reader.UxStringNullable() ?? string.Empty;
|
||
var payload = ParsePayload(reader);
|
||
(long rpcId, bool hasRpcId) = ParseCommandBase(reader);
|
||
commands.Add(new StoryClientCommand4229938(4, nid, name, rpcId, hasRpcId,
|
||
payload.Kind, payload.VehicleId, payload.SeatIndices, payload.Bool, payload.Int, payload.ULong,
|
||
payload.Police, payload.Text));
|
||
break;
|
||
}
|
||
|
||
case 5: // { Nid u32, Name string, Value StoryNetPayload, RpcId, HasRpcId }
|
||
{
|
||
uint nid = reader.U32();
|
||
string name = reader.UxStringNullable() ?? string.Empty;
|
||
var payload = ParsePayload(reader);
|
||
(long rpcId, bool hasRpcId) = ParseCommandBase(reader);
|
||
commands.Add(new StoryClientCommand4229938(5, nid, name, rpcId, hasRpcId,
|
||
payload.Kind, payload.VehicleId, payload.SeatIndices, payload.Bool, payload.Int, payload.ULong,
|
||
payload.Police, payload.Text));
|
||
break;
|
||
}
|
||
|
||
case 3:
|
||
case 6: // { RpcId i64, HasRpcId bool }
|
||
{
|
||
(long rpcId, bool hasRpcId) = ParseCommandBase(reader);
|
||
commands.Add(new StoryClientCommand4229938(mark, 0, mark == 3 ? "Rpc3" : "Rpc6", rpcId, hasRpcId, null, null, null, null, null, null));
|
||
break;
|
||
}
|
||
|
||
default:
|
||
return commands;
|
||
}
|
||
}
|
||
|
||
fullyConsumed = true;
|
||
return commands;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return commands;
|
||
}
|
||
}
|
||
|
||
private static (long RpcId, bool HasRpcId) ParseCommandBase(UxReader reader)
|
||
=> (reader.I64(), reader.Bool());
|
||
|
||
private static (string Kind, ulong? VehicleId, List<byte>? SeatIndices, bool? Bool, long? Int, ulong? ULong,
|
||
PoliceStoryPayload4229938? Police, string? Text) ParsePayload(UxReader reader)
|
||
{
|
||
byte mark = reader.U8();
|
||
switch (mark)
|
||
{
|
||
case 5: // BoolPayload { V bool }
|
||
return ("Bool", null, null, reader.Bool(), null, null, null, null);
|
||
case 6: // BytePayload { V byte } - S011 boarding phases arrive in this shape
|
||
return ("Byte", null, null, null, reader.U8(), null, null, null);
|
||
case 12: // IntPayload { V i32 }
|
||
return ("Int", null, null, null, reader.I32(), null, null, null);
|
||
case 44: // ULongPayload { V u64 }
|
||
return ("ULong", null, null, null, null, reader.U64(), null, null);
|
||
// ---- S002 里希盘查(移植自捐赠端)----
|
||
case 42: // StringPayload { V string }
|
||
return ("String", null, null, null, null, null, null, reader.UxStringNullable());
|
||
case 43: // UIntPayload { V u32 }
|
||
return ("UInt", null, null, null, reader.U32(), null, null, null);
|
||
case 19: // S002EnterExamRequestPayload { AgentId u64, StartInteract bool }
|
||
return ("S002", null, null, null, null, null,
|
||
new PoliceStoryPayload4229938(mark, AgentId: reader.U64(), StartInteract: reader.Bool()), null);
|
||
case 20: // S002EscortFromVehicleRequestPayload { AgentId u64 }
|
||
case 21: // S002EscortRequestPayload { AgentId u64 }
|
||
return ("S002", null, null, null, null, null,
|
||
new PoliceStoryPayload4229938(mark, AgentId: reader.U64()), null);
|
||
case 18:
|
||
case 22: // { AgentId u64, VehicleId u64 }
|
||
return ("S002", null, null, null, null, null,
|
||
new PoliceStoryPayload4229938(mark, AgentId: reader.U64(), VehicleId: reader.U64()), null);
|
||
case 23: // { VehicleId u64 }
|
||
return ("S002", null, null, null, null, null,
|
||
new PoliceStoryPayload4229938(mark, VehicleId: reader.U64()), null);
|
||
case 24: // S002ExamInteractPayload { InteractionId u32, SectionId u32, IsStandUp bool, params }
|
||
{
|
||
uint interaction = reader.U32(), section = reader.U32();
|
||
bool stand = reader.Bool();
|
||
byte param = reader.U8();
|
||
ulong vehicle = 0; int seat = -1; int[]? fines = null;
|
||
if (param == 2) { vehicle = reader.U64(); seat = reader.I32(); }
|
||
else if (param == 3 && reader.U8() != 0)
|
||
{
|
||
int count = reader.Int7();
|
||
if (count < 0 || count > 64) throw new InvalidDataException("invalid fine count");
|
||
fines = new int[count];
|
||
for (int i = 0; i < count; i++) fines[i] = reader.I32();
|
||
}
|
||
else if (param != 0 && param != 3) throw new InvalidDataException("unknown police parameters");
|
||
return ("S002", null, null, null, null, null,
|
||
new PoliceStoryPayload4229938(mark, InteractionId: interaction, SectionId: section,
|
||
IsStandUp: stand, VehicleId: vehicle, SeatIndex: seat, Fines: fines), null);
|
||
}
|
||
case 35: // S011EnterVehicleRequestPayload { VehicleId u64, SeatIndices byte[]? }
|
||
{
|
||
ulong vehicleId = reader.U64();
|
||
List<byte>? seats = null;
|
||
if (reader.U8() != 0x00)
|
||
{
|
||
int n = reader.Int7();
|
||
seats = new List<byte>(n);
|
||
for (int i = 0; i < n; i++) seats.Add(reader.U8());
|
||
}
|
||
return ("S011EnterVehicleRequest", vehicleId, seats, null, null, null, null, null);
|
||
}
|
||
case 36: // same shape under V2's build; kept for tolerance
|
||
{
|
||
ulong vehicleId = reader.U64();
|
||
List<byte>? seats = null;
|
||
if (reader.U8() != 0x00)
|
||
{
|
||
int n = reader.Int7();
|
||
seats = new List<byte>(n);
|
||
for (int i = 0; i < n; i++) seats.Add(reader.U8());
|
||
}
|
||
return ("S011EnterVehicleRequest", vehicleId, seats, null, null, null, null, null);
|
||
}
|
||
case 37: // S011VehicleAndSeatPayload { VehicleId u64, SeatIndex byte }
|
||
{
|
||
ulong vehicleId = reader.U64();
|
||
byte seat = reader.U8();
|
||
return ("S011VehicleAndSeat", vehicleId, [seat], null, null, null, null, null);
|
||
}
|
||
default:
|
||
throw new InvalidDataException($"unknown StoryNetPayload type mark {mark}");
|
||
}
|
||
}
|
||
}
|