222 lines
8.6 KiB
C#
222 lines
8.6 KiB
C#
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}");
|
||
}
|
||
}
|
||
}
|