/// <summary> /// Initializes a new instance of the <see cref="RecentFile"/> class. /// </summary> /// <param name="source">Archive source.</param> public RecentFile(NefsArchiveSource source) { this.DataFilePath = source.DataFilePath; this.HeaderFilePath = source.HeaderFilePath; this.HeaderOffset = source.HeaderOffset; this.HeaderPart6Offset = source.HeaderPart6Offset; }
private NefsArchiveSource ValidateNefs() { var isAdvanced = this.advancedCheckBox.Checked; var offsetString = isAdvanced ? this.headerOffsetTextBox.Text : "0"; var offsetNumStyle = NumberStyles.Integer; if (!ulong.TryParse(offsetString, offsetNumStyle, CultureInfo.InvariantCulture, out var offset)) { this.UiService.ShowMessageBox($"Invalid header offset {offsetString}."); return(null); } var offsetPart6String = isAdvanced ? this.headerPart6OffsetTextBox.Text : "0"; if (!ulong.TryParse(offsetString, offsetNumStyle, CultureInfo.InvariantCulture, out var offsetPart6)) { this.UiService.ShowMessageBox($"Invalid header part 6 offset {offsetPart6String}."); return(null); } var headerFile = this.nefsFileTextBox.Text; var dataFile = isAdvanced ? this.dataFileTextBox.Text : headerFile; var source = new NefsArchiveSource(headerFile, offset, offsetPart6, dataFile); return(this.ValidateSource(source) ? source : null); }
private void OpenButton_Click(Object sender, EventArgs e) { NefsArchiveSource source = null; if (this.typeComboBox.SelectedItem == this.openModeNefs) { source = this.ValidateNefs(); } else if (this.typeComboBox.SelectedItem == this.openModeGameDatDirtRally2) { source = this.ValidateGameDat(); } else if (this.typeComboBox.SelectedItem == this.openModeGameDatDirt4) { source = this.ValidateGameDat(); } else if (this.typeComboBox.SelectedItem == this.openModeCustom) { source = this.ValidateCustom(); } if (source != null) { this.DialogResult = DialogResult.OK; this.ArchiveSource = source; this.Close(); } }
private NefsArchiveSource ValidateRecent() { var recent = this.recentListBox.SelectedItem as RecentFile; if (recent == null) { return(null); } var source = new NefsArchiveSource(recent.HeaderFilePath, recent.HeaderOffset, recent.HeaderPart6Offset, recent.DataFilePath); return(this.ValidateSource(source) ? source : null); }
private bool ValidateSource(NefsArchiveSource source) { if (!this.FileSystem.File.Exists(source.HeaderFilePath)) { this.UiService.ShowMessageBox($"Cannot find file: {source.HeaderFilePath}."); return(false); } if (!this.FileSystem.File.Exists(source.DataFilePath)) { this.UiService.ShowMessageBox($"Cannot find file: {source.DataFilePath}."); return(false); } return(true); }
/// <inheritdoc/> public async Task <bool> SaveArchiveByDialogAsync() { if (this.Archive == null) { Log.LogError("Failed to save archive: no archive open."); return(false); } var fileName = Path.GetFileName(this.Archive.Items.DataFilePath); var(result, path) = this.UiService.ShowSaveFileDialog(fileName); if (result != DialogResult.OK) { return(false); } var source = new NefsArchiveSource(path); return(await this.DoSaveArchiveAsync(source)); }
private void OpenButton_Click(Object sender, EventArgs e) { NefsArchiveSource source = null; if (this.modeListBox.SelectedItem == this.openModeNefs) { source = this.ValidateNefs(); } else if (this.modeListBox.SelectedItem == this.openModeGameDatDirtRally2) { source = this.ValidateGameDat(); } else if (this.modeListBox.SelectedItem == this.openModeGameDatDirt4) { source = this.ValidateGameDat(); } else if (this.modeListBox.SelectedItem == this.openModeGameBinDirtRally1) { source = this.ValidateGameDat(); } else if (this.modeListBox.SelectedItem == this.openModeGameDatCustom) { source = this.ValidateGameDat(); } else if (this.modeListBox.SelectedItem == this.openModeRecent) { source = this.ValidateRecent(); } if (source != null) { this.DialogResult = DialogResult.OK; this.ArchiveSource = source; var recentFile = new RecentFile(source); this.SettingsService.RecentFiles.Insert(0, recentFile); this.Close(); } }
private void PrintDebugInfo(NefsArchive archive, NefsArchiveSource source) { this.richTextBox.Text = ""; if (archive == null) { return; } if (archive.Header is Nefs20Header h20) { this.richTextBox.Text = this.GetDebugInfoVersion20(h20, source); } else if (archive.Header is Nefs16Header h16) { this.richTextBox.Text = this.GetDebugInfoVersion16(h16, source); } else { this.richTextBox.Text = "Unknown header version."; } }
/// <inheritdoc/> /// <remarks> /// This method isn't pretty, but it works. It's based on assumptions that may break. /// </remarks> public async Task <List <NefsArchiveSource> > FindHeadersAsync(string exePath, string dataFileDir, NefsProgress p) { var sources = new List <NefsArchiveSource>(); var nextPart6Offset = 0U; // Load exe into memory var exeBytes = this.FileSystem.File.ReadAllBytes(exePath); // Search for the part 6 base offset. For NeFS version 1.6 and 2.0 (maybe others?) // header parts 6 and 7 are stored separate from the other header parts. So far all the // part 6/7 data has been in the ".data" section of the exe. So we can get that offset // from the PE header. Some games (e.g. Grid 2) have other data that comes before the // part 6/7 data in the ".data" section. So we have to look for a pattern that looks // like the data we are looking for. try { if (!PeHelper.GetRawOffsetToSection(exeBytes, ".data", out var dataSectionOffset)) { Log.LogError("Failed to find part 6 offset; using 0 as offset."); } nextPart6Offset = (uint)dataSectionOffset; } catch (Exception ex) { Log.LogError(ex, "Failed to find part 6 offset; using 0 as offset."); } // Search for headers var i = 0; while (i + 4 <= exeBytes.Length) { var offset = i; i += 4; // Check for cancel p.CancellationToken.ThrowIfCancellationRequested(); // Searching for a NeFS header: Look for 4E 65 46 53 (NeFS). This is the NeFS header // magic number. if (exeBytes[offset] != 0x4E || exeBytes[offset + 1] != 0x65 || exeBytes[offset + 2] != 0x46 || exeBytes[offset + 3] != 0x53) { continue; } // Check for a known version number var version = BitConverter.ToUInt32(exeBytes, offset + 0x68); if (version != 0x20000 && version != 0x10600) { continue; } // Try to read header intro try { using (var byteStream = new MemoryStream(exeBytes)) { var(intro, headerStream) = await this.ReadHeaderIntroAsync(byteStream, (ulong)offset, p); using (headerStream) { INefsHeaderIntroToc toc; uint p6Size; uint p7Size; // Find next part 6 offset - there may be padding or other data before // the part 6/7 data nextPart6Offset = this.FindNextPart6Offset(nextPart6Offset, exeBytes); // Read table of contents if (version == (int)NefsVersion.Version200) { toc = await this.Read20HeaderIntroTocAsync(headerStream, Nefs20HeaderIntroToc.Offset, p); var numPart1Entries = toc.Part1Size / NefsHeaderPart1Entry.Size; var numPart2Entries = toc.Part2Size / NefsHeaderPart2Entry.Size; p6Size = numPart1Entries * Nefs20HeaderPart6Entry.Size; p7Size = numPart2Entries * NefsHeaderPart7Entry.Size; } else { toc = await this.Read16HeaderIntroTocAsync(headerStream, Nefs16HeaderIntroToc.Offset, p); var numPart1Entries = toc.Part1Size / NefsHeaderPart1Entry.Size; var numPart2Entries = toc.Part2Size / NefsHeaderPart2Entry.Size; p6Size = numPart1Entries * Nefs16HeaderPart6Entry.Size; p7Size = numPart2Entries * NefsHeaderPart7Entry.Size; } // Read part 5 var p5 = await this.ReadHeaderPart5Async(headerStream, toc.OffsetToPart5, NefsHeaderPart5.Size, p); // Find file name headerStream.Seek(toc.OffsetToPart3, SeekOrigin.Begin); headerStream.Seek(p5.ArchiveNameStringOffset, SeekOrigin.Current); // Read 256 bytes - this is overkill, probably won't have a filename // that big var nameBytes = new byte[256]; await headerStream.ReadAsync(nameBytes, 0, 256, p.CancellationToken); var name = StringHelper.TryReadNullTerminatedAscii(nameBytes, 0, nameBytes.Length); if (string.IsNullOrWhiteSpace(name)) { // Failed to get name Log.LogError($"Thought we found a header at {offset}, but could not read data file name."); continue; } // Create archive source for this header var dataFilePath = Path.Combine(dataFileDir, name); var source = new NefsArchiveSource(exePath, (ulong)offset, nextPart6Offset, dataFilePath); sources.Add(source); // Keep looking offset += (int)intro.HeaderSize; // Update part 6 search offset to skip the one we just used nextPart6Offset += p6Size + p7Size; } } } catch (Exception) { // Failed to read header, so assume not a header continue; } } return(sources); }
/// <inheritdoc/> public async Task <NefsArchive> ReadArchiveAsync(NefsArchiveSource source, NefsProgress p) { return(await this.ReadArchiveAsync(source.HeaderFilePath, source.HeaderOffset, source.HeaderPart6Offset, source.DataFilePath, p)); }
private async Task <bool> DoSaveArchiveAsync(NefsArchiveSource source) { if (this.Archive == null) { Log.LogError("Failed to save archive: no archive open."); return(false); } // Currently don't support saving separated header/data archives (i.e., game.dat) if (source.IsHeaderSeparate) { var msg = "Saving archive with a separated header is not supported."; this.UiService.ShowMessageBox(msg); Log.LogError(msg); return(false); } // Currently don't support offset headers if (source.HeaderOffset != 0) { var msg = "Saving archive with an offset header is not supported."; this.UiService.ShowMessageBox(msg); Log.LogError(msg); return(false); } // Currently don't support saving encrypted archives if (this.Archive.Header.IsEncrypted) { this.UiService.ShowMessageBox("Saving encrypted archives is not supported.", icon: MessageBoxIcon.Error); return(false); } var destFilePath = source.DataFilePath; Log.LogInformation("----------------------------"); Log.LogInformation($"Writing archive: {destFilePath}."); var result = false; // Save archive await this.ProgressService.RunModalTaskAsync(p => Task.Run(async() => { using (var tt = p.BeginTask(1.0f)) { // Save archive try { this.Archive = await this.NefsWriter.WriteArchiveAsync(destFilePath, this.Archive, p); this.ArchiveSource = source; this.UndoBuffer.MarkAsSaved(); this.ArchiveSaved?.Invoke(this, EventArgs.Empty); result = true; Log.LogInformation($"Archive saved."); } catch (Exception ex) { Log.LogError($"Failed to saved archive {destFilePath}.\r\n{ex.Message}"); } } })); return(result); }
/// <inheritdoc/> public async Task <bool> SaveArchiveAsync(string destFilePath) { var source = new NefsArchiveSource(destFilePath); return(await this.DoSaveArchiveAsync(source)); }
/// <summary> /// Looks through the game executable to find header offsets for game.dat files. /// </summary> /// <returns>A list of game.dat archive sources.</returns> private async Task <List <NefsArchiveSource> > FindGameDatHeaderOffsetsAsync( string gameDatDir, string gameExePath, NefsProgress p) { if (!this.FileSystem.File.Exists(gameExePath)) { this.UiService.ShowMessageBox($"Cannot find executable file: {gameExePath}."); return(new List <NefsArchiveSource>()); } var headerOffsets = new Dictionary <string, ulong>(); var gameDatFiles = new List <string>(); // Read whole game exe into memory byte[] gameExeBuffer; using (var t = p.BeginTask(0.20f, "Reading game executable")) using (var reader = this.FileSystem.File.OpenRead(gameExePath)) { gameExeBuffer = new byte[reader.Length]; await reader.ReadAsync(gameExeBuffer, 0, (int)reader.Length, p.CancellationToken); } // Search for headers in the exe using (var t = p.BeginTask(0.50f, "Searching for headers")) { var searchOffset = 0UL; (string DataFileName, ulong Offset)? header; while ((header = await this.Reader.FindHeaderAsync(gameExeBuffer, searchOffset, p)) != null) { headerOffsets.Add(header.Value.DataFileName, header.Value.Offset); searchOffset = header.Value.Offset + 4; } } // Try to match offsets to game.dat files using (var t = p.BeginTask(0.30f, "Searching for game.dat files")) { foreach (var file in this.FileSystem.Directory.EnumerateFiles(gameDatDir)) { var fileName = Path.GetFileName(file); if (headerOffsets.ContainsKey(fileName)) { gameDatFiles.Add(file); } } } // Match offsets and files if (gameDatFiles.Count != headerOffsets.Count) { Log.LogError($"Found {gameDatFiles.Count} game*.dat files, but found {headerOffsets.Count} headers in game exectuable."); } // Build data sources for the game.dat files var sources = new List <NefsArchiveSource>(); for (var i = 0; i < gameDatFiles.Count; ++i) { var fileName = Path.GetFileName(gameDatFiles[i]); var isDataEncrypted = true; var source = new NefsArchiveSource(gameExePath, headerOffsets[fileName], gameDatFiles[i], isDataEncrypted); sources.Add(source); } return(sources); }
/// <inheritdoc/> public async Task <bool> OpenArchiveAsync(NefsArchiveSource source) { var result = false; // Close existing archive if needed if (this.Archive != null) { if (!await this.CloseArchiveAsync()) { // User canceled close or close failed return(false); } } // Open archive await this.ProgressService.RunModalTaskAsync(p => Task.Run(async() => { using (var tt = p.BeginTask(1.0f)) { Log.LogInformation("----------------------------"); // Check if header/data files are split if (source.IsHeaderSeparate) { Log.LogInformation($"Opening archive:"); Log.LogInformation($"Header file: {source.HeaderFilePath}"); Log.LogInformation($"Header offset: {source.HeaderOffset}"); Log.LogInformation($"Header part 6 offset: {source.HeaderPart6Offset}"); Log.LogInformation($"Data file: {source.DataFilePath}"); } else { Log.LogInformation($"Opening archive: {source.DataFilePath}"); } // Verify file exists if (!this.FileSystem.File.Exists(source.HeaderFilePath)) { Log.LogError($"File not found: {source.HeaderFilePath}."); return; } if (!this.FileSystem.File.Exists(source.DataFilePath)) { Log.LogError($"File not found: {source.DataFilePath}."); return; } // Open archive try { this.Archive = await this.NefsReader.ReadArchiveAsync(source, p); this.ArchiveSource = source; this.ArchiveOpened?.Invoke(this, EventArgs.Empty); result = true; Log.LogInformation($"Archive opened."); } catch (Exception ex) { Log.LogError($"Failed to open archive {source.DataFilePath}.\r\n{ex.Message}"); } } })); return(result); }
public GameDatFileItem(NefsArchiveSource source) { this.Source = source ?? throw new ArgumentNullException(nameof(source)); }
/// <inheritdoc/> public async Task <bool> OpenArchiveAsync(string filePath) { var source = new NefsArchiveSource(filePath); return(await this.OpenArchiveAsync(source)); }
private string GetDebugInfoVersion16(Nefs16Header h, NefsArchiveSource source) { var headerPart1String = new StringBuilder(); foreach (var entry in h.Part1.EntriesByIndex) { headerPart1String.Append($"0x{entry.OffsetToData.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.IndexPart2.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.IndexPart4.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart1String.Append("\n"); } var headerPart2String = new StringBuilder(); foreach (var entry in h.Part2.EntriesByIndex) { headerPart2String.Append($"0x{entry.DirectoryId.Value.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.FirstChildId.Value.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.OffsetIntoPart3.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.ExtractedSize.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart2String.Append("\n"); } var headerPart3String = new StringBuilder(); foreach (var s in h.Part3.FileNames) { headerPart3String.AppendLine(s); } var headerPart6String = new StringBuilder(); foreach (var entry in h.Part6.EntriesByIndex) { headerPart6String.Append($"0x{entry.Volume.ToString("X")}".PadRight(20)); headerPart6String.Append($"0x{((byte)entry.Flags).ToString("X")}".PadRight(20)); headerPart6String.Append($"0x{entry.Unknown0x3.ToString("X")}".PadRight(20)); headerPart6String.Append("\n"); } var headerPart7String = new StringBuilder(); foreach (var entry in h.Part7.EntriesByIndex) { headerPart7String.Append($"0x{entry.SiblingId.Value.ToString("X")}".PadRight(20)); headerPart7String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart7String.Append("\n"); } return($@"Archive Source ----------------------------------------------------------- Header source file: {source.HeaderFilePath} Header offset: {source.HeaderOffset.ToString("X")} Data file path: {source.DataFilePath} Is header/data separate: {source.IsHeaderSeparate} General Info ----------------------------------------------------------- Archive Size: {h.Part5.ArchiveSize.ToString("X")} Is Header Encrypted? {h.Intro.IsEncrypted} Header size: {h.Intro.HeaderSize.ToString("X")} Intro size: {NefsHeaderIntro.Size.ToString("X")} Toc size: {Nefs16HeaderIntroToc.Size.ToString("X")} Part 1 size: {h.TableOfContents.Part1Size.ToString("X")} Part 2 size: {h.TableOfContents.Part2Size.ToString("X")} Part 3 size: {h.TableOfContents.Part3Size.ToString("X")} Part 4 size: {h.TableOfContents.Part4Size.ToString("X")} Part 8 size: {(h.Intro.HeaderSize - h.TableOfContents.OffsetToPart8).ToString("X")} Header Intro ----------------------------------------------------------- Magic Number: {h.Intro.MagicNumber.ToString("X")} Expected SHA-256 hash: {StringHelper.ByteArrayToString(h.Intro.ExpectedHash)} AES 256 key hex string: {StringHelper.ByteArrayToString(h.Intro.AesKeyHexString)} Header size: {h.Intro.HeaderSize.ToString("X")} NeFS version: {h.Intro.NefsVersion.ToString("X")} Number of items: {h.Intro.NumberOfItems.ToString("X")} Unknown 0x70: {h.Intro.Unknown0x70zlib.ToString("X")} Unknown 0x78: {h.Intro.Unknown0x78.ToString("X")} Header Table of Contents ----------------------------------------------------------- Offset to Part 1: {h.TableOfContents.OffsetToPart1.ToString("X")} Offset to Part 2: {h.TableOfContents.OffsetToPart2.ToString("X")} Offset to Part 3: {h.TableOfContents.OffsetToPart3.ToString("X")} Offset to Part 4: {h.TableOfContents.OffsetToPart4.ToString("X")} Offset to Part 5: {h.TableOfContents.OffsetToPart5.ToString("X")} Offset to Part 6: {h.TableOfContents.OffsetToPart6.ToString("X")} Offset to Part 7: {h.TableOfContents.OffsetToPart7.ToString("X")} Offset to Part 8: {h.TableOfContents.OffsetToPart8.ToString("X")} Num Volumes: {h.TableOfContents.NumVolumes.ToString("X")} Hash Block Size (<< 15): {h.TableOfContents.HashBlockSize.ToString("X")} Block Size (<< 15): {h.TableOfContents.BlockSize.ToString("X")} Split Size (<< 15): {h.TableOfContents.SplitSize.ToString("X")} Unknown 0x28: {StringHelper.ByteArrayToString(h.TableOfContents.Unknown0x28)} Header Part 1 ----------------------------------------------------------- Data Offset Index Part 2 Index Part 4 Id {headerPart1String.ToString()} Header Part 2 ----------------------------------------------------------- Directory Id First child Id Part 3 offset Extracted size Id {headerPart2String.ToString()} Header Part 3 ----------------------------------------------------------- {headerPart3String.ToString()} Header Part 4 ----------------------------------------------------------- Number of entries: {h.Part4.EntriesByIndex.Count.ToString("X")} Header Part 5 ----------------------------------------------------------- Archive size: {h.Part5.ArchiveSize.ToString("X")} First data offset: {h.Part5.FirstDataOffset.ToString("X")} Archive name string offset: {h.Part5.ArchiveNameStringOffset.ToString("X")} Header Part 6 ----------------------------------------------------------- {headerPart6String.ToString()} Header Part 7 ----------------------------------------------------------- {headerPart7String.ToString()} "); }
private void PrintDebugInfo(NefsArchive archive, NefsArchiveSource source) { this.richTextBox.Text = ""; if (archive == null) { return; } var headerPart1String = new StringBuilder(); foreach (var entry in archive.Header.Part1.EntriesByIndex) { headerPart1String.Append($"0x{entry.OffsetToData.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.MetadataIndex.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.IndexIntoPart4.ToString("X")}".PadRight(20)); headerPart1String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart1String.Append("\n"); } var headerPart2String = new StringBuilder(); foreach (var entry in archive.Header.Part2.EntriesByIndex) { headerPart2String.Append($"0x{entry.DirectoryId.Value.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.FirstChildId.Value.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.OffsetIntoPart3.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.ExtractedSize.ToString("X")}".PadRight(20)); headerPart2String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart2String.Append("\n"); } var headerPart3String = new StringBuilder(); foreach (var s in archive.Header.Part3.FileNames) { headerPart3String.AppendLine(s); } var headerPart6String = new StringBuilder(); foreach (var entry in archive.Header.Part6.EntriesByIndex) { headerPart6String.Append($"0x{entry.Byte0.ToString("X")}".PadRight(20)); headerPart6String.Append($"0x{entry.Byte1.ToString("X")}".PadRight(20)); headerPart6String.Append($"0x{entry.Byte2.ToString("X")}".PadRight(20)); headerPart6String.Append($"0x{entry.Byte3.ToString("X")}".PadRight(20)); headerPart6String.Append("\n"); } var headerPart7String = new StringBuilder(); foreach (var entry in archive.Header.Part7.EntriesByIndex) { headerPart7String.Append($"0x{entry.SiblingId.Value.ToString("X")}".PadRight(20)); headerPart7String.Append($"0x{entry.Id.Value.ToString("X")}".PadRight(20)); headerPart7String.Append("\n"); } this.richTextBox.Text = $@"Archive Source ----------------------------------------------------------- Header source file: {source.HeaderFilePath} Header offset: {source.HeaderOffset.ToString("X")} Data file path: {source.DataFilePath} Is header/data separate: {source.IsHeaderSeparate} General Info ----------------------------------------------------------- Archive Size: {archive.Header.Part5.ArchiveSize.ToString("X")} Is Header Encrypted? {archive.Header.Intro.IsEncrypted} Header size: {archive.Header.Intro.HeaderSize.ToString("X")} Intro size: {NefsHeaderIntro.Size.ToString("X")} Toc size: {NefsHeaderIntroToc.Size.ToString("X")} Part 1 size: {archive.Header.TableOfContents.Part1Size.ToString("X")} Part 2 size: {archive.Header.TableOfContents.Part2Size.ToString("X")} Part 3 size: {archive.Header.TableOfContents.Part3Size.ToString("X")} Part 4 size: {archive.Header.TableOfContents.Part4Size.ToString("X")} Part 5 size: {archive.Header.TableOfContents.Part5Size.ToString("X")} Part 6 size: {archive.Header.TableOfContents.Part6Size.ToString("X")} Part 7 size: {archive.Header.TableOfContents.Part7Size.ToString("X")} Part 8 size: {(archive.Header.Intro.HeaderSize - archive.Header.TableOfContents.OffsetToPart8).ToString("X")} Header Intro ----------------------------------------------------------- Magic Number: {archive.Header.Intro.MagicNumber.ToString("X")} Expected SHA-256 hash: {StringHelper.ByteArrayToString(archive.Header.Intro.ExpectedHash)} AES 256 key hex string: {StringHelper.ByteArrayToString(archive.Header.Intro.AesKeyHexString)} Header size: {archive.Header.Intro.HeaderSize.ToString("X")} Unknown 0x68: {archive.Header.Intro.Unknown0x68.ToString("X")} Number of items: {archive.Header.Intro.NumberOfItems.ToString("X")} Unknown 0x70: {archive.Header.Intro.Unknown0x70zlib.ToString("X")} Unknown 0x78: {archive.Header.Intro.Unknown0x78.ToString("X")} Header Table of Contents ----------------------------------------------------------- Offset to Part 1: {archive.Header.TableOfContents.OffsetToPart1.ToString("X")} Offset to Part 2: {archive.Header.TableOfContents.OffsetToPart2.ToString("X")} Offset to Part 3: {archive.Header.TableOfContents.OffsetToPart3.ToString("X")} Offset to Part 4: {archive.Header.TableOfContents.OffsetToPart4.ToString("X")} Offset to Part 5: {archive.Header.TableOfContents.OffsetToPart5.ToString("X")} Offset to Part 6: {archive.Header.TableOfContents.OffsetToPart6.ToString("X")} Offset to Part 7: {archive.Header.TableOfContents.OffsetToPart7.ToString("X")} Offset to Part 8: {archive.Header.TableOfContents.OffsetToPart8.ToString("X")} Unknown 0x00: {archive.Header.TableOfContents.Unknown0x00.ToString("X")} Unknown 0x24: {StringHelper.ByteArrayToString(archive.Header.TableOfContents.Unknown0x24)} Header Part 1 ----------------------------------------------------------- Data Offset Metadata index Index to Part 4 Id {headerPart1String.ToString()} Header Part 2 ----------------------------------------------------------- Directory Id First child Id Part 3 offset Extracted size Id {headerPart2String.ToString()} Header Part 3 ----------------------------------------------------------- {headerPart3String.ToString()} Header Part 4 ----------------------------------------------------------- Number of files: {archive.Header.Part4.EntriesByIndex.Count.ToString("X")} Header Part 5 ----------------------------------------------------------- Archive size: {archive.Header.Part5.ArchiveSize.ToString("X")} First data offset: {archive.Header.Part5.FirstDataOffset.ToString("X")} Archive name string offset: {archive.Header.Part5.ArchiveNameStringOffset.ToString("X")} Header Part 6 ----------------------------------------------------------- {headerPart6String.ToString()} Header Part 7 ----------------------------------------------------------- {headerPart7String.ToString()} "; }