This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/Ananta.Server/Ananta.Core/Protocol/Client4229938/StoryVehicleCodec4229938.cs
T

214 lines
10 KiB
C#
Raw Normal View History

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}");
}
}
}