Ejemplo n.º 1
0
 /// <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;
 }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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();
            }
        }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        /// <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));
        }
Ejemplo n.º 7
0
        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();
            }
        }
Ejemplo n.º 8
0
        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.";
            }
        }
Ejemplo n.º 9
0
        /// <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);
        }
Ejemplo n.º 10
0
 /// <inheritdoc/>
 public async Task <NefsArchive> ReadArchiveAsync(NefsArchiveSource source, NefsProgress p)
 {
     return(await this.ReadArchiveAsync(source.HeaderFilePath, source.HeaderOffset, source.HeaderPart6Offset, source.DataFilePath, p));
 }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 12
0
        /// <inheritdoc/>
        public async Task <bool> SaveArchiveAsync(string destFilePath)
        {
            var source = new NefsArchiveSource(destFilePath);

            return(await this.DoSaveArchiveAsync(source));
        }
Ejemplo n.º 13
0
        /// <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);
        }
Ejemplo n.º 14
0
        /// <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);
        }
Ejemplo n.º 15
0
 public GameDatFileItem(NefsArchiveSource source)
 {
     this.Source = source ?? throw new ArgumentNullException(nameof(source));
 }
Ejemplo n.º 16
0
        /// <inheritdoc/>
        public async Task <bool> OpenArchiveAsync(string filePath)
        {
            var source = new NefsArchiveSource(filePath);

            return(await this.OpenArchiveAsync(source));
        }
Ejemplo n.º 17
0
        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()}
");
        }
Ejemplo n.º 18
0
        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()}
";
        }