Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // idProfile 0.34 by infogram :)
- // Requires Farmhash.Sharp & Newtonsoft.Json Nuget packages!
- using System;
- using System.IO;
- using System.Text;
- using System.Xml.Serialization;
- using System.Collections.Generic;
- using System.Security.Cryptography;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Converters;
- namespace idProfile
- {
- [JsonConverter(typeof(StringEnumConverter))]
- public enum ProfileVariableType : byte
- {
- Unknown = 0,
- Int8 = 1,
- Int16 = 2,
- Int32 = 3,
- Int64 = 4,
- UInt8 = 5,
- UInt16 = 6,
- UInt32 = 7,
- UInt64 = 8,
- Double = 0x9,
- String = 0xA,
- BooleanFalse = 0xB, // no data, type is value
- BooleanTrue = 0xC, // no data, type is value
- Values = 0xD, // contains multiple values
- KeyValues = 0xE, // contains multiple key-value pairs
- // 0xF+ aren't used?
- Boolean = 0xFF, // fake type, not used by game, but used in XML
- }
- public class Variable
- {
- [JsonIgnore]
- public ProfileVariableType Type;
- private sbyte _int8;
- private Int16 _int16;
- private Int32 _int32;
- private Int64 _int64;
- private byte _uint8;
- private UInt16 _uint16;
- private UInt32 _uint32;
- private UInt64 _uint64;
- private double _double;
- private string _string;
- private List<Variable> _values;
- private Dictionary<string, Variable> _keyValues;
- private bool _boolean;
- // Use properties for the public value fields, so they can automatically update Type value when set
- public sbyte Int8
- {
- get { return _int8; }
- set { _int8 = value; Type = ProfileVariableType.Int8; }
- }
- public Int16 Int16
- {
- get { return _int16; }
- set { _int16 = value; Type = ProfileVariableType.Int16; }
- }
- public Int32 Int32
- {
- get { return _int32; }
- set { _int32 = value; Type = ProfileVariableType.Int32; }
- }
- public Int64 Int64
- {
- get { return _int64; }
- set { _int64 = value; Type = ProfileVariableType.Int64; }
- }
- public byte UInt8
- {
- get { return _uint8; }
- set { _uint8 = value; Type = ProfileVariableType.UInt8; }
- }
- public UInt16 UInt16
- {
- get { return _uint16; }
- set { _uint16 = value; Type = ProfileVariableType.UInt16; }
- }
- public UInt32 UInt32
- {
- get { return _uint32; }
- set { _uint32 = value; Type = ProfileVariableType.UInt32; }
- }
- public UInt64 UInt64
- {
- get { return _uint64; }
- set { _uint64 = value; Type = ProfileVariableType.UInt64; }
- }
- public double Double
- {
- get { return _double; }
- set { _double = value; Type = ProfileVariableType.Double; }
- }
- public string String
- {
- get { return _string; }
- set { _string = value; if (value != null) Type = ProfileVariableType.String; }
- }
- public List<Variable> Values
- {
- get { return _values; }
- set { _values = value; if (value != null) Type = ProfileVariableType.Values; }
- }
- public Dictionary<string, Variable> KeyValues
- {
- get { return _keyValues; }
- set { _keyValues = value; if (value != null) Type = ProfileVariableType.KeyValues; }
- }
- public bool Boolean
- {
- get { return _boolean; }
- set { _boolean = value; Type = ProfileVariableType.Boolean; }
- }
- // ShouldSerialize*, gets used by JsonConvert to tell if the field should be output into the JSON or not
- public bool ShouldSerializeInt8()
- {
- return Type == ProfileVariableType.Int8;
- }
- public bool ShouldSerializeInt16()
- {
- return Type == ProfileVariableType.Int16;
- }
- public bool ShouldSerializeInt32()
- {
- return Type == ProfileVariableType.Int32;
- }
- public bool ShouldSerializeInt64()
- {
- return Type == ProfileVariableType.Int64;
- }
- public bool ShouldSerializeUInt8()
- {
- return Type == ProfileVariableType.UInt8;
- }
- public bool ShouldSerializeUInt16()
- {
- return Type == ProfileVariableType.UInt16;
- }
- public bool ShouldSerializeUInt32()
- {
- return Type == ProfileVariableType.UInt32;
- }
- public bool ShouldSerializeUInt64()
- {
- return Type == ProfileVariableType.UInt64;
- }
- public bool ShouldSerializeDouble()
- {
- return Type == ProfileVariableType.Double;
- }
- public bool ShouldSerializeString()
- {
- return Type == ProfileVariableType.String;
- }
- public bool ShouldSerializeValues()
- {
- return Type == ProfileVariableType.Values;
- }
- public bool ShouldSerializeKeyValues()
- {
- return Type == ProfileVariableType.KeyValues;
- }
- public bool ShouldSerializeBoolean()
- {
- return Type == ProfileVariableType.Boolean;
- }
- public void Read(BinaryReader reader)
- {
- // sub_140A66E50 handles parsing - appears to read into JSON objects
- Type = (ProfileVariableType)reader.ReadByte();
- if (Type == ProfileVariableType.BooleanFalse || Type == ProfileVariableType.BooleanTrue)
- {
- // Hack to make XML a little cleaner
- Boolean = Type == ProfileVariableType.BooleanTrue;
- Type = ProfileVariableType.Boolean;
- return;
- }
- switch (Type)
- {
- case ProfileVariableType.Int8:
- Int8 = reader.ReadSByte();
- break;
- case ProfileVariableType.Int16:
- Int16 = reader.ReadInt16();
- break;
- case ProfileVariableType.Int32:
- Int32 = reader.ReadInt32();
- break;
- case ProfileVariableType.Int64:
- Int64 = reader.ReadInt64();
- break;
- case ProfileVariableType.UInt8:
- UInt8 = reader.ReadByte();
- break;
- case ProfileVariableType.UInt16:
- UInt16 = reader.ReadUInt16();
- break;
- case ProfileVariableType.UInt32:
- UInt32 = reader.ReadUInt32();
- break;
- case ProfileVariableType.UInt64:
- UInt64 = reader.ReadUInt64();
- break;
- case ProfileVariableType.Double:
- Double = reader.ReadDouble();
- break;
- case ProfileVariableType.String:
- String = reader.ReadIdString();
- break;
- case ProfileVariableType.Values:
- {
- var count = reader.ReadOptimizedUInt32();
- Values = new List<Variable>();
- for (uint i = 0; i < count; i++)
- {
- var variable = new Variable();
- variable.Read(reader);
- Values.Add(variable);
- }
- break;
- }
- case ProfileVariableType.KeyValues:
- {
- var count = reader.ReadOptimizedUInt32();
- KeyValues = new Dictionary<string, Variable>();
- for (uint i = 0; i < count; i++)
- {
- var variable = new Variable();
- var key = reader.ReadIdString(); // XmlSerializer won't work with dictionary, have to stuff Key into the class
- variable.Read(reader);
- KeyValues.Add(key, variable);
- }
- break;
- }
- default:
- {
- Console.WriteLine($"Warning: unhandled value type {Type}!");
- Program.WarnedUser = true;
- }
- break;
- }
- }
- public void Write(BinaryWriter writer)
- {
- if (Type == ProfileVariableType.Boolean)
- {
- writer.Write((byte)(Boolean ? ProfileVariableType.BooleanTrue : ProfileVariableType.BooleanFalse));
- return;
- }
- writer.Write((byte)Type);
- switch (Type)
- {
- case ProfileVariableType.Int8:
- writer.Write((byte)Int8);
- break;
- case ProfileVariableType.Int16:
- writer.Write(Int16);
- break;
- case ProfileVariableType.Int32:
- writer.Write(Int32);
- break;
- case ProfileVariableType.Int64:
- writer.Write(Int64);
- break;
- case ProfileVariableType.UInt8:
- writer.Write(UInt8);
- break;
- case ProfileVariableType.UInt16:
- writer.Write(UInt16);
- break;
- case ProfileVariableType.UInt32:
- writer.Write(UInt32);
- break;
- case ProfileVariableType.UInt64:
- writer.Write(UInt64);
- break;
- case ProfileVariableType.Double:
- writer.Write(Double);
- break;
- case ProfileVariableType.String:
- writer.WriteIdString(String);
- break;
- case ProfileVariableType.Values:
- {
- writer.WriteOptimizedUInt32((uint)Values.Count);
- foreach (var variable in Values)
- variable.Write(writer);
- }
- break;
- case ProfileVariableType.KeyValues:
- {
- writer.WriteOptimizedUInt32((uint)KeyValues.Count);
- foreach (var variable in KeyValues)
- {
- writer.WriteIdString(variable.Key);
- variable.Value.Write(writer);
- }
- }
- break;
- default:
- {
- Console.WriteLine($"Warning: Key has unhandled value type {Type}!");
- Program.WarnedUser = true;
- }
- break;
- }
- }
- }
- public class DOOMProfile
- {
- // Header
- public uint Magic;
- public uint HashType;
- public uint Hash;
- // Data
- public Variable RootVariable;
- public void Read(BinaryReader reader)
- {
- Magic = reader.ReadUInt32().EndianSwap();
- HashType = reader.ReadUInt32().EndianSwap();
- Hash = reader.ReadUInt32().EndianSwap();
- RootVariable = new Variable();
- RootVariable.Read(reader);
- }
- public void Write(BinaryWriter writer)
- {
- // Update Hash before writing header
- byte[] variablesData;
- using (var ms = new MemoryStream())
- using (var msWriter = new BinaryWriter(ms))
- {
- RootVariable.Write(msWriter);
- variablesData = ms.ToArray();
- }
- Hash = CalculateHash(variablesData, HashType);
- // Header
- writer.Write(Magic.EndianSwap());
- writer.Write(HashType.EndianSwap());
- writer.Write(Hash.EndianSwap());
- // Data
- writer.Write(variablesData);
- }
- public static uint CalculateHash(byte[] data, uint hashType)
- {
- uint hash;
- if (hashType < 2)
- {
- // This is pretty much the essence of what sub_1403652C0 is doing, confirmed through idHash testing
- byte[] md5Hash = MD5.Create().ComputeHash(data);
- uint int0 = BitConverter.ToUInt32(md5Hash, 0x0);
- uint int4 = BitConverter.ToUInt32(md5Hash, 0x4);
- uint int8 = BitConverter.ToUInt32(md5Hash, 0x8);
- uint intC = BitConverter.ToUInt32(md5Hash, 0xC);
- hash = int0 ^ int4 ^ int8 ^ intC;
- }
- else
- hash = Farmhash.Sharp.Farmhash.Hash32(data, data.Length);
- return hash;
- }
- }
- class Program
- {
- public static bool WarnedUser = false;
- static void Main(string[] args)
- {
- Console.WriteLine("idProfile 0.34 - by infogram");
- if (args.Length < 1)
- {
- Console.WriteLine("Usage: idProfile.exe <path/to/decrypted/profile.bin>");
- Console.WriteLine(" idProfile.exe <path/to/deserialized/json.json>");
- return;
- }
- var profile = new DOOMProfile();
- var path = args[0];
- if (Path.GetExtension(path).ToLower() == ".xml")
- {
- Console.WriteLine("idProfile now only serializes JSON files, use idProfile 0.33 instead.");
- WarnedUser = true;
- }
- else
- {
- if (Path.GetExtension(path).ToLower() == ".json")
- {
- var fileData = File.ReadAllText(path);
- profile = JsonConvert.DeserializeObject<DOOMProfile>(fileData);
- using (var writer = new BinaryWriter(File.Create(path + ".bin")))
- profile.Write(writer);
- Console.WriteLine("Serialized profile to " + path + ".bin");
- }
- else
- {
- using (var reader = new BinaryReader(File.OpenRead(path)))
- {
- profile.Read(reader);
- var json = JsonConvert.SerializeObject(profile, Formatting.Indented);
- File.WriteAllText(path + ".json", json);
- Console.WriteLine("Deserialized profile to " + path + ".json");
- }
- }
- }
- if (WarnedUser)
- {
- Console.WriteLine("Press a key to exit...");
- Console.ReadKey();
- }
- }
- }
- public static class Utility
- {
- // Reads an idTech string
- public static string ReadIdString(this BinaryReader reader)
- {
- uint size = reader.ReadOptimizedUInt32();
- byte[] data = reader.ReadBytes((int)size);
- return Encoding.UTF8.GetString(data);
- }
- // Writes an idTech string
- public static void WriteIdString(this BinaryWriter writer, string value)
- {
- byte[] data = Encoding.UTF8.GetBytes(value);
- writer.WriteOptimizedUInt32((uint)data.Length);
- writer.Write(data);
- }
- // Reads an idTech optimized int (1 byte if possible, otherwise 4 bytes)
- public static uint ReadOptimizedUInt32(this BinaryReader reader)
- {
- // special int used by idTech to store ints efficiently
- // if (int << 1) is below 0x100, (int << 1) is stored as byte
- // otherwise ((int << 1) | 1) is stored as an int instead
- // the lowest bit (1) is used to tell if it's a byte or int
- // (values are shifted by 1 beforehand to make sure that bit won't be set)
- uint value = reader.ReadByte();
- if ((value & 1) == 1)
- {
- reader.BaseStream.Position--;
- value = reader.ReadUInt32();
- }
- return value >> 1; // undoes shift & will also remove the 1 bit
- }
- // Writes an idTech optimized int (1 byte if possible, otherwise 4 bytes)
- public static void WriteOptimizedUInt32(this BinaryWriter writer, uint value)
- {
- value = value << 1;
- if (value > 0xFF)
- {
- // won't fit in a byte
- writer.Write(value | 1);
- }
- else
- {
- writer.Write((byte)value);
- }
- }
- public static short EndianSwap(this short num)
- {
- byte[] data = BitConverter.GetBytes(num);
- Array.Reverse(data);
- return BitConverter.ToInt16(data, 0);
- }
- public static ushort EndianSwap(this ushort num)
- {
- byte[] data = BitConverter.GetBytes(num);
- Array.Reverse(data);
- return BitConverter.ToUInt16(data, 0);
- }
- public static int EndianSwap(this int num)
- {
- byte[] data = BitConverter.GetBytes(num);
- Array.Reverse(data);
- return BitConverter.ToInt32(data, 0);
- }
- public static uint EndianSwap(this uint num)
- {
- byte[] data = BitConverter.GetBytes(num);
- Array.Reverse(data);
- return BitConverter.ToUInt32(data, 0);
- }
- public static ulong EndianSwap(this ulong num)
- {
- byte[] data = BitConverter.GetBytes(num);
- Array.Reverse(data);
- return BitConverter.ToUInt64(data, 0);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement