/// <summary> /// Writes the header intro to an output stream. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="offset">The absolute offset in the stream to write at.</param> /// <param name="intro">The intro to write.</param> /// <param name="p">Progress info.</param> /// <returns>An async task.</returns> internal async Task WriteHeaderIntroAsync(Stream stream, UInt64 offset, NefsHeaderIntro intro, NefsProgress p) { using (var t = p.BeginTask(1.0f)) { await FileData.WriteDataAsync(stream, offset, intro, NefsVersion.Version200, p); } }
private bool ValidateHash(Stream stream, ulong offset, NefsHeaderIntro intro) { var dataToHashSize = intro.IsEncrypted ? intro.HeaderSize - 0x22 : intro.HeaderSize - 0x20; byte[] dataToHash = new byte[dataToHashSize]; stream.Seek((long)offset, SeekOrigin.Begin); stream.Read(dataToHash, 0, 4); stream.Seek(0x24, SeekOrigin.Begin); if (!intro.IsEncrypted) { stream.Read(dataToHash, 4, (int)intro.HeaderSize - 0x24); } else { stream.Read(dataToHash, 4, 0x5A); stream.Seek(0x80, SeekOrigin.Begin); stream.Read(dataToHash, 0x5E, (int)intro.HeaderSize - 0x80); } using (var hash = SHA256.Create()) { byte[] hashOut = hash.ComputeHash(dataToHash); return(hashOut.SequenceEqual(intro.ExpectedHash)); } }
public async Task WriterHeaderIntroAsync_ValidData_Written() { var aes = new byte[] { 0xE5, 0x69, 0x65, 0x23, 0xAB, 0xF5, 0x43, 0xFF, 0xC9, 0xDF, 0xB2, 0x2C, 0x64, 0xD1, 0x11, 0x46, 0xE5, 0x9B, 0xAC, 0xC8, 0xAC, 0x8B, 0xA4, 0x15, 0x9E, 0xE0, 0xE2, 0xBB, 0x54, 0x09, 0x0A, 0x6C, 0x99, 0x30, 0xC6, 0xC1, 0x84, 0x3C, 0x90, 0x29, 0x75, 0xB2, 0xB5, 0x5E, 0x3B, 0x7A, 0x06, 0x3D, 0xE1, 0xD2, 0x1F, 0x6F, 0xB7, 0xDC, 0x57, 0x5A, 0xC4, 0x4F, 0x84, 0xCB, 0x13, 0x87, 0xAB, 0xBF }; var hash = new byte[] { 0xCB, 0x13, 0x87, 0xAB, 0xBF, 0xD5, 0x45, 0x93, 0x34, 0x0A, 0x50, 0xC1, 0xA8, 0x0A, 0x82, 0x53, 0xF9, 0xD5, 0x46, 0xDA, 0x24, 0xDA, 0xA4, 0xDA, 0x82, 0xEA, 0x9A, 0xB5, 0xBC, 0xD8, 0x6B, 0xFC }; var intro = new NefsHeaderIntro(); intro.Data0x24_AesKeyHexString.Value = aes; intro.Data0x04_ExpectedHash.Value = hash; intro.Data0x64_HeaderSize.Value = 12345; intro.Data0x6c_NumberOfItems.Value = 9876; intro.Data0x68_NefsVersion.Value = 101; intro.Data0x70_UnknownZlib.Value = 202; intro.Data0x78_Unknown.Value = 303; /* * Write */ var writer = this.CreateWriter(); byte[] buffer; using (var ms = new MemoryStream()) { await writer.WriteHeaderIntroAsync(ms, 0U, intro, new NefsProgress()); buffer = ms.ToArray(); } /* * Verify */ // Magic # Assert.Equal(0x5346654EU, BitConverter.ToUInt32(buffer, 0)); // Expected hash this.VerifyArrraySlice(hash, 0, buffer, 0x04, 0x20); // AES key this.VerifyArrraySlice(aes, 0, buffer, 0x24, 0x40); // Header size Assert.Equal(12345, BitConverter.ToInt32(buffer, 0x64)); // 0x68 unknwon Assert.Equal(101, BitConverter.ToInt32(buffer, 0x68)); // Number of items Assert.Equal(9876, BitConverter.ToInt32(buffer, 0x6C)); // 0x70 unknown (8 bytes) Assert.Equal(202, BitConverter.ToInt64(buffer, 0x70)); // 0x78 unknwon (8 bytes) Assert.Equal(303, BitConverter.ToInt64(buffer, 0x78)); }
/// <summary> /// Creates a test archive. Does not write an archive to disk. Just creates a <see /// cref="NefsArchive"/> object. /// </summary> /// <param name="filePath">The file path to use for the archive.</param> /// <returns>A <see cref="NefsArchive"/>.</returns> public static NefsArchive Create(string filePath) { var items = new NefsItemList(filePath); Assert.Empty(items.EnumerateById()); Assert.Empty(items.EnumerateDepthFirstByName()); var intro = new NefsHeaderIntro(); intro.Data0x6c_NumberOfItems.Value = (uint)items.Count; var toc = new Nefs20HeaderIntroToc(); var header = new Nefs20Header(intro, toc, items); return(new NefsArchive(header, items)); }
/// <summary> /// Creates a test archive. Does not write an archive to disk. Just creates a <see /// cref="NefsArchive"/> object. /// </summary> /// <param name="filePath">The file path to use for the archive.</param> /// <returns>A <see cref="NefsArchive"/>.</returns> public static NefsArchive Create(string filePath) { var items = new NefsItemList(filePath); var aesString = "44927647059D3D73CDCC8D4C6E808538CAD7622D076A507E16C43A8DD8E3B5AB"; var file1Attributes = new NefsItemAttributes(v20IsZlib: true); var file1Chunks = NefsDataChunk.CreateChunkList(File1ChunkSizes, TestHelpers.TestTransform); var file1DataSource = new NefsItemListDataSource(items, File1Offset, new NefsItemSize(File1ExtractedSize, file1Chunks)); var file1 = new NefsItem(File1Guid, new NefsItemId(File1ItemId), File1Name, new NefsItemId(File1DirectoryId), file1DataSource, TestHelpers.TestTransform, file1Attributes); items.Add(file1); var dir1Attributes = new NefsItemAttributes(isDirectory: true); var dir1DataSource = new NefsEmptyDataSource(); var dir1 = new NefsItem(Dir1Guid, new NefsItemId(Dir1ItemId), Dir1Name, new NefsItemId(Dir1DirectoryId), dir1DataSource, null, dir1Attributes); items.Add(dir1); var file2Attributes = new NefsItemAttributes(v20IsZlib: true); var file2Chunks = NefsDataChunk.CreateChunkList(File2ChunkSizes, TestHelpers.TestTransform); var file2DataSource = new NefsItemListDataSource(items, File2Offset, new NefsItemSize(File2ExtractedSize, file2Chunks)); var file2 = new NefsItem(File2Guid, new NefsItemId(File2ItemId), File2Name, new NefsItemId(File2DirectoryId), file2DataSource, TestHelpers.TestTransform, file2Attributes); items.Add(file2); var file3Attributes = new NefsItemAttributes(v20IsZlib: true); var file3Transform = new NefsDataTransform(File3ExtractedSize); var file3Chunks = NefsDataChunk.CreateChunkList(File3ChunkSizes, file3Transform); var file3DataSource = new NefsItemListDataSource(items, File3Offset, new NefsItemSize(File3ExtractedSize, file3Chunks)); var file3 = new NefsItem(File3Guid, new NefsItemId(File3ItemId), File3Name, new NefsItemId(File3DirectoryId), file3DataSource, file3Transform, file3Attributes); items.Add(file3); Assert.Equal((int)NumItems, items.Count); var intro = new NefsHeaderIntro(); intro.Data0x6c_NumberOfItems.Value = (uint)items.Count; intro.Data0x24_AesKeyHexString.Value = Encoding.ASCII.GetBytes(aesString); var toc = new Nefs20HeaderIntroToc(); var header = new Nefs20Header(intro, toc, items); return(new NefsArchive(header, items)); }
/// <summary> /// Reads the header from an input stream. /// </summary> /// <param name="originalStream">The stream to read from.</param> /// <param name="offset">The offset to the header from the beginning of the stream.</param> /// <param name="part6Offset"> /// The offset to the start of part 6 data from the beginning of the stream. /// </param> /// <param name="p">Progress info.</param> /// <returns>The loaded header.</returns> internal async Task <INefsHeader> ReadHeaderAsync(Stream originalStream, ulong offset, ulong part6Offset, NefsProgress p) { Stream stream; Stream part6Stream; INefsHeader header = null; NefsHeaderIntro intro = null; using (p.BeginTask(0.2f, "Reading header intro")) { // Decrypt header if needed (intro, stream) = await this.ReadHeaderIntroAsync(originalStream, offset, p); } // For now, assume that if the header is encrypted, then the part 6 data is not // separated. We've only seen encrypted headers in some nefs 2.0 archives (i.e., DLC content). part6Stream = intro.IsEncrypted ? stream : originalStream; using (p.BeginTask(0.8f)) { if (intro.NefsVersion == 0x20000) { // 2.0.0 Log.LogInformation("Detected NeFS version 2.0."); header = await this.Read20HeaderAsync(stream, 0, part6Stream, part6Offset, intro, p); } else if (intro.NefsVersion == 0x10600) { // 1.6.0 Log.LogInformation("Detected NeFS version 1.6."); header = await this.Read16HeaderAsync(stream, 0, part6Stream, part6Offset, intro, p); } else { Log.LogInformation($"Detected unkown NeFS version {intro.NefsVersion}."); header = await this.Read20HeaderAsync(stream, 0, part6Stream, part6Offset, intro, p); } } // The header stream must be disposed stream.Dispose(); return(header); }
/// <summary> /// Creates a test archive. Does not write an archive to disk. Just creates a <see /// cref="NefsArchive"/> object. /// </summary> /// <param name="filePath">The file path to use for the archive.</param> /// <returns>A <see cref="NefsArchive"/>.</returns> public static NefsArchive Create(string filePath) { var items = new NefsItemList(filePath); var file1Attributes = new NefsItemAttributes(v20IsZlib: true); var file1Chunks = NefsDataChunk.CreateChunkList(File1ChunkSizes, TestHelpers.TestTransform); var file1DataSource = new NefsItemListDataSource(items, File1Offset, new NefsItemSize(File1ExtractedSize, file1Chunks)); var file1 = new NefsItem(File1Guid, new NefsItemId(File1ItemId), File1Name, new NefsItemId(File1DirectoryId), file1DataSource, TestHelpers.TestTransform, file1Attributes); items.Add(file1); var dir1Attributes = new NefsItemAttributes(isDirectory: true); var dir1DataSource = new NefsEmptyDataSource(); var dir1 = new NefsItem(Dir1Guid, new NefsItemId(Dir1ItemId), Dir1Name, new NefsItemId(Dir1DirectoryId), dir1DataSource, null, dir1Attributes); items.Add(dir1); var file2Attributes = new NefsItemAttributes(v20IsZlib: true); var file2Chunks = NefsDataChunk.CreateChunkList(File2ChunkSizes, TestHelpers.TestTransform); var file2DataSource = new NefsItemListDataSource(items, File2Offset, new NefsItemSize(File2ExtractedSize, file2Chunks)); var file2 = new NefsItem(File2Guid, new NefsItemId(File2ItemId), File2Name, new NefsItemId(File2DirectoryId), file2DataSource, TestHelpers.TestTransform, file2Attributes); items.Add(file2); var file3Attributes = new NefsItemAttributes(v20IsZlib: true); var file3Chunks = NefsDataChunk.CreateChunkList(File3ChunkSizes, TestHelpers.TestTransform); var file3DataSource = new NefsItemListDataSource(items, File3Offset, new NefsItemSize(File3ExtractedSize, file3Chunks)); var file3 = new NefsItem(File3Guid, new NefsItemId(File3ItemId), File3Name, new NefsItemId(File3DirectoryId), file3DataSource, TestHelpers.TestTransform, file3Attributes); items.Add(file3); Assert.Equal((int)NumItems, items.Count); var intro = new NefsHeaderIntro(); intro.Data0x6c_NumberOfItems.Value = (uint)items.Count; var toc = new Nefs20HeaderIntroToc(); var header = new Nefs20Header(intro, toc, items); return(new NefsArchive(header, items)); }
/// <summary> /// Creates a test archive. Does not write an archive to disk. Just creates a <see /// cref="NefsArchive"/> object. /// </summary> /// <param name="filePath">The file path to use for the archive.</param> /// <returns>A <see cref="NefsArchive"/>.</returns> public static NefsArchive Create(string filePath) { var items = new NefsItemList(filePath); var file1DataSource = new NefsItemListDataSource(items, File1Offset, new NefsItemSize(File1ExtractedSize, File1ChunkSizes)); var file1 = new NefsItem(new NefsItemId(File1ItemId), File1Name, new NefsItemId(File1DirectoryId), NefsItemType.File, file1DataSource, TestHelpers.CreateUnknownData()); items.Add(file1); var dir1DataSource = new NefsEmptyDataSource(); var dir1 = new NefsItem(new NefsItemId(Dir1ItemId), Dir1Name, new NefsItemId(Dir1DirectoryId), NefsItemType.Directory, dir1DataSource, TestHelpers.CreateUnknownData()); items.Add(dir1); var file2DataSource = new NefsItemListDataSource(items, File2Offset, new NefsItemSize(File2ExtractedSize, File2ChunkSizes)); var file2 = new NefsItem(new NefsItemId(File2ItemId), File2Name, new NefsItemId(File2DirectoryId), NefsItemType.File, file2DataSource, TestHelpers.CreateUnknownData()); items.Add(file2); var file3DataSource = new NefsItemListDataSource(items, File3Offset, new NefsItemSize(File3ExtractedSize, File3ChunkSizes)); var file3 = new NefsItem(new NefsItemId(File3ItemId), File3Name, new NefsItemId(File3DirectoryId), NefsItemType.File, file3DataSource, TestHelpers.CreateUnknownData()); items.Add(file3); Assert.Equal((int)NumItems, items.Count); var intro = new NefsHeaderIntro(); intro.Data0x6c_NumberOfItems.Value = (uint)items.Count; var toc = new NefsHeaderIntroToc(); var header = new NefsHeader(intro, toc, items); return(new NefsArchive(header, items)); }
/// <summary> /// Creates a <see cref="NefsArchive"/> to be used for testing. /// </summary> /// <param name="filePath">The file path to associate with the archive.</param> /// <returns>An archive object.</returns> /// <remarks><![CDATA[ Test archive items: /file1 /dir1 /dir1/file2 ]]></remarks> internal static NefsArchive CreateTestArchive(string filePath) { var items = new NefsItemList(filePath); var transform = new NefsDataTransform(50, true); var file1Attributes = new NefsItemAttributes(v20IsZlib: true); var file1Chunks = NefsDataChunk.CreateChunkList(new List <UInt32> { 2, 3, 4 }, transform); var file1DataSource = new NefsItemListDataSource(items, 100, new NefsItemSize(20, file1Chunks)); var file1 = new NefsItem(Guid.NewGuid(), new NefsItemId(0), "file1", new NefsItemId(0), file1DataSource, transform, file1Attributes); items.Add(file1); var dir1Attributes = new NefsItemAttributes(isDirectory: true); var dir1DataSource = new NefsEmptyDataSource(); var dir1 = new NefsItem(Guid.NewGuid(), new NefsItemId(1), "dir1", new NefsItemId(1), dir1DataSource, null, dir1Attributes); items.Add(dir1); var file2Attributes = new NefsItemAttributes(v20IsZlib: true); var file2Chunks = NefsDataChunk.CreateChunkList(new List <UInt32> { 5, 6, 7 }, transform); var file2DataSource = new NefsItemListDataSource(items, 104, new NefsItemSize(15, file2Chunks)); var file2 = new NefsItem(Guid.NewGuid(), new NefsItemId(2), "file2", dir1.Id, file2DataSource, transform, file2Attributes); items.Add(file2); var intro = new NefsHeaderIntro(); var toc = new Nefs20HeaderIntroToc(); var header = new Nefs20Header(intro, toc, items); return(new NefsArchive(header, items)); }
/// <summary> /// Reads the header intro from an input stream. Returns a new stream that contains the /// header data. This stream must be disposed by the caller. If the header is encrypted, the /// header data is decrypted before being placed in the new stream. /// </summary> /// <param name="stream">The stream to read from.</param> /// <param name="offset">The offset to the header intro from the beginning of the stream.</param> /// <param name="p">Progress info.</param> /// <returns>The loaded header intro and the stream to use for the rest of the header.</returns> internal async Task <(NefsHeaderIntro Intro, Stream HeaderStream)> ReadHeaderIntroAsync( Stream stream, ulong offset, NefsProgress p) { // The decrypted stream will need to be disposed by the caller var decryptedStream = new MemoryStream(); NefsHeaderIntro intro; // Read magic number (first four bytes) stream.Seek((long)offset, SeekOrigin.Begin); var magicNum = new UInt32Type(0); await magicNum.ReadAsync(stream, offset, p); // Reset stream position stream.Seek((long)offset, SeekOrigin.Begin); // Check magic number if (magicNum.Value == NefsHeaderIntro.NefsMagicNumber) { // This is a non-encrypted NeFS header intro = new NefsHeaderIntro(); await FileData.ReadDataAsync(stream, offset, intro, NefsVersion.Version200, p); // Copy the entire header to the decrypted stream (nothing to decrypt) stream.Seek((long)offset, SeekOrigin.Begin); await stream.CopyPartialAsync(decryptedStream, intro.HeaderSize, p.CancellationToken); } else { // Magic number is incorrect, assume file is encrpyted Log.LogInformation("Header magic number mismatch, assuming header is encrypted."); // Encrypted headers: // - Headers are "encrypted" in a two-step process. RSA-1024. No padding is used. // - First 0x80 bytes are signed with an RSA private key (data -> decrypt -> // scrambled data). // - Must use an RSA 1024-bit public key to unscramble the data (scrambled data -> // encrypt -> data). // - For DiRT Rally 2 this public key is stored in the main executable. byte[] encryptedHeader = new byte[NefsHeaderIntro.Size + 1]; // TODO : Why the +1? await stream.ReadAsync(encryptedHeader, 0, (int)NefsHeaderIntro.Size, p.CancellationToken); encryptedHeader[NefsHeaderIntro.Size] = 0; // Use big integers instead of RSA since the c# implementation forces the use of padding. var n = new BigInteger(this.RsaPublicKey); var e = new BigInteger(this.RsaExponent); var m = new BigInteger(encryptedHeader); // Decrypt the header intro byte[] decrypted = BigInteger.ModPow(m, e, n).ToByteArray(); decryptedStream.Write(decrypted, 0, decrypted.Length); // Fill any leftover space with zeros if (decrypted.Length != NefsHeaderIntro.Size) { for (int i = 0; i < (NefsHeaderIntro.Size - decrypted.Length); i++) { decryptedStream.WriteByte(0); } } // Read header intro data from decrypted stream intro = new NefsHeaderIntro(isEncrpyted: true); await FileData.ReadDataAsync(decryptedStream, 0, intro, NefsVersion.Version200, p); // The rest of the header is encrypted using AES-256, decrypt using the key from the // header intro byte[] key = intro.GetAesKey(); var headerSize = intro.HeaderSize; // Decrypt the rest of the header using (var rijAlg = new RijndaelManaged()) { rijAlg.KeySize = 256; rijAlg.Key = key; rijAlg.Mode = CipherMode.ECB; rijAlg.BlockSize = 128; rijAlg.Padding = PaddingMode.Zeros; var decryptor = rijAlg.CreateDecryptor(); decryptedStream.Seek(0, SeekOrigin.End); // Decrypt the data - make sure to leave open the base stream using (var cryptoStream = new CryptoStream(stream, decryptor, CryptoStreamMode.Read, true)) { // Decrypt data from input stream and copy to the decrypted stream await cryptoStream.CopyPartialAsync(decryptedStream, headerSize, p.CancellationToken); } } } return(intro, decryptedStream); }
/// <summary> /// Reads a version 2.0 header from an input stream. /// </summary> /// <param name="stream">The stream to read from.</param> /// <param name="offset">The offset to the header from the beginning of the stream.</param> /// <param name="part6Stream">The stream that contains part 6/7 data.</param> /// <param name="part6Offset">The offset to the start of part 6/7 data.</param> /// <param name="intro">The pre-parsed header intro.</param> /// <param name="p">Progress info.</param> /// <returns>The loaded header.</returns> internal async Task <Nefs20Header> Read20HeaderAsync( Stream stream, ulong offset, Stream part6Stream, ulong part6Offset, NefsHeaderIntro intro, NefsProgress p) { Nefs20HeaderIntroToc toc = null; NefsHeaderPart1 part1 = null; NefsHeaderPart2 part2 = null; NefsHeaderPart3 part3 = null; Nefs20HeaderPart4 part4 = null; NefsHeaderPart5 part5 = null; Nefs20HeaderPart6 part6 = null; NefsHeaderPart7 part7 = null; NefsHeaderPart8 part8 = null; // Calc weight of each task (8 parts + table of contents) var weight = 1.0f / 10.0f; using (p.BeginTask(weight, "Reading header intro table of contents")) { toc = await this.Read20HeaderIntroTocAsync(stream, Nefs20HeaderIntroToc.Offset, p); } using (p.BeginTask(weight, "Reading header part 1")) { part1 = await this.ReadHeaderPart1Async(stream, toc.OffsetToPart1, toc.Part1Size, p); } using (p.BeginTask(weight, "Reading header part 2")) { part2 = await this.ReadHeaderPart2Async(stream, toc.OffsetToPart2, toc.Part2Size, p); } using (p.BeginTask(weight, "Reading header part 3")) { part3 = await this.ReadHeaderPart3Async(stream, toc.OffsetToPart3, toc.Part3Size, p); } using (p.BeginTask(weight, "Reading header part 4")) { part4 = await this.Read20HeaderPart4Async(stream, toc.OffsetToPart4, toc.Part4Size, part1, p); } using (p.BeginTask(weight, "Reading header part 5")) { part5 = await this.ReadHeaderPart5Async(stream, toc.OffsetToPart5, NefsHeaderPart5.Size, p); } using (p.BeginTask(weight, "Reading header part 6")) { part6 = await this.Read20HeaderPart6Async(part6Stream, (uint)part6Offset + toc.OffsetToPart6, part1, p); } using (p.BeginTask(weight, "Reading header part 7")) { var numEntries = (uint)part2.EntriesByIndex.Count; part7 = await this.ReadHeaderPart7Async(part6Stream, (uint)part6Offset + toc.OffsetToPart7, numEntries, p); } using (p.BeginTask(weight, "Reading header part 8")) { var part8Size = intro.HeaderSize - toc.OffsetToPart8; part8 = await this.ReadHeaderPart8Async(stream, toc.OffsetToPart8, part8Size, p); } // Validate header hash if (!this.ValidateHash(stream, offset, intro)) { Log.LogWarning("Header hash does not match expected value."); } // The header stream must be disposed stream.Dispose(); return(new Nefs20Header(intro, toc, part1, part2, part3, part4, part5, part6, part7, part8)); }
/// <summary> /// Reads the header from an input stream. /// </summary> /// <param name="originalStream">The stream to read from.</param> /// <param name="offset">The offset to the header from the beginning of the stream.</param> /// <param name="p">Progress info.</param> /// <returns>The loaded header.</returns> internal async Task <NefsHeader> ReadHeaderAsync(Stream originalStream, ulong offset, NefsProgress p) { Stream stream; NefsHeaderIntro intro = null; NefsHeaderIntroToc toc = null; NefsHeaderPart1 part1 = null; NefsHeaderPart2 part2 = null; NefsHeaderPart3 part3 = null; NefsHeaderPart4 part4 = null; NefsHeaderPart5 part5 = null; NefsHeaderPart6 part6 = null; NefsHeaderPart7 part7 = null; NefsHeaderPart8 part8 = null; // Calc weight of each task (8 parts + intro + table of contents) var weight = 1.0f / 10.0f; using (p.BeginTask(weight, "Reading header intro")) { // Decrypt header if needed (intro, stream) = await this.ReadHeaderIntroAsync(originalStream, offset, p); } using (p.BeginTask(weight, "Reading header intro table of contents")) { toc = await this.ReadHeaderIntroTocAsync(stream, NefsHeaderIntroToc.Offset, p); } using (p.BeginTask(weight, "Reading header part 1")) { part1 = await this.ReadHeaderPart1Async(stream, toc.OffsetToPart1, toc.Part1Size, p); } using (p.BeginTask(weight, "Reading header part 2")) { part2 = await this.ReadHeaderPart2Async(stream, toc.OffsetToPart2, toc.Part2Size, p); } using (p.BeginTask(weight, "Reading header part 3")) { part3 = await this.ReadHeaderPart3Async(stream, toc.OffsetToPart3, toc.Part3Size, p); } using (p.BeginTask(weight, "Reading header part 4")) { part4 = await this.ReadHeaderPart4Async(stream, toc.OffsetToPart4, toc.Part4Size, part1, part2, p); } using (p.BeginTask(weight, "Reading header part 5")) { part5 = await this.ReadHeaderPart5Async(stream, toc.OffsetToPart5, toc.Part5Size, p); } using (p.BeginTask(weight, "Reading header part 6")) { if (toc.OffsetToPart6 == 0) { // game.dat files don't have part 6 Log.LogDebug("Archive does not have header part 6."); part6 = new NefsHeaderPart6(new List <NefsHeaderPart6Entry>()); } else { part6 = await this.ReadHeaderPart6Async(stream, toc.OffsetToPart6, toc.Part6Size, part2, p); } } using (p.BeginTask(weight, "Reading header part 7")) { if (toc.OffsetToPart6 == 0) { // game.dat files don't have part 7. Still checking if part 6 offset is 0. For // some reason, the part 7 offset still has a value, but doesn't appear to be a // correct one, so skipping part 7 as well Log.LogDebug("Archive does not have header part 7."); part7 = new NefsHeaderPart7(new List <NefsHeaderPart7Entry>()); } else { part7 = await this.ReadHeaderPart7Async(stream, toc.OffsetToPart7, toc.Part7Size, p); } } using (p.BeginTask(weight, "Reading header part 8")) { var part8Size = intro.HeaderSize - toc.OffsetToPart8; part8 = await this.ReadHeaderPart8Async(stream, toc.OffsetToPart8, part8Size, p); } // Validate header hash if (!this.ValidateHash(stream, offset, intro)) { Log.LogWarning("Header hash does not match expected value."); } // The header stream must be disposed stream.Dispose(); return(new NefsHeader(intro, toc, part1, part2, part3, part4, part5, part6, part7, part8)); }
/// <summary> /// Writes an archive to the specified stream. A new archive obejct is returned that /// contains the updated header and item metadata. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="sourceHeader">Donor header information.</param> /// <param name="sourceItems">List of items to write. This list is not modified directly.</param> /// <param name="workDir">Temp working directory path.</param> /// <param name="p">Progress info.</param> /// <returns>A new NefsArchive object containing the updated header and item metadata.</returns> private async Task <NefsArchive> WriteArchiveAsync( Stream stream, Nefs20Header sourceHeader, NefsItemList sourceItems, string workDir, NefsProgress p) { // Setup task weights var taskWeightPrepareItems = 0.45f; var taskWeightWriteItems = 0.45f; var taskWeightHeader = 0.1f; // Prepare items for writing NefsItemList items; using (var t = p.BeginTask(taskWeightPrepareItems, "Preparing items")) { items = await this.PrepareItemsAsync(sourceItems, workDir, p); } // Determine number of items var numItems = items.Count; // Update header parts 3 and 4 first (need to know their sizes) var p4 = new Nefs20HeaderPart4(items); var p3 = new NefsHeaderPart3(items); // Compute header size var introSize = NefsHeaderIntro.Size; var tocSize = Nefs20HeaderIntroToc.Size; var p1Size = numItems * NefsHeaderPart1Entry.Size; // TODO : What about duplicates? var p2Size = numItems * NefsHeaderPart2Entry.Size; // TODO : What about duplicates? var p3Size = p3.Size; var p4Size = p4.Size; var p5Size = NefsHeaderPart5.Size; var p6Size = numItems * Nefs20HeaderPart6Entry.Size; var p7Size = numItems * NefsHeaderPart7Entry.Size; var p8Size = sourceHeader.Intro.HeaderSize - sourceHeader.TableOfContents.OffsetToPart8; var headerSize = introSize + tocSize + p1Size + p2Size + p3Size + p4Size + p5Size + p6Size + p7Size + p8Size; // Determine first data offset. There are two known offset values. If the header is // large enough, the second (larger) offset is used. var firstDataOffset = Nefs20Header.DataOffsetDefault; if (headerSize > firstDataOffset) { firstDataOffset = Nefs20Header.DataOffsetLarge; } // Write item data UInt64 archiveSize; using (var t = p.BeginTask(taskWeightWriteItems, "Writing items")) { archiveSize = await this.WriteItemsAsync(stream, items, firstDataOffset, p); } // Update remaining header data var p1 = new NefsHeaderPart1(items, p4); var p2 = new NefsHeaderPart2(items, p3); var p6 = new Nefs20HeaderPart6(items); var p7 = new NefsHeaderPart7(items); // Compute total archive size var p5 = new NefsHeaderPart5(); p5.Data0x00_ArchiveSize.Value = archiveSize; p5.Data0x08_ArchiveNameStringOffset.Value = p3.OffsetsByFileName[items.DataFileName]; p5.Data0x0C_FirstDataOffset.Value = sourceHeader.Part5.FirstDataOffset; // Update header intro var intro = new NefsHeaderIntro(); intro.Data0x00_MagicNumber.Value = sourceHeader.Intro.MagicNumber; intro.Data0x24_AesKeyHexString.Value = sourceHeader.Intro.AesKeyHexString; intro.Data0x64_HeaderSize.Value = (uint)headerSize; intro.Data0x68_NefsVersion.Value = sourceHeader.Intro.NefsVersion; intro.Data0x6c_NumberOfItems.Value = (uint)numItems; intro.Data0x70_UnknownZlib.Value = sourceHeader.Intro.Unknown0x70zlib; intro.Data0x78_Unknown.Value = sourceHeader.Intro.Unknown0x78; var toc = new Nefs20HeaderIntroToc(); toc.Data0x00_NumVolumes.Value = sourceHeader.TableOfContents.NumVolumes; toc.Data0x02_HashBlockSize.Value = sourceHeader.TableOfContents.Data0x02_HashBlockSize.Value; toc.Data0x04_OffsetToPart1.Value = introSize + tocSize; toc.Data0x0c_OffsetToPart2.Value = toc.OffsetToPart1 + (uint)p1Size; toc.Data0x14_OffsetToPart3.Value = toc.OffsetToPart2 + (uint)p2Size; toc.Data0x18_OffsetToPart4.Value = toc.OffsetToPart3 + (uint)p3Size; toc.Data0x1c_OffsetToPart5.Value = toc.OffsetToPart4 + (uint)p4Size; toc.Data0x08_OffsetToPart6.Value = toc.OffsetToPart5 + (uint)p5Size; toc.Data0x10_OffsetToPart7.Value = toc.OffsetToPart6 + (uint)p6Size; toc.Data0x20_OffsetToPart8.Value = toc.OffsetToPart7 + (uint)p7Size; toc.Data0x24_Unknown.Value = sourceHeader.TableOfContents.Unknown0x24; // Part 8 - not writing anything for now var p8 = new NefsHeaderPart8(p8Size); // Create new header object var header = new Nefs20Header(intro, toc, p1, p2, p3, p4, p5, p6, p7, p8); // Write the header using (var t = p.BeginTask(taskWeightHeader, "Writing header")) { await this.WriteHeaderAsync(stream, 0, header, p); } // Update hash await this.UpdateHashAsync(stream, 0, header, p); // Create new archive object return(new NefsArchive(header, items)); }