Files
AnotherReplayReader/ReplayFile/RA3Commands.cs
2026-06-21 14:42:04 +02:00

222 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Text;
namespace AnotherReplayReader.ReplayFile
{
internal static class RA3Commands
{
private interface IArgumentReader
{
object Read(BinaryReader reader, int count);
}
private sealed class ArgumentReader<T> : IArgumentReader
{
private readonly Func<BinaryReader, T> _reader;
public ArgumentReader(Func<BinaryReader, T> reader) => _reader = reader;
public object Read(BinaryReader reader, int count)
{
if (count == 1)
{
return _reader(reader)!;
}
var result = new T[count];
for (int i = 0; i < count; i++)
{
result[i] = _reader(reader);
}
return result;
}
}
private static readonly Dictionary<CommandArgumentType, IArgumentReader> _readers = new()
{
[CommandArgumentType.Int32] = new ArgumentReader<int>(r => r.ReadInt32()),
[CommandArgumentType.Float32] = new ArgumentReader<float>(r => r.ReadSingle()),
[CommandArgumentType.Bool] = new ArgumentReader<bool>(r => r.ReadByte() != 0),
[CommandArgumentType.UInt16] = new ArgumentReader<ushort>(r => r.ReadUInt16()),
[CommandArgumentType.UInt32] = new ArgumentReader<uint>(r => r.ReadUInt32()),
[CommandArgumentType.UInt32_2] = new ArgumentReader<uint>(r => r.ReadUInt32()),
[CommandArgumentType.ObjectId] = new ArgumentReader<uint>(r => r.ReadUInt32()),
[CommandArgumentType.ObjectId_2] = new ArgumentReader<uint>(r => r.ReadUInt32()),
[CommandArgumentType.Vector3] = new ArgumentReader<Vector3>(ReadVector3),
[CommandArgumentType.AssetId] = new ArgumentReader<AssetId>(ReadAssetId),
[CommandArgumentType.AsciiString] = new ArgumentReader<string>(r => ReadString(r, CommandArgumentType.AsciiString)),
[CommandArgumentType.UnicodeString] = new ArgumentReader<string>(r => ReadString(r, CommandArgumentType.UnicodeString)),
};
private static readonly Dictionary<int, string> _commandNames;
public static ImmutableArray<int> UnknownCommands { get; } = new[]
{
0x1FD, // unk
0x25F, // unk
}.ToImmutableArray();
public static ImmutableArray<int> AutoCommands { get; } = new[]
{
0x1,
0x221, // 3 seconds heartbeat
0x233, // uuid
0x234, // uuid
0x235, // player info
0x237, // indeterminate autogen
0x247, // 5th frame auto gen
0x252, // another player quits
}.ToImmutableArray();
static RA3Commands()
{
_commandNames = new()
{
[0x1] = "[游戏结束]", // 1
[0x1F5] = "选择单位", // 501
[0x1F6] = "选择相同单位W", // 502
[0x1F8] = "取消选择", // 504
[0x1F9] = "从选择中移除单位", // 505
[0x1FA] = "创建编队", // 506
[0x1FB] = "选择编队", // 507
[0x1FC] = "将编队加入选择", // 508
[0x1FD] = "(未知指令 0x1FD", // 509
[0x1FE] = "释放特殊能力(无目标)", // 510
[0x1FF] = "释放特殊能力(指定位置)", // 511
[0x200] = "释放特殊能力(指定位置和角度)", // 512
[0x201] = "释放特殊能力(指定目标)", // 513
[0x202] = "设置集结点", // 514
[0x203] = "开始升级", // 515
[0x204] = "暂停/中止升级", // 516
[0x205] = "开始出兵", // 517
[0x206] = "暂停/取消出兵", // 518
[0x207] = "开始建造", // 519
[0x208] = "暂停/取消建造", // 520
[0x209] = "摆放建筑", // 521
[0x20A] = "出售建筑", // 522
// 523
[0x20C] = "从进驻的建筑撤出(?)", // 524
[0x20D] = "集火攻击", // 525
[0x20E] = "强制攻击单位Ctrl", // 526
[0x20F] = "强制攻击地板Ctrl", // 527
[0x210] = "进驻建筑", // 528
// 529
[0x212] = "命令矿车交矿", // 530
// 531
[0x214] = "移动", // 532
[0x215] = "行进攻击A", // 533
[0x216] = "强制移动/碾压G", // 534
// 535
// 536
// 537
[0x21A] = "停止S", // 538
[0x21B] = "散开X", // 539
// 0x21E 542 有可能是 AI 信标相关的?
[0x221] = "[游戏每3秒自动产生的检测不同步指令]", // 545
[0x228] = "开始维修建筑", // 552
[0x229] = "停止维修建筑", // 553
[0x22A] = "选择所有单位Q", // 554
// 555
[0x22C] = "队形操作(左右键)", // 556
// 557
[0x22E] = "切换姿态(警戒/侵略/固守/停火模式)", // 558
[0x22F] = "路径点模式/计划模式Alt", // 559
// 560
// 561
[0x232] = "释放特殊能力(一个或多个目标)", // 562
[0x233] = "[游戏自动生成的UUID指令]", // 563
[0x234] = "[游戏自动产生的UUID]", // 564
[0x235] = "[玩家信息(?)]", // 565
[0x236] = "倒车D", // 566
[0x237] = "[游戏不定期自动产生的指令]", // 567
[0x247] = "[游戏在第五帧自动产生的指令]", // 583
[0x248] = "让矿车去采矿", // 584
[0x24B] = "信标", // 587
[0x24C] = "删除信标", // 588
[0x24D] = "在信标里输入文字", // 589
[0x24E] = "选择协议", // 590
[0x252] = "[其他玩家主动退出游戏]", // 594
[0x25F] = "(未知指令 0x25F", // 607
};
}
public static List<CommandArgumentEntry> ReadCommandData(this BinaryReader reader)
{
var list = new List<CommandArgumentEntry>();
while (true)
{
byte head = reader.ReadByte();
if (head == 0xFF)
break;
int count = (head >> 4) + 1;
var type = (CommandArgumentType)(head & 0xF);
var value = _readers[type].Read(reader, count);
list.Add(new CommandArgumentEntry(type, value, count));
}
return list;
}
public static bool IsUnknownCommand(int commandId)
{
return !_commandNames.ContainsKey(commandId);
}
public static string GetCommandName(int commandId)
{
return _commandNames.TryGetValue(commandId, out var storedName)
? storedName
: $"(未知指令 0x{commandId:X2}";
}
private static AssetId ReadAssetId(BinaryReader reader)
{
var version = reader.ReadByte();
var typeId = reader.ReadUInt32();
var instanceId = reader.ReadUInt32();
return new(TypeId: typeId, InstanceId: instanceId);
}
private static Vector3 ReadVector3(BinaryReader reader)
{
var x = reader.ReadSingle();
var y = reader.ReadSingle();
var z = reader.ReadSingle();
return new(X: x, Y: y, Z: z);
}
private static string ReadString(BinaryReader current, CommandArgumentType type)
{
var length = (int)current.ReadByte();
if (length == 0xFF)
{
length = current.ReadInt32();
}
// read byte string or wchar_t string based on T type
switch (type)
{
case CommandArgumentType.AsciiString:
return Encoding.UTF8.GetString(current.ReadBytes(length));
case CommandArgumentType.UnicodeString:
return Encoding.Unicode.GetString(current.ReadBytes(length * 2));
}
throw new InvalidDataException($"Invalid string type {type}");
}
}
}