Advertisement
Guest User

idProfile 0.34

a guest
Apr 2nd, 2020
448
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.12 KB | None | 0 0
  1. // idProfile 0.34 by infogram :)
  2. // Requires Farmhash.Sharp & Newtonsoft.Json Nuget packages!
  3. using System;
  4. using System.IO;
  5. using System.Text;
  6. using System.Xml.Serialization;
  7. using System.Collections.Generic;
  8. using System.Security.Cryptography;
  9. using Newtonsoft.Json;
  10. using Newtonsoft.Json.Converters;
  11.  
  12. namespace idProfile
  13. {
  14. [JsonConverter(typeof(StringEnumConverter))]
  15. public enum ProfileVariableType : byte
  16. {
  17. Unknown = 0,
  18.  
  19. Int8 = 1,
  20. Int16 = 2,
  21. Int32 = 3,
  22. Int64 = 4,
  23.  
  24. UInt8 = 5,
  25. UInt16 = 6,
  26. UInt32 = 7,
  27. UInt64 = 8,
  28.  
  29. Double = 0x9,
  30. String = 0xA,
  31. BooleanFalse = 0xB, // no data, type is value
  32. BooleanTrue = 0xC, // no data, type is value
  33.  
  34. Values = 0xD, // contains multiple values
  35. KeyValues = 0xE, // contains multiple key-value pairs
  36.  
  37. // 0xF+ aren't used?
  38.  
  39. Boolean = 0xFF, // fake type, not used by game, but used in XML
  40. }
  41.  
  42. public class Variable
  43. {
  44. [JsonIgnore]
  45. public ProfileVariableType Type;
  46.  
  47. private sbyte _int8;
  48. private Int16 _int16;
  49. private Int32 _int32;
  50. private Int64 _int64;
  51.  
  52. private byte _uint8;
  53. private UInt16 _uint16;
  54. private UInt32 _uint32;
  55. private UInt64 _uint64;
  56.  
  57. private double _double;
  58. private string _string;
  59.  
  60. private List<Variable> _values;
  61. private Dictionary<string, Variable> _keyValues;
  62.  
  63. private bool _boolean;
  64.  
  65. // Use properties for the public value fields, so they can automatically update Type value when set
  66. public sbyte Int8
  67. {
  68. get { return _int8; }
  69. set { _int8 = value; Type = ProfileVariableType.Int8; }
  70. }
  71. public Int16 Int16
  72. {
  73. get { return _int16; }
  74. set { _int16 = value; Type = ProfileVariableType.Int16; }
  75. }
  76. public Int32 Int32
  77. {
  78. get { return _int32; }
  79. set { _int32 = value; Type = ProfileVariableType.Int32; }
  80. }
  81. public Int64 Int64
  82. {
  83. get { return _int64; }
  84. set { _int64 = value; Type = ProfileVariableType.Int64; }
  85. }
  86.  
  87. public byte UInt8
  88. {
  89. get { return _uint8; }
  90. set { _uint8 = value; Type = ProfileVariableType.UInt8; }
  91. }
  92. public UInt16 UInt16
  93. {
  94. get { return _uint16; }
  95. set { _uint16 = value; Type = ProfileVariableType.UInt16; }
  96. }
  97. public UInt32 UInt32
  98. {
  99. get { return _uint32; }
  100. set { _uint32 = value; Type = ProfileVariableType.UInt32; }
  101. }
  102. public UInt64 UInt64
  103. {
  104. get { return _uint64; }
  105. set { _uint64 = value; Type = ProfileVariableType.UInt64; }
  106. }
  107.  
  108. public double Double
  109. {
  110. get { return _double; }
  111. set { _double = value; Type = ProfileVariableType.Double; }
  112. }
  113. public string String
  114. {
  115. get { return _string; }
  116. set { _string = value; if (value != null) Type = ProfileVariableType.String; }
  117. }
  118.  
  119. public List<Variable> Values
  120. {
  121. get { return _values; }
  122. set { _values = value; if (value != null) Type = ProfileVariableType.Values; }
  123. }
  124. public Dictionary<string, Variable> KeyValues
  125. {
  126. get { return _keyValues; }
  127. set { _keyValues = value; if (value != null) Type = ProfileVariableType.KeyValues; }
  128. }
  129.  
  130. public bool Boolean
  131. {
  132. get { return _boolean; }
  133. set { _boolean = value; Type = ProfileVariableType.Boolean; }
  134. }
  135.  
  136. // ShouldSerialize*, gets used by JsonConvert to tell if the field should be output into the JSON or not
  137. public bool ShouldSerializeInt8()
  138. {
  139. return Type == ProfileVariableType.Int8;
  140. }
  141. public bool ShouldSerializeInt16()
  142. {
  143. return Type == ProfileVariableType.Int16;
  144. }
  145. public bool ShouldSerializeInt32()
  146. {
  147. return Type == ProfileVariableType.Int32;
  148. }
  149. public bool ShouldSerializeInt64()
  150. {
  151. return Type == ProfileVariableType.Int64;
  152. }
  153. public bool ShouldSerializeUInt8()
  154. {
  155. return Type == ProfileVariableType.UInt8;
  156. }
  157. public bool ShouldSerializeUInt16()
  158. {
  159. return Type == ProfileVariableType.UInt16;
  160. }
  161. public bool ShouldSerializeUInt32()
  162. {
  163. return Type == ProfileVariableType.UInt32;
  164. }
  165. public bool ShouldSerializeUInt64()
  166. {
  167. return Type == ProfileVariableType.UInt64;
  168. }
  169. public bool ShouldSerializeDouble()
  170. {
  171. return Type == ProfileVariableType.Double;
  172. }
  173. public bool ShouldSerializeString()
  174. {
  175. return Type == ProfileVariableType.String;
  176. }
  177. public bool ShouldSerializeValues()
  178. {
  179. return Type == ProfileVariableType.Values;
  180. }
  181. public bool ShouldSerializeKeyValues()
  182. {
  183. return Type == ProfileVariableType.KeyValues;
  184. }
  185. public bool ShouldSerializeBoolean()
  186. {
  187. return Type == ProfileVariableType.Boolean;
  188. }
  189.  
  190. public void Read(BinaryReader reader)
  191. {
  192. // sub_140A66E50 handles parsing - appears to read into JSON objects
  193. Type = (ProfileVariableType)reader.ReadByte();
  194.  
  195. if (Type == ProfileVariableType.BooleanFalse || Type == ProfileVariableType.BooleanTrue)
  196. {
  197. // Hack to make XML a little cleaner
  198. Boolean = Type == ProfileVariableType.BooleanTrue;
  199. Type = ProfileVariableType.Boolean;
  200. return;
  201. }
  202.  
  203. switch (Type)
  204. {
  205. case ProfileVariableType.Int8:
  206. Int8 = reader.ReadSByte();
  207. break;
  208. case ProfileVariableType.Int16:
  209. Int16 = reader.ReadInt16();
  210. break;
  211. case ProfileVariableType.Int32:
  212. Int32 = reader.ReadInt32();
  213. break;
  214. case ProfileVariableType.Int64:
  215. Int64 = reader.ReadInt64();
  216. break;
  217. case ProfileVariableType.UInt8:
  218. UInt8 = reader.ReadByte();
  219. break;
  220. case ProfileVariableType.UInt16:
  221. UInt16 = reader.ReadUInt16();
  222. break;
  223. case ProfileVariableType.UInt32:
  224. UInt32 = reader.ReadUInt32();
  225. break;
  226. case ProfileVariableType.UInt64:
  227. UInt64 = reader.ReadUInt64();
  228. break;
  229. case ProfileVariableType.Double:
  230. Double = reader.ReadDouble();
  231. break;
  232. case ProfileVariableType.String:
  233. String = reader.ReadIdString();
  234. break;
  235. case ProfileVariableType.Values:
  236. {
  237. var count = reader.ReadOptimizedUInt32();
  238.  
  239. Values = new List<Variable>();
  240. for (uint i = 0; i < count; i++)
  241. {
  242. var variable = new Variable();
  243. variable.Read(reader);
  244. Values.Add(variable);
  245. }
  246. break;
  247. }
  248. case ProfileVariableType.KeyValues:
  249. {
  250. var count = reader.ReadOptimizedUInt32();
  251.  
  252. KeyValues = new Dictionary<string, Variable>();
  253. for (uint i = 0; i < count; i++)
  254. {
  255. var variable = new Variable();
  256. var key = reader.ReadIdString(); // XmlSerializer won't work with dictionary, have to stuff Key into the class
  257. variable.Read(reader);
  258. KeyValues.Add(key, variable);
  259. }
  260. break;
  261. }
  262. default:
  263. {
  264. Console.WriteLine($"Warning: unhandled value type {Type}!");
  265. Program.WarnedUser = true;
  266. }
  267. break;
  268. }
  269. }
  270.  
  271. public void Write(BinaryWriter writer)
  272. {
  273. if (Type == ProfileVariableType.Boolean)
  274. {
  275. writer.Write((byte)(Boolean ? ProfileVariableType.BooleanTrue : ProfileVariableType.BooleanFalse));
  276. return;
  277. }
  278.  
  279. writer.Write((byte)Type);
  280.  
  281. switch (Type)
  282. {
  283. case ProfileVariableType.Int8:
  284. writer.Write((byte)Int8);
  285. break;
  286. case ProfileVariableType.Int16:
  287. writer.Write(Int16);
  288. break;
  289. case ProfileVariableType.Int32:
  290. writer.Write(Int32);
  291. break;
  292. case ProfileVariableType.Int64:
  293. writer.Write(Int64);
  294. break;
  295. case ProfileVariableType.UInt8:
  296. writer.Write(UInt8);
  297. break;
  298. case ProfileVariableType.UInt16:
  299. writer.Write(UInt16);
  300. break;
  301. case ProfileVariableType.UInt32:
  302. writer.Write(UInt32);
  303. break;
  304. case ProfileVariableType.UInt64:
  305. writer.Write(UInt64);
  306. break;
  307. case ProfileVariableType.Double:
  308. writer.Write(Double);
  309. break;
  310. case ProfileVariableType.String:
  311. writer.WriteIdString(String);
  312. break;
  313. case ProfileVariableType.Values:
  314. {
  315. writer.WriteOptimizedUInt32((uint)Values.Count);
  316.  
  317. foreach (var variable in Values)
  318. variable.Write(writer);
  319. }
  320. break;
  321. case ProfileVariableType.KeyValues:
  322. {
  323. writer.WriteOptimizedUInt32((uint)KeyValues.Count);
  324.  
  325. foreach (var variable in KeyValues)
  326. {
  327. writer.WriteIdString(variable.Key);
  328. variable.Value.Write(writer);
  329. }
  330. }
  331. break;
  332. default:
  333. {
  334. Console.WriteLine($"Warning: Key has unhandled value type {Type}!");
  335. Program.WarnedUser = true;
  336. }
  337. break;
  338. }
  339. }
  340. }
  341.  
  342. public class DOOMProfile
  343. {
  344. // Header
  345. public uint Magic;
  346. public uint HashType;
  347. public uint Hash;
  348.  
  349. // Data
  350. public Variable RootVariable;
  351.  
  352. public void Read(BinaryReader reader)
  353. {
  354. Magic = reader.ReadUInt32().EndianSwap();
  355. HashType = reader.ReadUInt32().EndianSwap();
  356. Hash = reader.ReadUInt32().EndianSwap();
  357.  
  358. RootVariable = new Variable();
  359. RootVariable.Read(reader);
  360. }
  361.  
  362. public void Write(BinaryWriter writer)
  363. {
  364. // Update Hash before writing header
  365. byte[] variablesData;
  366. using (var ms = new MemoryStream())
  367. using (var msWriter = new BinaryWriter(ms))
  368. {
  369. RootVariable.Write(msWriter);
  370. variablesData = ms.ToArray();
  371. }
  372.  
  373. Hash = CalculateHash(variablesData, HashType);
  374.  
  375. // Header
  376. writer.Write(Magic.EndianSwap());
  377. writer.Write(HashType.EndianSwap());
  378. writer.Write(Hash.EndianSwap());
  379.  
  380. // Data
  381. writer.Write(variablesData);
  382. }
  383.  
  384. public static uint CalculateHash(byte[] data, uint hashType)
  385. {
  386. uint hash;
  387. if (hashType < 2)
  388. {
  389. // This is pretty much the essence of what sub_1403652C0 is doing, confirmed through idHash testing
  390. byte[] md5Hash = MD5.Create().ComputeHash(data);
  391. uint int0 = BitConverter.ToUInt32(md5Hash, 0x0);
  392. uint int4 = BitConverter.ToUInt32(md5Hash, 0x4);
  393. uint int8 = BitConverter.ToUInt32(md5Hash, 0x8);
  394. uint intC = BitConverter.ToUInt32(md5Hash, 0xC);
  395. hash = int0 ^ int4 ^ int8 ^ intC;
  396. }
  397. else
  398. hash = Farmhash.Sharp.Farmhash.Hash32(data, data.Length);
  399.  
  400. return hash;
  401. }
  402. }
  403.  
  404. class Program
  405. {
  406. public static bool WarnedUser = false;
  407. static void Main(string[] args)
  408. {
  409. Console.WriteLine("idProfile 0.34 - by infogram");
  410. if (args.Length < 1)
  411. {
  412. Console.WriteLine("Usage: idProfile.exe <path/to/decrypted/profile.bin>");
  413. Console.WriteLine(" idProfile.exe <path/to/deserialized/json.json>");
  414. return;
  415. }
  416.  
  417. var profile = new DOOMProfile();
  418.  
  419. var path = args[0];
  420.  
  421. if (Path.GetExtension(path).ToLower() == ".xml")
  422. {
  423. Console.WriteLine("idProfile now only serializes JSON files, use idProfile 0.33 instead.");
  424. WarnedUser = true;
  425. }
  426. else
  427. {
  428. if (Path.GetExtension(path).ToLower() == ".json")
  429. {
  430. var fileData = File.ReadAllText(path);
  431. profile = JsonConvert.DeserializeObject<DOOMProfile>(fileData);
  432.  
  433. using (var writer = new BinaryWriter(File.Create(path + ".bin")))
  434. profile.Write(writer);
  435.  
  436. Console.WriteLine("Serialized profile to " + path + ".bin");
  437. }
  438. else
  439. {
  440. using (var reader = new BinaryReader(File.OpenRead(path)))
  441. {
  442. profile.Read(reader);
  443.  
  444. var json = JsonConvert.SerializeObject(profile, Formatting.Indented);
  445. File.WriteAllText(path + ".json", json);
  446.  
  447. Console.WriteLine("Deserialized profile to " + path + ".json");
  448. }
  449. }
  450. }
  451.  
  452. if (WarnedUser)
  453. {
  454. Console.WriteLine("Press a key to exit...");
  455. Console.ReadKey();
  456. }
  457. }
  458. }
  459. public static class Utility
  460. {
  461. // Reads an idTech string
  462. public static string ReadIdString(this BinaryReader reader)
  463. {
  464. uint size = reader.ReadOptimizedUInt32();
  465. byte[] data = reader.ReadBytes((int)size);
  466. return Encoding.UTF8.GetString(data);
  467. }
  468.  
  469. // Writes an idTech string
  470. public static void WriteIdString(this BinaryWriter writer, string value)
  471. {
  472. byte[] data = Encoding.UTF8.GetBytes(value);
  473. writer.WriteOptimizedUInt32((uint)data.Length);
  474. writer.Write(data);
  475. }
  476.  
  477. // Reads an idTech optimized int (1 byte if possible, otherwise 4 bytes)
  478. public static uint ReadOptimizedUInt32(this BinaryReader reader)
  479. {
  480. // special int used by idTech to store ints efficiently
  481. // if (int << 1) is below 0x100, (int << 1) is stored as byte
  482. // otherwise ((int << 1) | 1) is stored as an int instead
  483. // the lowest bit (1) is used to tell if it's a byte or int
  484. // (values are shifted by 1 beforehand to make sure that bit won't be set)
  485.  
  486. uint value = reader.ReadByte();
  487. if ((value & 1) == 1)
  488. {
  489. reader.BaseStream.Position--;
  490. value = reader.ReadUInt32();
  491. }
  492.  
  493. return value >> 1; // undoes shift & will also remove the 1 bit
  494. }
  495.  
  496. // Writes an idTech optimized int (1 byte if possible, otherwise 4 bytes)
  497. public static void WriteOptimizedUInt32(this BinaryWriter writer, uint value)
  498. {
  499. value = value << 1;
  500. if (value > 0xFF)
  501. {
  502. // won't fit in a byte
  503. writer.Write(value | 1);
  504. }
  505. else
  506. {
  507. writer.Write((byte)value);
  508. }
  509. }
  510.  
  511. public static short EndianSwap(this short num)
  512. {
  513. byte[] data = BitConverter.GetBytes(num);
  514. Array.Reverse(data);
  515. return BitConverter.ToInt16(data, 0);
  516. }
  517.  
  518. public static ushort EndianSwap(this ushort num)
  519. {
  520. byte[] data = BitConverter.GetBytes(num);
  521. Array.Reverse(data);
  522. return BitConverter.ToUInt16(data, 0);
  523. }
  524.  
  525. public static int EndianSwap(this int num)
  526. {
  527. byte[] data = BitConverter.GetBytes(num);
  528. Array.Reverse(data);
  529. return BitConverter.ToInt32(data, 0);
  530. }
  531.  
  532. public static uint EndianSwap(this uint num)
  533. {
  534. byte[] data = BitConverter.GetBytes(num);
  535. Array.Reverse(data);
  536. return BitConverter.ToUInt32(data, 0);
  537. }
  538.  
  539. public static ulong EndianSwap(this ulong num)
  540. {
  541. byte[] data = BitConverter.GetBytes(num);
  542. Array.Reverse(data);
  543. return BitConverter.ToUInt64(data, 0);
  544. }
  545. }
  546. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement