Guest User

P1632 protocol analyzer

a guest
Jul 1st, 2020
32
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7.  
  8. namespace alarmpakanalyzer {
  9.   class Program {
  10.     private static bool skipDupes = true;
  11.     private static bool aggregatedDedupe = false;
  12.     private static string filter = null; // "filename";
  13.  
  14.     private static List<PacketReaderSpec> packetReaderSpecs;
  15.  
  16.     static void Main(string[] args) {
  17.       Console.WindowWidth = 280;
  18.       InitPacketReaders();
  19.       List<String> files = new List<String>();
  20.       files.AddRange(Directory.GetFiles(@"E:\myfolder", "*.csv"));
  21.       if (filter != null)
  22.         files = files.Where(f => f.ToLower().Contains(filter.ToLower())).ToList();
  23.       var packetsPerFile = new Dictionary<string, Packet[]>();
  24.       foreach (string file in files) {
  25.         packetsPerFile.Add(file, AnalyzeLog(File.ReadAllLines(file)).ToArray());
  26.         if (aggregatedDedupe)
  27.           MarkDuplicates(packetsPerFile.SelectMany(kv => kv.Value)).Count();
  28.         else
  29.           packetsPerFile.SelectMany(kv => MarkDuplicates(kv.Value)).Count();
  30.       }
  31.       ConsoleColor[] colors = Enum.GetValues(typeof(ConsoleColor)).Cast<ConsoleColor>().Where(x => x != ConsoleColor.Black).ToArray();
  32.       foreach (KeyValuePair<string, Packet[]> pakFile in packetsPerFile) {
  33.         Console.ForegroundColor = ConsoleColor.White;
  34.         Console.BackgroundColor = ConsoleColor.DarkRed;
  35.         Console.WriteLine($"File: {pakFile.Key}".PadRight(Console.WindowWidth-1));
  36.         Console.BackgroundColor = ConsoleColor.Black;
  37.        
  38.         foreach (var pak in pakFile.Value) {
  39.           if (pak.IsDuplicate && skipDupes)
  40.             continue;
  41.           if (pak.Data.Length >= 2) {
  42.             ConsoleColor c = ConsoleColor.Yellow;
  43.             switch (pak.Data[0]) {
  44.               case 0x31: c = ConsoleColor.White; break;
  45.               case 0x21: c = ConsoleColor.Red; break;
  46.               case 0x22: c = ConsoleColor.Green; break;
  47.             }
  48.             if (pak.Port == "KBD") c += (ConsoleColor.DarkRed - ConsoleColor.Red);
  49.             //Console.ForegroundColor = colors[((byte)pak.Data[1]) % colors.Length];
  50.             Console.ForegroundColor = c;
  51.           }
  52.           Console.WriteLine(pak);
  53.         }
  54.       }
  55.       // Print binary paks
  56.       Console.ReadLine();
  57.       Console.WriteLine("==========================================");
  58.       Console.ForegroundColor = ConsoleColor.White;
  59.       foreach (var kv in packetsPerFile.Where(pair => pair.Key.Contains("confModeEnd2"))) {
  60.         Console.WriteLine(kv.Key);
  61.         foreach (var pak in kv.Value) {
  62.           Console.WriteLine((int)(pak.StartTime*1000) + $" ${pak.Port} | {pak.Parser?.Name} -> " + pak.Data.Select(b => $"0x{b:X2}").Aggregate((a,b) => a + ", " + b));
  63.         }
  64.       }
  65.       Console.ReadLine();
  66.     }
  67.  
  68.     [Flags]
  69.     public enum TxtMode {
  70.       WTF = 0,            // 0b00000000,
  71.       ShowText = 0x01,    // 0b00000001,
  72.       FuncMenuKbd = 0x80, // 0b10000000,
  73.       Admin =  0x40,      // 0b01000000,
  74.     }
  75.  
  76.     [Flags]
  77.     public enum TxtIndex {
  78.       // Upper octlet
  79.       Line = 0x20, // 0b0100000
  80.       Line2 = Line | 0x40, // 0b1100000
  81.  
  82.       // Lower octlet
  83.       Arming = 0x02,
  84.       Fault = 0x03,
  85.     }
  86.  
  87.     [Flags]
  88.     public enum Led {
  89.       None = 0x00,
  90.       Green = 0x02,   // 0b00000010
  91.       GreenB = 0x04,  // 0b00000100
  92.       Trouble = 0x08, // 0b00001000
  93.       Red = 0x80,     // 0b10000000
  94.       RedB = 0x81,    // 0b10000001
  95.       Beeper = 0x40,  // 0b01000000
  96.       // Panic = 0b11000101  = GreenB | RedB | Beeper
  97.     }
  98.  
  99.     public enum KbdButton {
  100.       OnOff = 0x80,
  101.       Reset = 0x81,
  102.       Interior = 0x82,
  103.       Func = 0x88,
  104.       Bypass = 0x84,
  105.       No = 0x91,
  106.     }
  107.  
  108.     private static void InitPacketReaders() {
  109.       packetReaderSpecs = new List<PacketReaderSpec>();
  110.       // txt: 31 3B 00 00 01 20 00 00 00 00 20 5A 4F 4E 45 20 46 41 55 4C 54 45 44 20 20 20 6E   | 1;?????????ZONE?FAULTED???n
  111.       // txt: 31 3B 00 00 01 60 00 00 00 00 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 CD   | 1;???`????????????????????I
  112.       // wtf: 31 3B 00 04 00 00 00 00 00 00 09 06 00 03 00 00 00 00 00 00 00 00 00 00 00 00 7F   | 1;?????????????????????????
  113.       packetReaderSpecs.Add(
  114.         new PacketReaderSpec() {
  115.           PacketID = 0x313B,
  116.           Name = "P:ExtStatus",
  117.           Len = 27,
  118.           ParseFn = p => {
  119.             p.UnkConst(0x00);
  120.             byte wtf = p.ExpectByte("statusUpdate", Pair.of(0x00, ""), Pair.of(0x04, "yes")); // sometime 0x04 when invChecksum/format used, otherwise 0x00
  121.             if (wtf == 0x04) { // Maybe some old/different device protocol?
  122.               p.Zeros(6);
  123.               p.ExpectUshort("txtTable", Pair.of(0x1706, "SystemReady"), Pair.of(0x1F1D, "ExitInXsecs"), Pair.of(0x1E1D, "Xsecs"), Pair.of(0x0906, "Fault"), Pair.of(0x0504, "ArmZnFlt"), Pair.of(0x7902, "Zone2"), Pair.of(0x7006, "Zone3"), Pair.of(0xFC12, "Zone11"), Pair.of(0x2506, "BypassEnabled"));
  124.               p.Print("countdown", 1, d => d == 0 ? "" : $"{d}s");
  125.               p.UnkConsts(0x03);
  126.               p.ExpectByte("alarmState", Pair.of(0x00, ""), Pair.of(0x20, "Arming"), Pair.of(0x40, "Tripped"));
  127.               p.Zeros(2);
  128.               p.ExpectByte("alarm", Pair.of(0x00, ""), Pair.of(0x10, "armed"));
  129.               p.Zeros(8);
  130.               p.InvChecksum(0x02);
  131.             } else {
  132.               //  31 3B 00 04 00 00 00 00 00 00 17 06 00 03 00 00 00 00 00 00 00 00 00 00 00 00 71
  133.               TxtMode txtMode = p.Enum<TxtMode>("mode");
  134.               TxtIndex txtIndex = p.Enum<TxtIndex>("idx");
  135.               p.UnkConsts(0x00, 0x00);
  136.               p.Enum<Led>("led");
  137.               p.ExpectByte("alarm", Pair.of(0x00, ""), Pair.of(0x80, "armed"), Pair.of(0x48, "trouble"));
  138.               if ((txtMode & TxtMode.ShowText) > 0)
  139.                 p.String("txt", 16);
  140.               else {
  141.                 p.Skip(16);
  142.               }
  143.               p.Checksum();
  144.             }
  145.             return p.Build();
  146.           }
  147.         });
  148.       // txt: 31 31 00 00 01 23 00 00 01 00 46 41 55 4C 54 20 23 | 11???#????FAULT?#              |
  149.       // txt: 31 31 00 00 01 63 00 00 01 00 20 20 20 20 20 20 87 | 11 ??? c ???????????              |
  150.       // wtf: 31 31 00 00 00 E0 00 00 00 00 00 00 00 00 00 00 BD | 11 ??? à ??????????½              |
  151.       packetReaderSpecs.Add(
  152.         new PacketReaderSpec() {
  153.           PacketID = 0x3131,
  154.           Name = "P:Status",
  155.           Len = 17,
  156.           ParseFn = p => {
  157.             p.ExpectByte("error", Pair.of(0x00, ""), Pair.of(0x03, "ZONE"), Pair.of(0x20, "ALARM_TRIPPED"), Pair.of(0x0C, "ArmZnFlt"));
  158.             p.Print("zone", 1, d => d == 0 ? "" : $"Zone{d}"); // contains zone ID
  159.             TxtMode txtMode = p.Enum<TxtMode>("mode");
  160.             if ((txtMode & TxtMode.ShowText) != 0) {
  161.               TxtIndex txtIndex = p.Enum<TxtIndex>("idx");
  162.               p.Enum<Led>("led");
  163.               p.ExpectByte("alarm", Pair.of(0x00, ""), Pair.of(0x80, "armed"));
  164.               p.ExpectByte("state", Pair.of(0x01, "normal"), Pair.of(0x11, "bypassed"), Pair.of(0x31, "interior"));
  165.               p.UnkConst(0x00);
  166.               p.String("txt", 6);
  167.               p.Checksum();
  168.             } else {
  169.               p.UnkConst(0xE0);
  170.               p.UnkConst(0x00);
  171.               p.UnkConst(0x00);
  172.               p.UnkConst(0x00);
  173.               p.UnkConst(0x00);
  174.               p.UnkConst(0x00);
  175.               p.ExpectByte("wtfAlarm", Pair.of(0x00, ""), Pair.of(0x10, "armed"));
  176.               p.Zeros(4);
  177.               p.InvChecksum(0x00);
  178.             }
  179.             return p.Build();
  180.           }
  181.         });
  182.  
  183.       // ----------------      
  184.       // KDB <<>> PNL communication
  185.       for (int i = 1; i < 5; i++) {
  186.         ushort kbdId = (ushort)((0x20 + i) << 8); // 21 first, 22 = 2nd, ..
  187.  
  188.         // 22 4D 00 00 01 01 40 00 80 80 00 00 B1
  189.         packetReaderSpecs.Add(new PacketReaderSpec() {
  190.           PacketID = (ushort)(kbdId | 0x4D),
  191.           Name = $"P->K{i} Ping",
  192.           Len = 13,
  193.           ParseFn = p => {
  194.             p.UnkConsts(0x00, 0x00);
  195.             p.ExpectByte("bus", Pair.of(0x01, "idle"), Pair.of(0xC1, "adminFuncMode"), Pair.of(0x81, "userFuncMode"));
  196.             p.ExpectByte("error", Pair.of(0x01, ""), Pair.of(0x31, "wrongCode"), Pair.of(0x61, "armZnFlt"));
  197.             p.UnkConsts(0x40, 0x00, 0x80, 0x80, 0x00, 0x00);
  198.             p.Checksum();
  199.             return p.Build();
  200.           }
  201.         });
  202.         packetReaderSpecs.Add(new PacketReaderSpec() {
  203.           PacketID = (ushort)(kbdId | 0x44),
  204.           Name = $"K{i}->P Pong",
  205.           Len = 4,
  206.           ParseFn = p => {
  207.             p.ExpectByte("state", Pair.of(0x01, "normal"), Pair.of(0x20, "outOfSystem"), Pair.of(0x21, "wrongCodeLockedOut"));
  208.             p.Checksum();
  209.             return p.Build();
  210.           }
  211.         });
  212.  
  213.         // s=2501ms t=8ms   KBD   1x l= 5 |                 |  21 25 21 81 E8
  214.         // s=4700ms t=16ms  KBD   2x l= 9 |                 |  21 29 01 x x x x key chk
  215.         // s = 4488ms t = 21ms KBD 1x l = 11 |              |  21 2B 01 x x x x x x key chk | !+........ó |
  216.         for (int l = 0; l <= 6; l++) {
  217.           int len = l;
  218.           packetReaderSpecs.Add(new PacketReaderSpec() {
  219.             PacketID = (ushort)(kbdId | (0x25 + l)), // essentially "code0"; sent as a response to Ping instead of Pong
  220.             Name = $"K{i}->P Key&Code{len}",
  221.             Len = 5+len,
  222.             ParseFn = p => {
  223.               p.ExpectByte("add", Pair.of(0x01, ""), Pair.of(0x21, "RESET"));
  224.               if (len > 0) {
  225.                 p.Print($"code{len}", len, data => $"{data % 10}"); // 0x0A = 0
  226.               }
  227.               p.Enum<KbdButton>("key");
  228.               p.Checksum();
  229.               return p.Build();
  230.             }
  231.           });
  232.         }
  233.         // s=4554ms t=8ms   PNL   1x l= 5 |               |  21 C5 00 00 E6   | !Å..æ  
  234.         packetReaderSpecs.Add(new PacketReaderSpec() {
  235.           PacketID = (ushort)(kbdId | 0xC5),
  236.           Name = $"P->K{i} CmdRecv",
  237.           Len = 5,
  238.           ParseFn = p => {
  239.             p.UnkConsts(0x00, 0x00);
  240.             p.Checksum();
  241.             return p.Build();
  242.           }
  243.         });
  244.  
  245.         // s=7577ms t=8ms   PNL   3x l= 5 |                 |  21 85 00 00 A6    
  246.         packetReaderSpecs.Add(new PacketReaderSpec() {
  247.           PacketID = (ushort)(kbdId | 0x85),
  248.           Name = $"P->K{i} Configure",
  249.           Len = 5,
  250.           ParseFn = p => {
  251.             p.UnkConsts(0x00, 0x00);
  252.             p.Checksum();
  253.             return p.Build();
  254.           }
  255.         });
  256.       }
  257.     }
  258.  
  259.     private class Packet {
  260.       public string Port { get; set; }
  261.       public double StartTime { get; set; }
  262.       public double EndTime { get; set; }
  263.       public byte[] Data { get; set; }
  264.       public bool IsDuplicate { get; set; }
  265.       public int Quantity { get; set; }
  266.  
  267.       public string HexString { get { return Data.Select(d => d.ToString("X2")).Aggregate("", (a, b) => a + " " + b); } }
  268.       public string AsciiString { get { return Data.Select(d => (char)d).Select(c => Char.IsWhiteSpace(c) || Char.IsControl(c) ? '.' : c).Aggregate("", (a, b) => a + b); } }
  269.       public PacketReaderSpec Parser { get { return FindPacketReaderSpec(this); } }
  270.       public string ElapsedTimeStr { get { return $"{(int)((EndTime - StartTime) * 1000)}ms"; } }
  271.  
  272.       public override string ToString() {
  273.         string prefix = $"s={(int)(StartTime * 1000),-4}ms {"t="+ElapsedTimeStr,-7} {Port} {Quantity,3}x l={Data.Length,2}";
  274.         return $"{prefix} | {Parser?.Name,-15} | { HexString,-81} | {AsciiString,-30} | {Parser?.Parse(this) ?? " NEW PACKET"}";
  275.       }
  276.     }
  277.  
  278.     static IEnumerable<Packet> MarkDuplicates(IEnumerable<Packet> packets) {
  279.       var hexStrs = new Dictionary<string, int>();
  280.       List<Packet> paks = new List<Packet>();
  281.  
  282.       // Mark duplicates
  283.       foreach (var pak in packets) {
  284.         string signature = pak.Port + "=" + pak.HexString;
  285.         if (hexStrs.ContainsKey(signature)) {
  286.           hexStrs[signature]++;
  287.           pak.IsDuplicate = true;
  288.         } else {
  289.           hexStrs.Add(signature, 1);
  290.         }
  291.         paks.Add(pak);
  292.       }
  293.      
  294.       // Set quantity
  295.       foreach (var pak in paks) {
  296.         string signature = pak.Port + "=" + pak.HexString;
  297.         pak.Quantity = hexStrs[signature];
  298.       }
  299.  
  300.       return paks;
  301.     }
  302.  
  303.     static IEnumerable<Packet> AnalyzeLog(string[] lines) {
  304.       Packet curPacket = null;
  305.       double lastTime = 0;
  306.       bool firstPak = true;
  307.       List<byte> pak = new List<byte>();
  308.  
  309.       foreach (var line in lines) {
  310.         if (line == "Time [s], Analyzer Name, Decoded Protocol Result")
  311.           continue;
  312.         if (line.Contains("error"))
  313.           continue;
  314.  
  315.         // Parse input
  316.         string[] split = line.Split(new char[] { ',' }, 3);
  317.         double time = double.Parse(split[0]);
  318.         string port = split[1];
  319.         byte value;
  320.         string data = split[2];
  321.         Match hexMatch = Regex.Match(data, @".*\(0x([0-9A-F]{2})\)$");
  322.         if (hexMatch.Success)
  323.           value = byte.Parse(hexMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
  324.         else if (data == @"\n")
  325.           value = (byte)'\n';
  326.         else if (data == @"\t")
  327.           value = (byte)'\t';
  328.         else if (data == "' '")
  329.           value = (byte)' ';
  330.         else if (data == "'")
  331.           value = (byte)'\''; // ??
  332.         else if (data.StartsWith("'"))
  333.           value = byte.Parse(data.Trim('\'')); // decimal
  334.         else if (data.StartsWith("0x"))
  335.           value = byte.Parse(data.Substring("0x".Length), System.Globalization.NumberStyles.HexNumber);
  336.         else if (data.Length == 1)
  337.           value = (byte)data[0];
  338.         else
  339.           throw new Exception();
  340.  
  341.         // Group together packets
  342.         if (curPacket?.Port != port || time - lastTime > 0.005 || time < lastTime) { // New packet
  343.           if (firstPak) {
  344.             if (curPacket != null) {
  345.               // Skip first packet since it might be partial
  346.               firstPak = false;
  347.             }
  348.           } else {
  349.             curPacket.Data = pak.ToArray();
  350.             curPacket.EndTime = lastTime;
  351.             //if (curPacket.EndTime < 10)
  352.               yield return curPacket;
  353.           }
  354.           curPacket = new Packet() { StartTime = time, Port = port };
  355.           pak.Clear();
  356.         }
  357.  
  358.         // Build packet
  359.         pak.Add(value);
  360.         lastTime = time;
  361.       }
  362.     }
  363.  
  364.     private static PacketReaderSpec FindPacketReaderSpec(Packet pak) {
  365.       return packetReaderSpecs.FirstOrDefault(pr => pr.Matches(pak));
  366.     }
  367.  
  368.     private class Pair {
  369.       public byte K;
  370.       public ushort KU;
  371.       public string V;
  372.  
  373.       public static Pair of(byte k, string v) {
  374.         return new Pair() { K = k, V = v };
  375.       }
  376.       public static Pair of(ushort k, string v) {
  377.         return new Pair() { KU = k, V = v };
  378.       }
  379.     }
  380.  
  381.     private class PacketReaderSpec {
  382.       public ushort PacketID { get; set; }
  383.       public int Len { get; set; }
  384.       public string Name { get; set; }
  385.       public Func<PacketReader, string> ParseFn { get; set; }
  386.  
  387.       public String Parse(Packet pak) {
  388.         string str = "";
  389.         if (pak.Data.Length != Len) {
  390.           str += $" INVALID_LEN:{pak.Data.Length}!={Len}";
  391.           return str;
  392.         }
  393.         var pr = new PacketReader(pak.Data);
  394.         pr.KnownConst("id1", (byte)(PacketID >> 8));
  395.         pr.KnownConst("id2", (byte)(PacketID & 0xFF));
  396.         str += " " + (ParseFn != null ? ParseFn(pr) : pr.Build());
  397.         return str;
  398.       }
  399.  
  400.       public bool Matches(Packet pak) {
  401.         byte[] packetPrefix = { (byte)(PacketID >> 8), (byte)(PacketID & 0xFF) };
  402.         if (pak.Data.Length < packetPrefix.Length)
  403.           return false;
  404.         for (int i = 0; i < packetPrefix.Length; i++) {
  405.           if (packetPrefix[i] != pak.Data[i])
  406.             return false;
  407.         }
  408.         return true;
  409.       }
  410.     }
  411.  
  412.     private class PacketReader {
  413.       private string str = "";
  414.       private BinaryReader br;
  415.       private int unknown = 0;
  416.  
  417.       public PacketReader(byte[] data) {
  418.         MemoryStream ms = new MemoryStream(data);
  419.         ms.Capacity = data.Length;
  420.         br = new BinaryReader(ms);
  421.       }
  422.  
  423.       public byte Byte(string name) {
  424.         byte data = br.ReadByte();
  425.         Add(name, data.ToString("X2"));
  426.         return data;
  427.       }
  428.       public byte ExpectByte(string name, params byte[] expectedValues) {
  429.         byte data = br.ReadByte();
  430.         if (expectedValues.Any(e => e == data))
  431.           Add(name, data.ToString("X2"));
  432.         else
  433.           Add($"DIFF[{name}]", data.ToString("X2"));
  434.         return data;
  435.       }
  436.       public byte ExpectByte(string name, Dictionary<byte,string> expectedValues) {
  437.         byte data = br.ReadByte();
  438.         if (expectedValues.ContainsKey(data)) {
  439.           if (expectedValues[data] != "")
  440.             Add(name, expectedValues[data]);
  441.         } else
  442.           Add($"DIFF[{name}]", data.ToString("X2"));
  443.         return data;
  444.       }
  445.       public byte ExpectByte(string name, params Pair[] expectedValues) {
  446.         return ExpectByte(name, expectedValues.ToDictionary(kv => kv.K, kv => kv.V));
  447.       }
  448.       public T Enum<T>(string name) where T : struct {
  449.         T data = (T) System.Enum.Parse(typeof(T), br.ReadByte().ToString());
  450.         Add(name, data.ToString().Replace(" ", ""));
  451.         return data;
  452.       }
  453.       public byte[] Bytes(string name, int num) {
  454.         byte[] data = br.ReadBytes(num);
  455.         Add(name, data.Aggregate("", (str, bytes) => str + bytes.ToString("X2")));
  456.         return data;
  457.       }
  458.       public byte[] Print(string name, int num, Func<byte,string> printFn) {
  459.         byte[] data = br.ReadBytes(num);
  460.         string aggr = data.Aggregate("", (str, bytes) => str + printFn(bytes));
  461.         if (aggr != "")
  462.           Add(name, aggr);
  463.         return data;
  464.       }
  465.       public ushort Ushort(string name) {
  466.         byte b1 = br.ReadByte();
  467.         byte b2 = br.ReadByte();
  468.         Add(name, $"{b1:X2}{b2:X2}");
  469.         return (ushort)((b1 << 8) | b2);
  470.       }
  471.       public ushort ExpectUshort(string name, Dictionary<ushort, string> expectedValues) {
  472.         byte b1 = br.ReadByte();
  473.         byte b2 = br.ReadByte();
  474.         ushort data = (ushort)((b1 << 8) | b2);
  475.         if (expectedValues.ContainsKey(data)) {
  476.           if (expectedValues[data] != "")
  477.             Add(name, expectedValues[data]);
  478.         } else
  479.           Add($"DIFF[{name}]", data.ToString("X2"));
  480.         return data;
  481.       }
  482.       public ushort ExpectUshort(string name, params Pair[] expectedValues) {
  483.         Dictionary<ushort, string> d = expectedValues.ToDictionary(kv => kv.KU, kv => kv.V);
  484.         return ExpectUshort(name, d);
  485.       }
  486.       public void Checksum() {
  487.         KnownConst("chk", CalculateChecksum());
  488.       }
  489.       public void InvChecksum(byte magicOffset) {
  490.         KnownConst("ichk", (byte)(~CalculateChecksum() + magicOffset));
  491.       }
  492.       private byte CalculateChecksum() {
  493.         var curPos = br.BaseStream.Position;
  494.         br.BaseStream.Position = 0;
  495.         byte sum = 0;
  496.         while (br.BaseStream.Position < curPos) {
  497.           sum += br.ReadByte();
  498.         }
  499.         return sum;
  500.       }
  501.       public string String(string name, int num) {
  502.         var str = new string(br.ReadChars(num));
  503.         Add(name, '"' + str + '"');
  504.         return str;
  505.       }
  506.       public byte KnownConst(string name, byte expected) {
  507.         byte val = br.ReadByte();
  508.         if (val != expected)
  509.           Add(name, $"{val:X2}!={expected:X2}");
  510.         return val;
  511.       }
  512.       public byte UnkConst(byte expected) {
  513.         return KnownConst($"[{br.BaseStream.Position}]", expected);
  514.       }
  515.       public byte[] UnkConsts(params byte[] expected) {
  516.         byte[] res = new byte[expected.Length];
  517.         for (int i = 0; i < expected.Length; i++)
  518.           res[i] = UnkConst(expected[i]);
  519.         return res;
  520.       }
  521.       public void Skip(int num) {
  522.         Bytes($"unk{unknown++}x{num}", num);
  523.       }
  524.       public void Zeros(int num) {
  525.         while (num-- > 0)
  526.           UnkConst(0x00);
  527.       }
  528.  
  529.       public string Build() {
  530.         var remainingLen = br.BaseStream.Length - br.BaseStream.Position;
  531.         if (remainingLen > 0)
  532.           Skip((int) remainingLen);
  533.         return str;
  534.       }
  535.  
  536.       private void Add(string key, string value) {
  537.         str += $"{key}={value} ";
  538.       }
  539.     }
  540.   }
  541. }
RAW Paste Data