using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; namespace AnotherReplayReader.ReplayFile { internal static class RA3Commands { private static readonly Dictionary> _commandParser; private static readonly Dictionary _commandNames; public static ImmutableArray UnknownCommands { get; } = new byte[] { 0x0F, // unk 0x5F, // unk 0x12, // unk 0x1B, // unk 0x48, // unk 0x52, // unk 0xFC, // unk 0xFD, // unk }.ToImmutableArray(); public static ImmutableArray AutoCommands { get; } = new byte[] { 0x01, // auto gen 0x21, // 3 seconds heartbeat 0x33, // uuid 0x34, // uuid 0x35, // player info 0x37, // indeterminate autogen 0x47, // 5th frame auto gen 0xF6, // auto, maybe from barrack deploy 0xF9, // auto, maybe unit from structures }.ToImmutableArray(); static RA3Commands() { Action FixedSizeParser(byte command, int size) { return (BinaryReader current) => { var lastByte = current.ReadBytes(size - 2).Last(); if (lastByte != 0xFF) { throw new InvalidDataException($"Failed to parse command {command:X}, last byte is {lastByte:X}"); } }; } Action VariableSizeParser(byte command, int offset) { return (BinaryReader current) => { var totalBytes = 2; totalBytes += current.ReadBytes(offset - 2).Length; for (var x = current.ReadByte(); x != 0xFF; x = current.ReadByte()) { totalBytes += 1; var size = ((x >> 4) + 1) * 4; totalBytes += current.ReadBytes(size).Length; } totalBytes += 1; var chk = totalBytes; }; }; var list = new List<(byte, Func>, int, string)> { (0x00, FixedSizeParser, 45, "展开建筑/建造碉堡(?)"), (0x03, FixedSizeParser, 17, "开始升级"), (0x04, FixedSizeParser, 17, "暂停/中止升级"), (0x05, FixedSizeParser, 20, "开始生产单位或纳米核心"), (0x06, FixedSizeParser, 20, "暂停/取消生产单位或纳米核心"), (0x07, FixedSizeParser, 17, "开始建造建筑"), (0x08, FixedSizeParser, 17, "暂停/取消建造建筑"), (0x09, FixedSizeParser, 35, "摆放建筑"), (0x0F, FixedSizeParser, 16, "(未知指令 0x0F)"), (0x14, FixedSizeParser, 16, "移动"), (0x15, FixedSizeParser, 16, "移动攻击(A)"), (0x16, FixedSizeParser, 16, "强制移动/碾压(G)"), (0x21, FixedSizeParser, 20, "[游戏每3秒自动产生的指令]"), (0x2C, FixedSizeParser, 29, "队形移动(左右键)"), (0x32, FixedSizeParser, 53, "释放技能或协议(多个目标,如侦察扫描协议)"), (0x34, FixedSizeParser, 45, "[游戏自动产生的UUID]"), (0x35, FixedSizeParser, 1049, "[玩家信息(?)]"), (0x36, FixedSizeParser, 16, "倒车移动(D)"), (0x5F, FixedSizeParser, 11, "(未知指令 0x5F)"), (0x0A, VariableSizeParser, 2, "出售建筑"), (0x0D, VariableSizeParser, 2, "右键攻击"), (0x0E, VariableSizeParser, 2, "强制攻击(Ctrl)"), (0x12, VariableSizeParser, 2, "(未知指令 0x12)"), (0x1A, VariableSizeParser, 2, "停止(S)"), (0x1B, VariableSizeParser, 2, "(未知指令 0x1B)"), (0x28, VariableSizeParser, 2, "开始维修建筑"), (0x29, VariableSizeParser, 2, "停止维修建筑"), (0x2A, VariableSizeParser, 2, "选择所有单位(Q)"), (0x2E, VariableSizeParser, 2, "切换警戒/侵略/固守/停火模式"), (0x2F, VariableSizeParser, 2, "路径点模式(Alt)(?)"), (0x37, VariableSizeParser, 2, "[游戏不定期自动产生的指令]"), (0x47, VariableSizeParser, 2, "[游戏在第五帧自动产生的指令]"), (0x48, VariableSizeParser, 2, "(未知指令 0x48)"), (0x4C, VariableSizeParser, 2, "删除信标(或 F9?)"), (0x4E, VariableSizeParser, 2, "选择协议"), (0x52, VariableSizeParser, 2, "(未知指令 0x52)"), (0xF5, VariableSizeParser, 5, "选择单位"), (0xF6, VariableSizeParser, 5, "[未知指令,貌似会在展开兵营核心时自动产生?]"), (0xF8, VariableSizeParser, 4, "鼠标左键单击/取消选择"), (0xF9, VariableSizeParser, 2, "[可能是步兵自行从进驻的建筑撤出]"), (0xFA, VariableSizeParser, 7, "创建编队"), (0xFB, VariableSizeParser, 2, "选择编队"), (0xFC, VariableSizeParser, 2, "(未知指令 0xFC)"), (0xFD, VariableSizeParser, 7, "(未知指令 0xFD)"), (0xFE, VariableSizeParser, 15, "释放技能或协议(无目标)"), (0xFF, VariableSizeParser, 34, "释放技能或协议(单个目标)"), }; var specialList = new List<(byte, Action, string)> { (0x01, ParseSpecialChunk0x01, "[游戏自动生成的指令]"), (0x02, ParseSetRallyPoint0x02, "设计集结点"), (0x0C, ParseUngarrison0x0C, "从进驻的建筑撤出(?)"), (0x10, ParseGarrison0x10, "进驻建筑"), (0x33, ParseUuid0x33, "[游戏自动生成的UUID指令]"), (0x4B, ParsePlaceBeacon0x4B, "信标") }; _commandParser = new Dictionary>(); _commandNames = new Dictionary(); foreach (var (id, maker, size, description) in list) { _commandParser.Add(id, maker(id, size)); _commandNames.Add(id, description); } foreach (var (id, parser, description) in specialList) { _commandParser.Add(id, parser); _commandNames.Add(id, description); } _commandNames.Add(0x4D, "在信标里输入文字"); } public static void ReadCommandData(this BinaryReader reader, byte commandId) { if (_commandParser.TryGetValue(commandId, out var parser)) { _commandParser[commandId](reader); } else { UnknownCommandParser(reader, commandId); } } public static bool IsUnknownCommand(byte commandId) { return !_commandNames.ContainsKey(commandId); } public static string GetCommandName(byte commandId) { return _commandNames.TryGetValue(commandId, out var storedName) ? storedName : $"(未知指令 0x{commandId:X2})"; } public static void UnknownCommandParser(BinaryReader current, byte commandId) { while (true) { var value = current.ReadByte(); if (value == 0xFF) { break; } } //return $"(未知指令 0x{commandID:2X})"; } private static void ParseSpecialChunk0x01(BinaryReader current) { var firstByte = current.ReadByte(); if (firstByte == 0xFF) { return; } var sixthByte = current.ReadBytes(5).Last(); if (sixthByte == 0xFF) { return; } var sixteenthByte = current.ReadBytes(10).Last(); var size = (int)(sixteenthByte + 1) * 4 + 14; var lastByte = current.ReadBytes(size).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } return; } private static void ParseSetRallyPoint0x02(BinaryReader current) { var size = (current.ReadBytes(23).Last() + 1) * 2 + 1; var lastByte = current.ReadBytes(size).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } } private static void ParseUngarrison0x0C(BinaryReader current) { current.ReadByte(); var size = (current.ReadByte() + 1) * 4 + 1; var lastByte = current.ReadBytes(size).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } } private static void ParseGarrison0x10(BinaryReader current) { var type = current.ReadByte(); var size = -1; if (type == 0x14) { size = 9; } else if (type == 0x04) { size = 10; } var lastByte = current.ReadBytes(size).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } } private static void ParseUuid0x33(BinaryReader current) { current.ReadByte(); var firstStringLength = (int)current.ReadByte(); current.ReadBytes(firstStringLength + 1); var secondStringLength = current.ReadByte() * 2; var lastByte = current.ReadBytes(secondStringLength + 6).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } } private static void ParsePlaceBeacon0x4B(BinaryReader current) { var type = current.ReadByte(); var size = -1; if (type == 0x04) { size = 5; } else if (type == 0x07) { size = 13; } var lastByte = current.ReadBytes(size).Last(); if (lastByte != 0xFF) { throw new InvalidDataException(); } } } }