378 lines
8.3 KiB
C#
378 lines
8.3 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Text;
|
||
|
|
|
||
|
|
namespace PureCrack.Wire;
|
||
|
|
|
||
|
|
public static class ProtoNet
|
||
|
|
{
|
||
|
|
private const int MaxDumpDepth = 8;
|
||
|
|
|
||
|
|
public static byte[] WriteVarint(ulong n)
|
||
|
|
{
|
||
|
|
Span<byte> span = stackalloc byte[10];
|
||
|
|
int length = 0;
|
||
|
|
while (n > 127)
|
||
|
|
{
|
||
|
|
span[length++] = (byte)((n & 0x7F) | 0x80);
|
||
|
|
n >>= 7;
|
||
|
|
}
|
||
|
|
span[length++] = (byte)n;
|
||
|
|
return span.Slice(0, length).ToArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] WriteTag(int field, ProtoWire wire)
|
||
|
|
{
|
||
|
|
return WriteVarint((ulong)((long)field << 3) | (ulong)wire);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] FString(int field, string s)
|
||
|
|
{
|
||
|
|
byte[] bytes = Encoding.UTF8.GetBytes(s);
|
||
|
|
return Concat(WriteTag(field, ProtoWire.Bytes), WriteVarint((ulong)bytes.Length), bytes);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] FBytes(int field, byte[] b)
|
||
|
|
{
|
||
|
|
return Concat(WriteTag(field, ProtoWire.Bytes), WriteVarint((ulong)b.Length), b);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] FInt(int field, long v)
|
||
|
|
{
|
||
|
|
return Concat(WriteTag(field, ProtoWire.Varint), WriteVarint((ulong)v));
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] FBool(int field, bool v)
|
||
|
|
{
|
||
|
|
return Concat(WriteTag(field, ProtoWire.Varint), WriteVarint((ulong)(v ? 1 : 0)));
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[] FSub(int field, byte[] body)
|
||
|
|
{
|
||
|
|
return Concat(WriteTag(field, ProtoWire.Bytes), WriteVarint((ulong)body.Length), body);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static byte[] Concat(params byte[][] chunks)
|
||
|
|
{
|
||
|
|
int num = 0;
|
||
|
|
byte[][] array = chunks;
|
||
|
|
foreach (byte[] array2 in array)
|
||
|
|
{
|
||
|
|
num += array2.Length;
|
||
|
|
}
|
||
|
|
byte[] array3 = new byte[num];
|
||
|
|
int num2 = 0;
|
||
|
|
array = chunks;
|
||
|
|
foreach (byte[] array4 in array)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(array4, 0, array3, num2, array4.Length);
|
||
|
|
num2 += array4.Length;
|
||
|
|
}
|
||
|
|
return array3;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static (ulong val, int newOff) ReadVarint(byte[] buf, int off)
|
||
|
|
{
|
||
|
|
ulong num = 0uL;
|
||
|
|
int num2 = 0;
|
||
|
|
while (off < buf.Length)
|
||
|
|
{
|
||
|
|
byte b = buf[off++];
|
||
|
|
num |= (ulong)((long)(b & 0x7F) << num2);
|
||
|
|
if ((b & 0x80) == 0)
|
||
|
|
{
|
||
|
|
return (val: num, newOff: off);
|
||
|
|
}
|
||
|
|
num2 += 7;
|
||
|
|
if (num2 >= 64)
|
||
|
|
{
|
||
|
|
throw new FormatException("varint exceeds 10 bytes");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
throw new FormatException("truncated varint");
|
||
|
|
}
|
||
|
|
|
||
|
|
public static Dictionary<int, List<ProtoValue>> Parse(byte[] data)
|
||
|
|
{
|
||
|
|
Dictionary<int, List<ProtoValue>> dictionary = new Dictionary<int, List<ProtoValue>>();
|
||
|
|
int num = 0;
|
||
|
|
while (num < data.Length)
|
||
|
|
{
|
||
|
|
(ulong val, int newOff) tuple = ReadVarint(data, num);
|
||
|
|
ulong item = tuple.val;
|
||
|
|
num = tuple.newOff;
|
||
|
|
int key = (int)(item >> 3);
|
||
|
|
int num2 = (int)(item & 7);
|
||
|
|
ProtoValue item2;
|
||
|
|
switch (num2)
|
||
|
|
{
|
||
|
|
case 0:
|
||
|
|
{
|
||
|
|
(ulong val, int newOff) tuple2 = ReadVarint(data, num);
|
||
|
|
ulong item3 = tuple2.val;
|
||
|
|
num = tuple2.newOff;
|
||
|
|
item2 = ProtoValue.OfVarint(item3);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 1:
|
||
|
|
if (num + 8 > data.Length)
|
||
|
|
{
|
||
|
|
throw new FormatException("truncated fixed64");
|
||
|
|
}
|
||
|
|
item2 = ProtoValue.OfFixed64(BitConverter.ToUInt64(data, num));
|
||
|
|
num += 8;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
{
|
||
|
|
ulong num3;
|
||
|
|
(num3, num) = ReadVarint(data, num);
|
||
|
|
if (num + (int)num3 > data.Length)
|
||
|
|
{
|
||
|
|
throw new FormatException("truncated length-delimited");
|
||
|
|
}
|
||
|
|
byte[] array = new byte[(uint)num3];
|
||
|
|
Buffer.BlockCopy(data, num, array, 0, (int)num3);
|
||
|
|
num += (int)num3;
|
||
|
|
item2 = ProtoValue.OfBytes(array);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 5:
|
||
|
|
if (num + 4 > data.Length)
|
||
|
|
{
|
||
|
|
throw new FormatException("truncated fixed32");
|
||
|
|
}
|
||
|
|
item2 = ProtoValue.OfFixed32(BitConverter.ToUInt32(data, num));
|
||
|
|
num += 4;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
throw new FormatException($"unknown wire type {num2} at offset {num}");
|
||
|
|
}
|
||
|
|
if (!dictionary.TryGetValue(key, out var value))
|
||
|
|
{
|
||
|
|
value = (dictionary[key] = new List<ProtoValue>());
|
||
|
|
}
|
||
|
|
value.Add(item2);
|
||
|
|
}
|
||
|
|
return dictionary;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static List<string> GetStrings(Dictionary<int, List<ProtoValue>> parsed, int field)
|
||
|
|
{
|
||
|
|
if (!parsed.TryGetValue(field, out List<ProtoValue> value))
|
||
|
|
{
|
||
|
|
return new List<string>();
|
||
|
|
}
|
||
|
|
List<string> list = new List<string>(value.Count);
|
||
|
|
foreach (ProtoValue item in value)
|
||
|
|
{
|
||
|
|
if (item.Wire == ProtoWire.Bytes)
|
||
|
|
{
|
||
|
|
list.Add(Encoding.UTF8.GetString(item.Bytes));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static List<long> GetInts(Dictionary<int, List<ProtoValue>> parsed, int field)
|
||
|
|
{
|
||
|
|
if (!parsed.TryGetValue(field, out List<ProtoValue> value))
|
||
|
|
{
|
||
|
|
return new List<long>();
|
||
|
|
}
|
||
|
|
List<long> list = new List<long>(value.Count);
|
||
|
|
foreach (ProtoValue item in value)
|
||
|
|
{
|
||
|
|
if (item.Wire == ProtoWire.Varint)
|
||
|
|
{
|
||
|
|
list.Add((long)item.Number);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static string? FirstString(Dictionary<int, List<ProtoValue>> parsed, int field, string? fallback = null)
|
||
|
|
{
|
||
|
|
List<string> strings = GetStrings(parsed, field);
|
||
|
|
if (strings.Count <= 0)
|
||
|
|
{
|
||
|
|
return fallback;
|
||
|
|
}
|
||
|
|
return strings[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
public static byte[]? FirstSub(Dictionary<int, List<ProtoValue>> parsed, int field)
|
||
|
|
{
|
||
|
|
if (!parsed.TryGetValue(field, out List<ProtoValue> value))
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
foreach (ProtoValue item in value)
|
||
|
|
{
|
||
|
|
if (item.Wire == ProtoWire.Bytes)
|
||
|
|
{
|
||
|
|
return item.Bytes;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static string Dump(byte[] data)
|
||
|
|
{
|
||
|
|
StringBuilder stringBuilder = new StringBuilder();
|
||
|
|
DumpInto(data, 0, stringBuilder);
|
||
|
|
return stringBuilder.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void DumpInto(byte[] data, int depth, StringBuilder sb)
|
||
|
|
{
|
||
|
|
if (depth > 8)
|
||
|
|
{
|
||
|
|
sb.Append(Indent(depth)).Append("<max depth>\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
Dictionary<int, List<ProtoValue>> source;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
source = Parse(data);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
sb.Append(Indent(depth)).Append("<parse err: ").Append(ex.Message)
|
||
|
|
.Append(">\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
foreach (KeyValuePair<int, List<ProtoValue>> item in source.OrderBy((KeyValuePair<int, List<ProtoValue>> p) => p.Key))
|
||
|
|
{
|
||
|
|
foreach (ProtoValue item2 in item.Value)
|
||
|
|
{
|
||
|
|
string value = Indent(depth);
|
||
|
|
switch (item2.Wire)
|
||
|
|
{
|
||
|
|
case ProtoWire.Varint:
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" varint = ")
|
||
|
|
.Append(item2.Number)
|
||
|
|
.Append('\n');
|
||
|
|
break;
|
||
|
|
case ProtoWire.Fixed64:
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" fixed64 = 0x")
|
||
|
|
.Append(item2.Number.ToString("x16"))
|
||
|
|
.Append('\n');
|
||
|
|
break;
|
||
|
|
case ProtoWire.Fixed32:
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" fixed32 = 0x")
|
||
|
|
.Append(((uint)item2.Number).ToString("x8"))
|
||
|
|
.Append('\n');
|
||
|
|
break;
|
||
|
|
case ProtoWire.Bytes:
|
||
|
|
{
|
||
|
|
string s;
|
||
|
|
if (item2.Bytes.Length == 0)
|
||
|
|
{
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" empty\n");
|
||
|
|
}
|
||
|
|
else if (LooksLikeProto(item2.Bytes))
|
||
|
|
{
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" sub(")
|
||
|
|
.Append(item2.Bytes.Length)
|
||
|
|
.Append("):\n");
|
||
|
|
DumpInto(item2.Bytes, depth + 1, sb);
|
||
|
|
}
|
||
|
|
else if (TryUtf8(item2.Bytes, out s))
|
||
|
|
{
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" str(")
|
||
|
|
.Append(item2.Bytes.Length)
|
||
|
|
.Append(") = ")
|
||
|
|
.Append(EscapeString(s))
|
||
|
|
.Append('\n');
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
sb.Append(value).Append('F').Append(item.Key)
|
||
|
|
.Append(" bytes(")
|
||
|
|
.Append(item2.Bytes.Length)
|
||
|
|
.Append(") = ")
|
||
|
|
.Append(HexHead(item2.Bytes, 24))
|
||
|
|
.Append('\n');
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string Indent(int depth)
|
||
|
|
{
|
||
|
|
return new string(' ', depth * 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool TryUtf8(byte[] b, out string s)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
s = Encoding.UTF8.GetString(b);
|
||
|
|
string text = s;
|
||
|
|
foreach (char c in text)
|
||
|
|
{
|
||
|
|
if (c < ' ' && c != '\t' && c != '\n' && c != '\r')
|
||
|
|
{
|
||
|
|
s = "";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
s = "";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool LooksLikeProto(byte[] b)
|
||
|
|
{
|
||
|
|
if (b.Length < 2)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
try
|
||
|
|
{
|
||
|
|
return Parse(b).Count > 0;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string HexHead(byte[] b, int n)
|
||
|
|
{
|
||
|
|
StringBuilder stringBuilder = new StringBuilder(n * 3 + 4);
|
||
|
|
for (int i = 0; i < Math.Min(n, b.Length); i++)
|
||
|
|
{
|
||
|
|
stringBuilder.Append(b[i].ToString("x2")).Append(' ');
|
||
|
|
}
|
||
|
|
if (b.Length > n)
|
||
|
|
{
|
||
|
|
stringBuilder.Append("...");
|
||
|
|
}
|
||
|
|
return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string EscapeString(string s)
|
||
|
|
{
|
||
|
|
if (s.Length > 80)
|
||
|
|
{
|
||
|
|
s = s.Substring(0, 80) + "...";
|
||
|
|
}
|
||
|
|
return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
||
|
|
}
|
||
|
|
}
|