diff --git a/Destiny.FileFormats/Destiny.FileFormats.csproj b/Destiny.FileFormats/Destiny.FileFormats.csproj new file mode 100644 index 0000000..50c908f --- /dev/null +++ b/Destiny.FileFormats/Destiny.FileFormats.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937} + Library + Properties + Destiny.FileFormats + Destiny.FileFormats + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Destiny.FileFormats/EndianIO.cs b/Destiny.FileFormats/EndianIO.cs new file mode 100644 index 0000000..5b38656 --- /dev/null +++ b/Destiny.FileFormats/EndianIO.cs @@ -0,0 +1,579 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Destiny.IO +{ + public enum EndianType + { + Big, + Little + } + + public class EndianIO + { + private EndianType _endianness; + private bool _requiresReverse; + + public EndianType Endianness + { + get { return this._endianness; } + set + { + this._endianness = value; + + if (IsLittleEndian) + this._requiresReverse = this.Endianness == EndianType.Big; + else + this._requiresReverse = this.Endianness == EndianType.Little; + } + } + + private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; + + private readonly byte[] _buffer = new byte[8]; + + public Stream Stream; + + public EndianIO(Stream stream, EndianType endianType) + { + this.Endianness = endianType; + this.Stream = stream; + } + + public EndianIO(EndianType endianType) + : this(new MemoryStream(), endianType) + { + + } + + public EndianIO(byte[] buffer, EndianType endianType) + : this(new MemoryStream(buffer), endianType) + { + + } + + public EndianIO(string fileName, EndianType endianType, FileMode fileMode = FileMode.Open, + FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.Read, int bufferSize = 0x08, bool isAsync = false) + : this(new FileStream(fileName, fileMode, fileAccess, fileShare, bufferSize, isAsync), endianType) + { + + } + + public bool EOF + { + get { return this.Stream.Position == this.Stream.Length; } + } + + public virtual long Length + { + get + { + return this.Stream.Length; + } + } + + public virtual long Position + { + get + { + return this.Stream.Position; + } + set + { + this.Stream.Position = value; + } + } + + public virtual void Seek(long position, SeekOrigin origin) + { + this.Stream.Seek(position, origin); + } + + public virtual void Flush() + { + this.Stream.Flush(); + } + + public virtual void SetLength(long value) + { + this.Stream.SetLength(value); + } + + public byte[] ToArray() + { + var ms = this.Stream as MemoryStream; + + if (ms != null) + return ms.ToArray(); + + if (this.Stream is FileStream) + { + this.Position = 0; + var buffer = new byte[this.Length]; + this.Read(buffer, 0, buffer.Length); + return buffer; + } + + return ((dynamic)this.Stream).ToArray(); + } + + public void Close() + { + this.Stream.Close(); + } + + public virtual int Read(byte[] buffer, int offset, int count) + { + return this.Stream.Read(buffer, offset, count); + } + + public virtual async Task ReadAsync(byte[] buffer, int offset, int count) + { + return await this.Stream.ReadAsync(buffer, offset, count); + } + + public byte[] ReadByteArray(long count) + { + var buffer = new byte[count]; + this.Read(buffer, 0, buffer.Length); + return buffer; + } + + public async Task ReadByteArrayAsync(long count) + { + var buffer = new byte[count]; + await this.ReadAsync(buffer, 0, buffer.Length); + return buffer; + } + + public byte[] ReadToEnd() + { + return this.ReadByteArray((int)(this.Length - this.Position)); + } + + public byte ReadByte() + { + this.Read(_buffer, 0, 1); + return _buffer[0]; + } + + public bool ReadBoolean() + { + return this.ReadByte() != 0x00; + } + + public short ReadInt16() + { + this.Read(_buffer, 0, 2); + if (this._requiresReverse) + return (short)(_buffer[1] | (_buffer[0] << 8)); + return (short)(_buffer[0] | (_buffer[1] << 8)); + } + + public short ReadInt16(long address) + { + Position = address; + return ReadInt16(); + } + + public int ReadInt32() + { + this.Read(_buffer, 0, 4); + if (this._requiresReverse) + return _buffer[3] | (_buffer[2] << 8) | (_buffer[1] << 16) | (_buffer[0] << 24); + return _buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24); + } + + public int ReadInt32(long address) + { + Position = address; + return ReadInt32(); + } + + public long ReadInt64() + { + this.Read(_buffer, 0, 8); + if (this._requiresReverse) + { + long n1 = (_buffer[3] | (_buffer[2] << 8) | (_buffer[1] << 16) | (_buffer[0] << 24)) & 0xFFFFFFFF; + long n2 = (_buffer[7] | (_buffer[6] << 8) | (_buffer[5] << 16) | (_buffer[4] << 24)) & 0xFFFFFFFF; + return n2 | (n1 << 32); + } + long n3 = (_buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24)) & 0xFFFFFFFF; + long n4 = (_buffer[4] | (_buffer[5] << 8) | (_buffer[6] << 16) | (_buffer[7] << 24)) & 0xFFFFFFFF; + return n3 | (n4 << 32); + } + + public long ReadInt64(long address) + { + Position = address; + return ReadInt64(); + } + + public ushort ReadUInt16() + { + return (ushort)this.ReadInt16(); + } + + public ushort ReadUInt16(long address) + { + Position = address; + return ReadUInt16(); + } + + public uint ReadUInt32() + { + return (uint)this.ReadInt32(); + } + + public uint ReadUInt32(long address) + { + Position = address; + return ReadUInt32(); + } + + public ulong ReadUInt64() + { + return (ulong)this.ReadInt64(); + } + + public ulong ReadUInt64(long address) + { + Position = address; + return ReadUInt64(); + } + + public uint ReadUInt24() + { + this.Read(_buffer, 0, 3); + if (this._requiresReverse) + return (uint)(_buffer[2] | (_buffer[1] << 8) | (_buffer[0] << 16)); + return (uint)(_buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16)); + } + + public uint ReadUInt24(long address) + { + Position = address; + return ReadUInt24(); + } + + public float ReadSingle() + { + this.Read(_buffer, 0, 4); + if (this._requiresReverse) + { + byte t = _buffer[0]; + _buffer[0] = _buffer[3]; + _buffer[3] = t; + t = _buffer[1]; + _buffer[1] = _buffer[2]; + _buffer[2] = t; + } + return BitConverter.ToSingle(_buffer, 0); + } + + public float ReadSingle(long address) + { + Position = address; + return ReadSingle(); + } + + public double ReadDouble() + { + return BitConverter.Int64BitsToDouble(this.ReadInt64()); + } + + public double ReadDouble(long address) + { + Position = address; + return ReadDouble(); + } + + public string ReadAsciiString(int length) + { + return Encoding.ASCII.GetString(this.ReadByteArray(length)); + } + + public string ReadAsciiString(long position, int length) + { + Position = position; + return Encoding.ASCII.GetString(this.ReadByteArray(length)); + } + + public string ReadNullTerminatedAsciiString() + { + int stringLength = 0; + long startPosition = this.Position; + while (this.ReadByte() != 0x00) + stringLength++; + this.Position = startPosition; + var str = ReadAsciiString(stringLength); + this.Position++; + return str; + } + + public string ReadUnicodeString(int length) + { + return Encoding.BigEndianUnicode.GetString(this.ReadByteArray(length * 2)); + } + + public string ReadNullTerminatedUnicodeString() + { + int stringLength = 0; + long startPosition = this.Position; + while (this.ReadUInt16() != 0x00) + stringLength++; + this.Position = startPosition; + var str = ReadUnicodeString(stringLength); + this.Position += 0x02; + return str; + } + + [Obfuscation] + public void WriteByte(byte value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[3]; + buffer[3] = t; + t = buffer[1]; + buffer[1] = buffer[2]; + buffer[2] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public virtual void Write(byte[] buffer, int offset, int count) + { + this.Stream.Write(buffer, offset, count); + } + + [Obfuscation] + public virtual async Task WriteAsync(byte[] buffer, int offset, int count) + { + await this.Stream.WriteAsync(buffer, offset, count); + } + + [Obfuscation] + public void Write(byte[] buffer) + { + this.Write(buffer, 0, buffer.Length); + } + + [Obfuscation] + public async void WriteAsync(byte[] buffer) + { + await this.WriteAsync(buffer, 0, buffer.Length); + } + + [Obfuscation] + public void Write(byte value) + { + this.Write(new[] { value }); + } + + [Obfuscation] + public void Write(bool value) + { + this.Write(value ? (byte)1 : (byte)0); + } + + [Obfuscation] + public void Write(short value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[1]; + buffer[1] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(int value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[3]; + buffer[3] = t; + t = buffer[1]; + buffer[1] = buffer[2]; + buffer[2] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(long value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[7]; + buffer[7] = t; + t = buffer[1]; + buffer[1] = buffer[6]; + buffer[6] = t; + t = buffer[2]; + buffer[2] = buffer[5]; + buffer[5] = t; + t = buffer[3]; + buffer[3] = buffer[4]; + buffer[4] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(ushort value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[1]; + buffer[1] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[3]; + buffer[3] = t; + t = buffer[1]; + buffer[1] = buffer[2]; + buffer[2] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(ulong value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[7]; + buffer[7] = t; + t = buffer[1]; + buffer[1] = buffer[6]; + buffer[6] = t; + t = buffer[2]; + buffer[2] = buffer[5]; + buffer[5] = t; + t = buffer[3]; + buffer[3] = buffer[4]; + buffer[4] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(float value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[3]; + buffer[3] = t; + t = buffer[1]; + buffer[1] = buffer[2]; + buffer[2] = t; + } + this.Write(buffer); + } + + [Obfuscation] + public void Write(double value) + { + byte[] buffer = BitConverter.GetBytes(value); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[7]; + buffer[7] = t; + t = buffer[1]; + buffer[1] = buffer[6]; + buffer[6] = t; + t = buffer[2]; + buffer[2] = buffer[5]; + buffer[5] = t; + t = buffer[3]; + buffer[3] = buffer[4]; + buffer[4] = t; + } + this.Write(buffer); + } + + public void WriteUInt24(uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + Array.Resize(ref buffer, 3); + if (this._requiresReverse) + { + byte t = buffer[0]; + buffer[0] = buffer[2]; + buffer[2] = t; + } + this.Write(buffer); + } + + public void WriteAsciiString(string value) + { + this.Write(Encoding.ASCII.GetBytes(value)); + } + + public void WriteAsciiString(string value, int length) + { + var buffer = new byte[length]; + byte[] stringBuffer = Encoding.ASCII.GetBytes(value); + Array.Copy(stringBuffer, buffer, (stringBuffer.Length > length) ? length : stringBuffer.Length); + this.Write(buffer); + } + + public void WriteNullTerminatedAsciiString(string value) + { + this.WriteAsciiString(value); + this.Write((byte)0x00); + } + + public void WriteUnicodeString(string value) + { + this.Write(this.Endianness == EndianType.Big ? Encoding.BigEndianUnicode.GetBytes(value) : Encoding.Unicode.GetBytes(value)); + } + + public void WriteUnicodeString(string value, int length) + { + length *= 2; + var buffer = new byte[length]; + byte[] stringBuffer = this.Endianness == EndianType.Big ? Encoding.BigEndianUnicode.GetBytes(value) : Encoding.Unicode.GetBytes(value); + Array.Copy(stringBuffer, buffer, (stringBuffer.Length > length) ? length : stringBuffer.Length); + this.Write(buffer); + } + + public void WriteNullTerminatedUnicodeString(string value) + { + this.WriteUnicodeString(value); + this.Write((short)0x00); + } + } +} \ No newline at end of file diff --git a/Destiny.FileFormats/Package.cs b/Destiny.FileFormats/Package.cs new file mode 100644 index 0000000..2aa4680 --- /dev/null +++ b/Destiny.FileFormats/Package.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Destiny.IO; + +namespace Destiny.FileFormats +{ + public class Package + { + public Package(EndianIO io) + { + short BaseVersion = io.ReadInt16(); + PackagePlatform Platform = (PackagePlatform)io.ReadInt16(); + ushort EntryCount = io.ReadUInt16(); // max = 0xFFFF + +#if DEBUG + Verify(io); +#endif + } + + /// + /// Function verifies the hashes stored in the header of the pkg file. (Does not verify the RSA Signature at POSITION: 0x800) + /// + private void Verify(EndianIO io) + { + byte[] hash; + + //verify the resource entry table + io.Position = 0xB4; + uint entryCount = io.ReadUInt32(); + uint entryTableLocation = io.ReadUInt32(); + hash = io.ReadByteArray(0x14); + + io.Position = entryTableLocation; + if (!Security.ArrayEquals(Security.SHA1(io.ReadByteArray(entryCount * 0x10)), hash)) + throw new Exception("Failed to verify the entry table hash in the pkg file."); + + //verify the data block table + io.Position = 0xD0; + uint blockCount = io.ReadUInt32(); + uint blockTableLocation = io.ReadUInt32(); + hash = io.ReadByteArray(0x14); + + io.Position = blockTableLocation; + if (!Security.ArrayEquals(Security.SHA1(io.ReadByteArray(blockCount * 0x20)), hash)) + throw new Exception("Failed to verify the block table hash in the pkg file."); + + // verify the symbol table + io.Position = 0xEC; + uint symbolCount = io.ReadUInt32(); + uint symbolTableLocation = io.ReadUInt32(); + hash = io.ReadByteArray(0x14); + + io.Position = symbolTableLocation; + if (!Security.ArrayEquals(Security.SHA1(io.ReadByteArray(symbolCount * 0x44)), hash)) + throw new Exception("Failed to verify the symbol table hash in the pkg file."); + + } + } +} diff --git a/Destiny.FileFormats/PackagePlatform.cs b/Destiny.FileFormats/PackagePlatform.cs new file mode 100644 index 0000000..9a3714b --- /dev/null +++ b/Destiny.FileFormats/PackagePlatform.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Destiny.FileFormats +{ + public enum PackagePlatform + { + Xbox360 = 0x03, + Playstation3 = 0x04 + } +} diff --git a/Destiny.FileFormats/Properties/AssemblyInfo.cs b/Destiny.FileFormats/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..470d0d8 --- /dev/null +++ b/Destiny.FileFormats/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Destiny.FileFormats")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Destiny.FileFormats")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a7a89795-a0cd-413c-af2f-683820cd9f62")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Destiny.FileFormats/ResourceEntry.cs b/Destiny.FileFormats/ResourceEntry.cs new file mode 100644 index 0000000..464ffa4 --- /dev/null +++ b/Destiny.FileFormats/ResourceEntry.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Destiny.IO; + +namespace Destiny.FileFormats +{ + /// + /// Offset for the location of the Resource Entry Table is located at 0xB8 in the *.pkg files + /// + public class ResourceEntry + { + public uint Unknown1; + public uint Unknown2; + + public ulong ResourceInformation; + + public ResourceEntry(EndianIO io) + { + Unknown1 = io.ReadUInt32(); + Unknown2 = io.ReadUInt32(); + + ResourceInformation = io.ReadUInt64(); + } + + /// + /// Calculates the position of the resource in the fully decompressed file + /// + /// Position of resource in file. + public uint GetRealOffset() + { + uint blockIndex = (uint)((ResourceInformation & 0x3FF) * 0x40000); + return (uint)((ResourceInformation >> 0xE) << 0x4) & 0x3FFF0; + } + /// + /// Gets the length of the resource data. + /// + /// Length of resource entry item. + public uint GetLength() + { + return (uint)((ResourceInformation >> 0x1C) & 0x3FFFFFFF); + } + } +} diff --git a/Destiny.FileFormats/Security.cs b/Destiny.FileFormats/Security.cs new file mode 100644 index 0000000..9c81207 --- /dev/null +++ b/Destiny.FileFormats/Security.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Destiny.FileFormats +{ + public static class Security + { + public static byte[] SHA1(byte[] Input) + { + return System.Security.Cryptography.SHA1.Create().ComputeHash(Input); + } + public static bool ArrayEquals(T[] a, T[] b) + { + if (a.Length != b.Length) + return false; + + return !a.Where((t, i) => !t.Equals(b[i])).Any(); + } + } +} diff --git a/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.dll b/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.dll new file mode 100644 index 0000000..ca1efcc Binary files /dev/null and b/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.dll differ diff --git a/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.pdb b/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.pdb new file mode 100644 index 0000000..a1334a4 Binary files /dev/null and b/Destiny.FileFormats/bin/Debug/Destiny.FileFormats.pdb differ diff --git a/DestinyPKGTool.sln b/DestinyPKGTool.sln index ad868b9..5a521c2 100644 --- a/DestinyPKGTool.sln +++ b/DestinyPKGTool.sln @@ -2,6 +2,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DestinyPKGTool", "DestinyPKGTool\DestinyPKGTool.csproj", "{2EC8BCE4-8905-4AE0-A2C4-632BDC734C13}" + ProjectSection(ProjectDependencies) = postProject + {1632CB64-116B-4E0D-92E7-4749EF3D3937} = {1632CB64-116B-4E0D-92E7-4749EF3D3937} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Destiny.FileFormats", "Destiny.FileFormats\Destiny.FileFormats.csproj", "{1632CB64-116B-4E0D-92E7-4749EF3D3937}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,6 +24,12 @@ Global {2EC8BCE4-8905-4AE0-A2C4-632BDC734C13}.Release|Any CPU.Build.0 = Release|Any CPU {2EC8BCE4-8905-4AE0-A2C4-632BDC734C13}.Release|x86.ActiveCfg = Release|x86 {2EC8BCE4-8905-4AE0-A2C4-632BDC734C13}.Release|x86.Build.0 = Release|x86 + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Debug|x86.ActiveCfg = Debug|Any CPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Release|Any CPU.Build.0 = Release|Any CPU + {1632CB64-116B-4E0D-92E7-4749EF3D3937}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DestinyPKGTool/DestinyPKGTool.csproj b/DestinyPKGTool/DestinyPKGTool.csproj index 7343ced..ca2e708 100644 --- a/DestinyPKGTool/DestinyPKGTool.csproj +++ b/DestinyPKGTool/DestinyPKGTool.csproj @@ -76,6 +76,12 @@ + + + {1632cb64-116b-4e0d-92e7-4749ef3d3937} + Destiny.FileFormats + +