├── .gitmodules
├── Directory.Build.targets
├── VictorBush.Ego.NefsLib
├── .gitignore
├── Header
│ ├── INefsTocData.cs
│ ├── Version160
│ │ ├── NefsTocHashDigest160.cs
│ │ ├── NefsTocEntryWriteable160.cs
│ │ ├── NefsTocSharedEntryInfoWriteable160.cs
│ │ ├── NefsHeaderEntryTable160.cs
│ │ ├── NefsHeaderWriteableEntryTable160.cs
│ │ ├── NefsHeaderSharedEntryInfoTable160.cs
│ │ ├── NefsHeaderWriteableSharedEntryInfoTable160.cs
│ │ ├── NefsTocSharedEntryInfo160.cs
│ │ ├── NefsHeaderHashDigestTable160.cs
│ │ ├── NefsTocEntry160.cs
│ │ └── NefsTocHeaderA160.cs
│ ├── Version200
│ │ ├── NefsTocBlock200.cs
│ │ ├── NefsHeaderBlockTable200.cs
│ │ └── NefsTocEntryFlags200.cs
│ ├── VolumeInfo.cs
│ ├── Version010
│ │ ├── NefsTocVolumeSize010.cs
│ │ ├── NefsTocBlock010.cs
│ │ ├── NefsHeaderLinkTable010.cs
│ │ ├── NefsHeaderBlockTable010.cs
│ │ ├── NefsHeaderEntryTable010.cs
│ │ ├── NefsHeaderVolumeSizeTable010.cs
│ │ ├── NefsTocLink010.cs
│ │ ├── NefsTocEntryFlags010.cs
│ │ ├── NefsTocHeader010.cs
│ │ ├── NefsTocEntry010.cs
│ │ └── NefsHeader010.cs
│ ├── Version130
│ │ ├── NefsTocVolumeNameStart130.cs
│ │ ├── NefsHeaderVolumeNameStartTable130.cs
│ │ └── NefsTocHeader130.cs
│ ├── Version140
│ │ ├── NefsTocVolumeDataStart140.cs
│ │ ├── NefsHeaderVolumeDataStartTable140.cs
│ │ └── NefsTocHeader140.cs
│ ├── NefsDataTransformType.cs
│ ├── NefsTocTable.cs
│ ├── Version150
│ │ ├── NefsHeaderBlockTable151.cs
│ │ ├── NefsHeaderEntryTable150.cs
│ │ ├── NefsTocBlock151.cs
│ │ ├── NefsTocVolumeInfo150.cs
│ │ ├── NefsHeaderVolumeInfoTable150.cs
│ │ ├── NefsHeaderSharedEntryInfoTable150.cs
│ │ ├── NefsTocEntry150.cs
│ │ ├── NefsTocHeader150.cs
│ │ ├── NefsTocEntryFlags150.cs
│ │ ├── NefsTocSharedEntryInfo150.cs
│ │ ├── NefsTocHeader151.cs
│ │ └── NefsHeader150.cs
│ ├── Version020
│ │ ├── NefsTocHeader020.cs
│ │ └── NefsHeader020.cs
│ ├── NefsConstants.cs
│ ├── Builder
│ │ ├── NefsItemListBuilder020.cs
│ │ ├── NefsItemListBuilder130.cs
│ │ ├── NefsItemListBuilder140.cs
│ │ ├── NefsItemListBuilder150.cs
│ │ ├── NefsItemListBuilder151.cs
│ │ ├── NefsItemListBuilder010.cs
│ │ ├── NefsHeaderBuilder.cs
│ │ └── NefsItemListBuilder150Base.cs
│ ├── AesKeyHexBuffer.cs
│ ├── INefsTocTable.cs
│ └── INefsHeader.cs
├── Utility
│ ├── IsExternalInit.cs
│ ├── DictionaryExtensions.cs
│ ├── FileSystemExtensions.cs
│ ├── Sha256Hash.cs
│ ├── StreamExtensions.cs
│ ├── Sha256HashBuilder.cs
│ ├── DeflateHelper.cs
│ └── StringHelper.cs
├── ArchiveSource
│ ├── StandardSource.cs
│ ├── NefsArchiveSource.cs
│ └── HeadlessSource.cs
├── Item
│ ├── NefsItemType.cs
│ ├── NefsItemState.cs
│ ├── NefsItemId.cs
│ ├── NefsItemSize.cs
│ └── NefsItemAttributes.cs
├── IO
│ ├── NefsWriterSettings.cs
│ ├── INefsExeHeaderFinder.cs
│ ├── INefsWriter.cs
│ ├── ResizableBuffer.cs
│ ├── INefsReader.cs
│ ├── NefsWriterStrategy160Base.cs
│ ├── StructEx.cs
│ ├── EndianBinaryWriter.cs
│ └── NefsWriterStrategy151.cs
├── DataSource
│ ├── NefsEmptyDataSource.cs
│ ├── INefsDataSource.cs
│ ├── NefsFileDataSource.cs
│ ├── NefsDataTransform.cs
│ ├── NefsDataChunk.cs
│ └── NefsVolumeSource.cs
├── Progress
│ ├── NefsProgressTask.cs
│ └── NefsProgressEventArgs.cs
├── VictorBush.Ego.NefsLib.csproj
├── NefsArchive.cs
├── NefsLog.cs
└── NefsVersion.cs
├── VictorBush.Ego.NefsEdit
├── .gitignore
├── Resources
│ ├── question.ico
│ ├── arrow_Up_16xLG.png
│ ├── arrow_back_16xLG.png
│ └── arrow_Forward_16xLG.png
├── Properties
│ ├── Settings.settings
│ └── Settings.Designer.cs
├── Commands
│ ├── INefsEditCommand.cs
│ ├── NefsEditCommandEventKind.cs
│ ├── NefsEditCommandEventArgs.cs
│ ├── RemoveFileDuplicatesCommand.cs
│ ├── RemoveFileCommand.cs
│ ├── ReplaceFileDuplicatesCommand.cs
│ └── ReplaceFileCommand.cs
├── Utility
│ ├── TextBoxExtensions.cs
│ ├── DialogExtensions.cs
│ ├── HexStringTypeConverter.cs
│ ├── TreeNodeExtensions.cs
│ ├── LogHelper.cs
│ ├── Constants.cs
│ └── RichTextWriter.cs
├── Services
│ ├── IProgressService.cs
│ ├── ProgressService.cs
│ ├── ISettingsService.cs
│ └── IUiService.cs
├── UI
│ ├── TablessControl.cs
│ ├── ConsoleForm.cs
│ ├── PropertyGridForm.cs
│ ├── PropertyGridForm.Designer.cs
│ ├── ConsoleForm.Designer.cs
│ ├── ItemDebugForm.Designer.cs
│ ├── ArchiveDebugForm.Designer.cs
│ ├── BrowseAllForm.Designer.cs
│ ├── ProgressDialogForm.cs
│ └── OpenFileForm.resx
├── App.config
├── VictorBush.Ego.NefsEdit.csproj
├── Settings
│ ├── Settings.cs
│ └── OpenFileDialogState.cs
├── README.md
└── Program.cs
├── VictorBush.Ego.NefsEdit.Tests
├── .gitignore
├── VictorBush.Ego.NefsEdit.Tests.csproj.user
├── Services
│ └── InvisibleProgressService.cs
├── VictorBush.Ego.NefsEdit.Tests.csproj
├── Commands
│ └── TestCommand.cs
└── TestHelpers.cs
├── VictorBush.Ego.NefsLib.Tests
├── .gitignore
├── VictorBush.Ego.NefsLib.Tests.csproj
├── Utility
│ └── StreamExtensionsTests.cs
├── TestArchives
│ └── TestArchiveNoItems.cs
├── Item
│ └── NefsItemTests.cs
├── DataSource
│ └── NefsDataChunkTests.cs
└── IO
│ ├── NefsReaderStrategy200Tests.cs
│ ├── LzssDecompressTests.cs
│ ├── NefsWriterTests.cs
│ ├── NefsReaderStrategy150Tests.cs
│ └── NefsReaderStrategyTests.cs
├── .gitattributes
├── Directory.Build.props
├── README.md
├── TODO.md
├── LICENSE
├── Directory.Packages.props
├── .editorconfig
└── CodeMaid.config
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /obj/
3 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/.gitignore:
--------------------------------------------------------------------------------
1 | /obj/
2 | /bin/
3 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/.gitignore:
--------------------------------------------------------------------------------
1 | /obj/
2 | /bin/
3 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /obj/
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.cs text diff=csharp
4 | *.csproj text merge=union
5 | *.sln text merge=union
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Resources/question.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/HEAD/VictorBush.Ego.NefsEdit/Resources/question.ico
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Resources/arrow_Up_16xLG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/HEAD/VictorBush.Ego.NefsEdit/Resources/arrow_Up_16xLG.png
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Resources/arrow_back_16xLG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/HEAD/VictorBush.Ego.NefsEdit/Resources/arrow_back_16xLG.png
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Resources/arrow_Forward_16xLG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/HEAD/VictorBush.Ego.NefsEdit/Resources/arrow_Forward_16xLG.png
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/VictorBush.Ego.NefsEdit.Tests.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProjectFiles
5 |
6 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | latest
4 | true
5 | true
6 |
7 | Copyright © VictorBush 2021
8 | 0.7.1
9 |
10 |
11 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/INefsTocData.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | public interface INefsTocData where T : unmanaged, INefsTocData
6 | {
7 | ///
8 | /// The size of the data in bytes.
9 | ///
10 | static abstract int ByteCount { get; }
11 |
12 | void ReverseEndianness();
13 | }
14 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/IsExternalInit.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.ComponentModel;
4 |
5 | namespace System.Runtime.CompilerServices;
6 |
7 | ///
8 | /// Enables use of C# record init while still targeting .net standard.
9 | ///
10 | [EditorBrowsable(EditorBrowsableState.Never)]
11 | internal class IsExternalInit
12 | { }
13 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/ArchiveSource/StandardSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.ArchiveSource;
4 |
5 | ///
6 | /// Header and file data are both stored in the same file.
7 | ///
8 | public sealed class StandardSource : NefsArchiveSource
9 | {
10 | internal StandardSource(string filePath)
11 | : base(filePath)
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Item/NefsItemType.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Item;
4 |
5 | ///
6 | /// Types of NeFS items.
7 | ///
8 | public enum NefsItemType
9 | {
10 | ///
11 | /// A file in an archive.
12 | ///
13 | File,
14 |
15 | ///
16 | /// A directory in an archive.
17 | ///
18 | Directory,
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NeFS Edit
2 |
3 | NeFS Edit allows opening and modifying NeFS archive files. These archive files are used by Ego Engine games such as DiRT 4 and DiRT Rally 2.
4 |
5 | There are two components to this project.
6 | - NefsLib - a C# library for working with NeFS archives.
7 | - NefsEdit - a Windows Forms application that uses NefsLib.
8 |
9 | ## Acknowledgements
10 | [@Gigi1237]( https://github.com/Gigi1237 ) - encryption/reversing contributions
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/INefsEditCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Commands;
4 |
5 | ///
6 | /// A command that can be performed by the application.
7 | ///
8 | internal interface INefsEditCommand
9 | {
10 | ///
11 | /// Executes the command.
12 | ///
13 | void Do();
14 |
15 | ///
16 | /// Reverts the command.
17 | ///
18 | void Undo();
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocHashDigest160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Runtime.CompilerServices;
4 | using VictorBush.Ego.NefsLib.Utility;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header;
7 |
8 | public struct NefsTocHashDigest160 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public Sha256Hash Data;
13 |
14 | public void ReverseEndianness()
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/NefsWriterSettings.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsLib.IO;
2 |
3 | public readonly record struct NefsWriterSettings
4 | {
5 | public bool IsEncrypted { get; init; }
6 | public bool IsXorEncoded { get; init; }
7 | public bool IsLittleEndian { get; init; } = BitConverter.IsLittleEndian;
8 |
9 | public NefsWriterSettings(bool IsEncrypted, bool IsXorEncoded, bool IsLittleEndian)
10 | {
11 | this.IsEncrypted = IsEncrypted;
12 | this.IsXorEncoded = IsXorEncoded;
13 | this.IsLittleEndian = IsLittleEndian;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version200/NefsTocBlock200.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version200;
7 |
8 | public struct NefsTocBlock200 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint End;
13 |
14 | public void ReverseEndianness()
15 | {
16 | this.End = BinaryPrimitives.ReverseEndianness(this.End);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/VolumeInfo.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | public record VolumeInfo
6 | {
7 | ///
8 | /// The file name of the volume.
9 | ///
10 | public required string Name { get; init; }
11 |
12 | ///
13 | /// The total size of the volume.
14 | ///
15 | public ulong Size { get; init; }
16 |
17 | ///
18 | /// The offset to the data.
19 | ///
20 | public uint DataOffset { get; init; }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/NefsEditCommandEventKind.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Commands;
4 |
5 | ///
6 | /// Defines kinds of command events.
7 | ///
8 | internal enum NefsEditCommandEventKind
9 | {
10 | ///
11 | /// A new command was executed.
12 | ///
13 | New,
14 |
15 | ///
16 | /// An undo was performed.
17 | ///
18 | Undo,
19 |
20 | ///
21 | /// A redo was performed.
22 | ///
23 | Redo,
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/Services/InvisibleProgressService.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsEdit.Services;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Tests.Services;
7 |
8 | ///
9 | /// Progress service that does not display any progress dialog. Used for testing.
10 | ///
11 | internal class InvisibleProgressService : IProgressService
12 | {
13 | public async Task RunModalTaskAsync(Func task)
14 | {
15 | await task(new NefsProgress());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocVolumeSize010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | public struct NefsTocVolumeSize010 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public ulong Size;
14 |
15 | public void ReverseEndianness()
16 | {
17 | this.Size = BinaryPrimitives.ReverseEndianness(this.Size);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/TextBoxExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Utility;
4 |
5 | ///
6 | /// Extension methods for text boxes.
7 | ///
8 | public static class TextBoxExtensions
9 | {
10 | ///
11 | /// Scrolls text box to end to show the end of the text string.
12 | ///
13 | /// The text box to scroll.
14 | public static void ScrollToEnd(this TextBox textBox)
15 | {
16 | textBox.SelectionStart = textBox.Text.Length;
17 | textBox.SelectionLength = 0;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version130/NefsTocVolumeNameStart130.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version130;
7 |
8 | public struct NefsTocVolumeNameStart130 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Start;
14 |
15 | public void ReverseEndianness()
16 | {
17 | this.Start = BinaryPrimitives.ReverseEndianness(this.Start);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version140/NefsTocVolumeDataStart140.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version140;
7 |
8 | public struct NefsTocVolumeDataStart140 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Start;
14 |
15 | public void ReverseEndianness()
16 | {
17 | this.Start = BinaryPrimitives.ReverseEndianness(this.Start);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version200/NefsHeaderBlockTable200.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version200;
4 |
5 | ///
6 | /// The block table.
7 | ///
8 | public sealed class NefsHeaderBlockTable200 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | internal NefsHeaderBlockTable200(IReadOnlyList entries)
17 | {
18 | Entries = entries;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Services/IProgressService.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Progress;
4 |
5 | namespace VictorBush.Ego.NefsEdit.Services;
6 |
7 | ///
8 | /// Provides task execution and progress display services.
9 | ///
10 | internal interface IProgressService
11 | {
12 | ///
13 | /// Opens a modal progress dialog and executes a foreground task that reports progress to the progress popup.
14 | ///
15 | /// The task to execute.
16 | /// The .
17 | Task RunModalTaskAsync(Func task);
18 | }
19 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/NefsDataTransformType.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | ///
6 | /// Types of transformations applied to a data block.
7 | ///
8 | public enum NefsDataTransformType
9 | {
10 | ///
11 | /// No transformation.
12 | ///
13 | None = 0x0,
14 |
15 | ///
16 | /// Chunk is LZSS compressed.
17 | ///
18 | Lzss = 0x1,
19 |
20 | ///
21 | /// Chunk is AES encrypted.
22 | ///
23 | Aes = 0x4,
24 |
25 | ///
26 | /// Chunk is zlib compressed.
27 | ///
28 | Zlib = 0x7,
29 | }
30 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/INefsExeHeaderFinder.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.ArchiveSource;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsLib.IO;
7 |
8 | ///
9 | /// Represents an object that finds NeFS headers in exe files.
10 | ///
11 | public interface INefsExeHeaderFinder
12 | {
13 | ///
14 | /// Find headers within the exe file.
15 | ///
16 | /// The archive source based on the found headers.
17 | Task> FindHeadersAsync(string exePath, string dataFileDir, bool searchEntireExe,
18 | NefsProgress p);
19 | }
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocBlock010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | public struct NefsTocBlock010 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint End;
14 | public uint Transformation;
15 |
16 | public void ReverseEndianness()
17 | {
18 | this.End = BinaryPrimitives.ReverseEndianness(this.End);
19 | this.Transformation = BinaryPrimitives.ReverseEndianness(this.Transformation);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/TablessControl.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.UI;
4 |
5 | ///
6 | /// A tab control that hides its tab buttons at run time.
7 | ///
8 | /// https://stackoverflow.com/questions/6953487/hide-tab-header-on-c-sharp-tabcontrol.
9 | public class TablessControl : TabControl
10 | {
11 | ///
12 | protected override void WndProc(ref Message m)
13 | {
14 | // Hide tabs by trapping the TCM_ADJUSTRECT message
15 | if (m.Msg == 0x1328 && !DesignMode)
16 | {
17 | m.Result = (IntPtr)1;
18 | }
19 | else
20 | {
21 | base.WndProc(ref m);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocEntryWriteable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version160;
7 |
8 | public record struct NefsTocEntryWriteable160 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public ushort Volume;
13 | public ushort Flags;
14 |
15 | public void ReverseEndianness()
16 | {
17 | this.Volume = BinaryPrimitives.ReverseEndianness(this.Volume);
18 | this.Flags = BinaryPrimitives.ReverseEndianness(this.Flags);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/NefsTocTable.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | ///
6 | public abstract class NefsTocTable : INefsTocTable
7 | where T : unmanaged, INefsTocData
8 | {
9 | ///
10 | public IReadOnlyList Entries { get; }
11 |
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | protected NefsTocTable(IReadOnlyList entries)
16 | {
17 | Entries = entries;
18 | }
19 |
20 | public T GetEntryByOffset(ulong offset)
21 | {
22 | var index = Convert.ToInt32(offset / (uint)T.ByteCount);
23 | return Entries[index];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocSharedEntryInfoWriteable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header;
7 |
8 | public struct NefsTocSharedEntryInfoWriteable160 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint NextSibling;
13 | public uint PatchedEntry;
14 |
15 | public void ReverseEndianness()
16 | {
17 | this.NextSibling = BinaryPrimitives.ReverseEndianness(this.NextSibling);
18 | this.PatchedEntry = BinaryPrimitives.ReverseEndianness(this.PatchedEntry);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsHeaderBlockTable151.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version150;
4 |
5 | ///
6 | /// The block table.
7 | ///
8 | public sealed class NefsHeaderBlockTable151 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A collection of entries to initialize this object with.
17 | internal NefsHeaderBlockTable151(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsHeaderEntryTable150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version150;
4 |
5 | ///
6 | /// The primary table of entries.
7 | ///
8 | public sealed class NefsHeaderEntryTable150 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderEntryTable150(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsHeaderEntryTable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version160;
4 |
5 | ///
6 | /// The primary table of entries.
7 | ///
8 | public sealed class NefsHeaderEntryTable160 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderEntryTable160(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocBlock151.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocBlock151 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint End;
13 | public ushort Transformation;
14 | public ushort Checksum;
15 |
16 | public void ReverseEndianness()
17 | {
18 | this.End = BinaryPrimitives.ReverseEndianness(this.End);
19 | this.Transformation = BinaryPrimitives.ReverseEndianness(this.Transformation);
20 | this.Checksum = BinaryPrimitives.ReverseEndianness(this.Checksum);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocVolumeInfo150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocVolumeInfo150 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public ulong Size;
13 | public uint NameOffset;
14 | public uint DataOffset;
15 |
16 | public void ReverseEndianness()
17 | {
18 | this.Size = BinaryPrimitives.ReverseEndianness(this.Size);
19 | this.NameOffset = BinaryPrimitives.ReverseEndianness(this.NameOffset);
20 | this.DataOffset = BinaryPrimitives.ReverseEndianness(this.DataOffset);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsHeaderVolumeInfoTable150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version150;
4 |
5 | ///
6 | /// The volume info table.
7 | ///
8 | public sealed class NefsHeaderVolumeInfoTable150 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this table with.
17 | internal NefsHeaderVolumeInfoTable150(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/VictorBush.Ego.NefsLib.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | Library
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 | all
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsHeaderWriteableEntryTable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version160;
4 |
5 | ///
6 | /// The writable entry table.
7 | ///
8 | public sealed class NefsHeaderWriteableEntryTable160 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderWriteableEntryTable160(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/DialogExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Utility;
4 |
5 | ///
6 | /// Extension to allow the form to be a modal dialog while perform async operations.
7 | /// See: https://stackoverflow.com/questions/33406939/async-showdialog.
8 | ///
9 | public static class DialogExtensions
10 | {
11 | ///
12 | /// Allows opening a modal dialog asynchronously.
13 | ///
14 | /// The dialog.
15 | /// The dialog result.
16 | public static async Task ShowDialogAsync(this Form @this)
17 | {
18 | await Task.Yield();
19 | if (@this.IsDisposed)
20 | {
21 | return DialogResult.OK;
22 | }
23 |
24 | return @this.ShowDialog();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsHeaderSharedEntryInfoTable150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version150;
4 |
5 | ///
6 | /// The shared entry info table.
7 | ///
8 | public sealed class NefsHeaderSharedEntryInfoTable150 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderSharedEntryInfoTable150(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsHeaderSharedEntryInfoTable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version160;
4 |
5 | ///
6 | /// The shared entry info table.
7 | ///
8 | public sealed class NefsHeaderSharedEntryInfoTable160 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderSharedEntryInfoTable160(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/INefsWriter.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.ArchiveSource;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsLib.IO;
7 |
8 | ///
9 | /// Writes NeFS archives.
10 | ///
11 | public interface INefsWriter
12 | {
13 | ///
14 | /// Writes the archive to the specified desintation.
15 | ///
16 | /// The path to write the file to.
17 | /// The archive to write.
18 | /// Progress info.
19 | /// An updated archive object containing updated metadata.
20 | Task<(NefsArchive Archive, NefsArchiveSource Source)> WriteArchiveAsync(string destFilePath, NefsArchive nefs, NefsProgress p);
21 | }
22 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 | - Checksum in 1.5.1 header.
3 | - Computing hashes in hash table.
4 | - Logging verbosity options (i.e., settings save/lod, etc can be debug). Cleanup info/dbg/wrn/error usage.
5 | - Save window position, size, pane locations, etc for next startup.
6 | - Console logging performance issues (https://github.com/victorbush/ego.nefsedit/issues/8).
7 | - Fix verifying hash for game.dat headers (https://github.com/victorbush/ego.nefsedit/issues/9).
8 | - Fix sorting by id in debug view and allow resetting sorting order (https://github.com/victorbush/ego.nefsedit/issues/11).
9 | - Add protection against saving over an archive in a game's directory (or always force a "Save As").
10 | - Support writing multiple volumes
11 | - Support writing split archives across multiple files
12 | - Allow removing files (adjust flags like IsLastSibling)
13 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/ConsoleForm.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsEdit.Utility;
4 | using WeifenLuo.WinFormsUI.Docking;
5 |
6 | namespace VictorBush.Ego.NefsEdit.UI;
7 |
8 | ///
9 | /// Console output form.
10 | ///
11 | public partial class ConsoleForm : DockContent
12 | {
13 | private RichTextWriter? writer;
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | public ConsoleForm()
19 | {
20 | InitializeComponent();
21 | }
22 |
23 | ///
24 | /// Sets the application's standard output to write to the form's RichTextBox control.
25 | ///
26 | public void SetupConsole()
27 | {
28 | this.writer = new RichTextWriter(this.richTextBox);
29 | Console.SetOut(this.writer);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/HexStringTypeConverter.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.ComponentModel;
4 | using System.Globalization;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Utility;
7 |
8 | ///
9 | /// Hex string formatting utility.
10 | ///
11 | internal class HexStringTypeConverter : TypeConverter
12 | {
13 | ///
14 | public override object? ConvertTo(
15 | ITypeDescriptorContext? context,
16 | CultureInfo? culture,
17 | object? value,
18 | Type destinationType)
19 | {
20 | if (destinationType == typeof(string) &&
21 | (value?.GetType() == typeof(int) || value?.GetType() == typeof(uint)))
22 | {
23 | return string.Format("0x{0:X8}", value);
24 | }
25 | else
26 | {
27 | return base.ConvertTo(context, culture, value, destinationType);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsHeaderLinkTable010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version010;
4 |
5 | ///
6 | /// The link table.
7 | ///
8 | public sealed class NefsHeaderLinkTable010 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderLinkTable010(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderLinkTable010 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/VictorBush.Ego.NefsEdit.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | Library
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsHeaderBlockTable010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version010;
4 |
5 | ///
6 | /// The block table.
7 | ///
8 | public sealed class NefsHeaderBlockTable010 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderBlockTable010(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderBlockTable010 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Item/NefsItemState.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Item;
4 |
5 | ///
6 | /// Identifies the modification state of an item in an archive. When an item is modified, the action does not take
7 | /// effect until the archive is saved. In the mean time, the modification state identifies the pending change.
8 | ///
9 | public enum NefsItemState
10 | {
11 | ///
12 | /// No change has been made to the item.
13 | ///
14 | None = 0,
15 |
16 | ///
17 | /// The item is being added to the archive.
18 | ///
19 | Added,
20 |
21 | ///
22 | /// The item is being removed from the archive.
23 | ///
24 | Removed,
25 |
26 | ///
27 | /// The item's file data is being replaced.
28 | ///
29 | Replaced,
30 | }
31 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsHeaderEntryTable010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version010;
4 |
5 | ///
6 | /// The primary table of entries.
7 | ///
8 | public sealed class NefsHeaderEntryTable010 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderEntryTable010(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderEntryTable010 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsHeaderWriteableSharedEntryInfoTable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version160;
4 |
5 | ///
6 | /// The writeable shared entry info table.
7 | ///
8 | public sealed class NefsHeaderWriteableSharedEntryInfoTable160 : INefsTocTable
9 | {
10 | ///
11 | public IReadOnlyList Entries { get; }
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// A list of entries to instantiate this part with.
17 | internal NefsHeaderWriteableSharedEntryInfoTable160(IReadOnlyList entries)
18 | {
19 | Entries = entries;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/Commands/TestCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Text;
4 | using VictorBush.Ego.NefsEdit.Commands;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Tests.Commands;
7 |
8 | ///
9 | /// A simple command use for testing the undo buffer.
10 | ///
11 | internal class TestCommand : INefsEditCommand
12 | {
13 | public TestCommand(StringBuilder stringBuilder, string oldVal, string newVal)
14 | {
15 | TheString = stringBuilder;
16 | OldVal = oldVal;
17 | NewVal = newVal;
18 | }
19 |
20 | private string NewVal { get; }
21 |
22 | private string OldVal { get; }
23 |
24 | private StringBuilder TheString { get; }
25 |
26 | public void Do()
27 | {
28 | TheString.Clear();
29 | TheString.Append(NewVal);
30 | }
31 |
32 | public void Undo()
33 | {
34 | TheString.Clear();
35 | TheString.Append(OldVal);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/TreeNodeExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Utility;
4 |
5 | ///
6 | /// Extension methods for tree nodes.
7 | ///
8 | public static class TreeNodeExtensions
9 | {
10 | ///
11 | /// Gets all descendant nodes of a TreeNode.
12 | ///
13 | /// See https://stackoverflow.com/questions/177277/how-to-get-a-list-of-all-child-nodes-in-a-treeview-in-net.
14 | /// The TreeNode to get descendants off.
15 | /// List of descendant tree nodes.
16 | public static IEnumerable DescendantNodes(this TreeNode input)
17 | {
18 | foreach (TreeNode node in input.Nodes)
19 | {
20 | yield return node;
21 | foreach (var subnode in node.DescendantNodes())
22 | {
23 | yield return subnode;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/NefsEmptyDataSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Abstractions;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsLib.DataSource;
7 |
8 | ///
9 | /// Data source that has no data.
10 | ///
11 | public class NefsEmptyDataSource : INefsDataSource
12 | {
13 | ///
14 | public string FilePath => "";
15 |
16 | ///
17 | public bool IsTransformed => true;
18 |
19 | ///
20 | public long Offset => 0;
21 |
22 | ///
23 | public NefsItemSize Size => new(0);
24 |
25 | ///
26 | public Stream OpenRead(IFileSystem fileSystem)
27 | {
28 | throw new NotSupportedException("Empty data source cannot be opened.");
29 | }
30 |
31 | ///
32 | public bool Exists(IFileSystem fileSystem)
33 | {
34 | return false;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Utility;
4 |
5 | ///
6 | /// Extension methods for dictionaries.
7 | ///
8 | public static class DictionaryExtensions
9 | {
10 | ///
11 | /// Gets the value from a dictionary or returns the default value if doesn't exist.
12 | ///
13 | /// The type of key.
14 | /// The type of value.
15 | /// The dictionary.
16 | /// The key.
17 | /// The value.
18 | public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key)
19 | {
20 | if (dictionary.TryGetValue(key, out var val))
21 | {
22 | return val;
23 | }
24 |
25 | return default;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsHeaderVolumeSizeTable010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version010;
4 |
5 | ///
6 | /// The volume size table.
7 | ///
8 | public sealed class NefsHeaderVolumeSizeTable010 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderVolumeSizeTable010(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderVolumeSizeTable010 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocLink010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | public struct NefsTocLink010 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint ParentOffset;
14 | public uint NextSiblingOffset;
15 | public uint FirstChildOffset;
16 | public uint NameOffset;
17 |
18 | public void ReverseEndianness()
19 | {
20 | this.ParentOffset = BinaryPrimitives.ReverseEndianness(this.ParentOffset);
21 | this.NextSiblingOffset = BinaryPrimitives.ReverseEndianness(this.NextSiblingOffset);
22 | this.FirstChildOffset = BinaryPrimitives.ReverseEndianness(this.FirstChildOffset);
23 | this.NameOffset = BinaryPrimitives.ReverseEndianness(this.NameOffset);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocEntryFlags010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version010;
4 |
5 | [Flags]
6 | public enum NefsTocEntryFlags010 : uint
7 | {
8 | ///
9 | /// No flags set.
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// Indicates if the entry's data has transforms applied (compressed, encrypted, etc).
15 | ///
16 | Transformed = 0x01,
17 |
18 | ///
19 | /// Indicates if the entry is a directory.
20 | ///
21 | Directory = 0x02,
22 |
23 | ///
24 | /// Indicates if the entry is a duplicate.
25 | ///
26 | Duplicate = 0x04,
27 |
28 | ///
29 | /// Indicates if the entry has duplicates.
30 | ///
31 | Duplicated = 0x08,
32 |
33 | ///
34 | /// Indicates if the entry is cacheable by game engine.
35 | ///
36 | Cacheable = 0x16
37 | }
38 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Progress/NefsProgressTask.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Progress;
4 |
5 | ///
6 | /// Represents a progress task. Exists as a convenience to call EndTask on dispose.
7 | ///
8 | ///
9 | ///
10 | ///using (var t = p.BeginTask(1.0f))
11 | ///{
12 | ///doSomething();
13 | ///}
14 | ///
15 | ///
16 | public sealed class NefsProgressTask : IDisposable
17 | {
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The progress context.
22 | internal NefsProgressTask(NefsProgress p)
23 | {
24 | Context = p;
25 | }
26 |
27 | ///
28 | /// Get progress context this task is for.
29 | ///
30 | private NefsProgress Context { get; }
31 |
32 | ///
33 | public void Dispose()
34 | {
35 | Context.EndTask();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/PropertyGridForm.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using WeifenLuo.WinFormsUI.Docking;
4 |
5 | namespace VictorBush.Ego.NefsEdit.UI;
6 |
7 | ///
8 | /// Form that contains the property grid.
9 | ///
10 | public partial class PropertyGridForm : DockContent
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | public PropertyGridForm()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | ///
21 | /// Refreshes the properties list.
22 | ///
23 | public void RefreshGrid()
24 | {
25 | this.propertyGrid.Refresh();
26 | }
27 |
28 | ///
29 | /// Sets the selected object in the property grid.
30 | ///
31 | /// The object to display in the property grid.
32 | public void SetSelectedObject(object? obj)
33 | {
34 | this.propertyGrid.SelectedObject = obj;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocHeader010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | public struct NefsTocHeader010 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Magic;
14 | public uint TocSize;
15 | public uint Version;
16 | public uint NumVolumes;
17 | public uint BlockSize;
18 | public uint EntryTableStart;
19 | public uint LinkTableStart;
20 | public uint NameTableStart;
21 | public uint BlockTableStart;
22 | public uint VolumeSizeTableStart;
23 |
24 | public unsafe void ReverseEndianness()
25 | {
26 | var buffer = new Span(Unsafe.AsPointer(ref this), 10);
27 | for (var i = 0; i < buffer.Length; ++i)
28 | {
29 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version130/NefsHeaderVolumeNameStartTable130.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version130;
4 |
5 | ///
6 | /// The volume name start table.
7 | ///
8 | public sealed class NefsHeaderVolumeNameStartTable130 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderVolumeNameStartTable130(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderVolumeNameStartTable130 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version140/NefsHeaderVolumeDataStartTable140.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version140;
4 |
5 | ///
6 | /// The volume data start table.
7 | ///
8 | public sealed class NefsHeaderVolumeDataStartTable140 : NefsTocTable,
9 | INefsTocTable
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal NefsHeaderVolumeDataStartTable140(IReadOnlyList entries) : base(entries)
15 | {
16 | }
17 |
18 | ///
19 | static NefsHeaderVolumeDataStartTable140 INefsTocTable.Create(
20 | IReadOnlyList entries)
21 | {
22 | return new(entries);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/VictorBush.Ego.NefsLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | Library
5 | enable
6 | true
7 | true
8 |
9 |
10 |
11 | <_Parameter1>$(MSBuildProjectName).Tests
12 |
13 |
14 | <_Parameter1>VictorBush.Ego.NefsEdit.Tests
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocSharedEntryInfo160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version160;
7 |
8 | public struct NefsTocSharedEntryInfo160 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint Parent;
13 | public uint FirstChild;
14 | public uint NameOffset;
15 | public uint Size;
16 | public uint FirstDuplicate;
17 |
18 | public void ReverseEndianness()
19 | {
20 | this.Parent = BinaryPrimitives.ReverseEndianness(this.Parent);
21 | this.FirstChild = BinaryPrimitives.ReverseEndianness(this.FirstChild);
22 | this.NameOffset = BinaryPrimitives.ReverseEndianness(this.NameOffset);
23 | this.Size = BinaryPrimitives.ReverseEndianness(this.Size);
24 | this.FirstDuplicate = BinaryPrimitives.ReverseEndianness(this.FirstDuplicate);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsHeaderHashDigestTable160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version160;
4 |
5 | ///
6 | /// A list of SHA-256 hashes of the archive's file data. The size of the hashed chunks are typically 0x800000. NeFS
7 | /// version 1.6 has a Block Size property that can specify this size. There are some unknown oddities. The last chunk of
8 | /// data does not seem to have a corresponding hash. There is additional data at the end of part 8 that is unknown.
9 | ///
10 | public sealed class NefsHeaderHashDigestTable160 : INefsTocTable
11 | {
12 | ///
13 | public IReadOnlyList Entries { get; }
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | internal NefsHeaderHashDigestTable160(IReadOnlyList entries)
19 | {
20 | Entries = entries;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version020/NefsTocHeader020.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version020;
7 |
8 | public struct NefsTocHeader020 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Magic;
14 | public uint TocSize;
15 | public uint Version;
16 | public uint NumVolumes;
17 | public uint BlockSize;
18 | public uint EntryTableStart;
19 | public uint LinkTableStart;
20 | public uint NameTableStart;
21 | public uint BlockTableStart;
22 | public uint VolumeSizeTableStart;
23 | public AesKeyHexBuffer AesKey;
24 |
25 | public unsafe void ReverseEndianness()
26 | {
27 | var buffer = new Span(Unsafe.AsPointer(ref this), 10);
28 | for (var i = 0; i < buffer.Length; ++i)
29 | {
30 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/ResizableBuffer.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers;
4 |
5 | namespace VictorBush.Ego.NefsLib.IO;
6 |
7 | public class ResizableBuffer : IDisposable
8 | {
9 | private readonly ArrayPool arrayPool;
10 | private byte[] buffer;
11 |
12 | public Memory Memory { get; private set; }
13 |
14 | public ResizableBuffer() : this(ArrayPool.Shared, 16)
15 | {
16 | }
17 |
18 | public ResizableBuffer(ArrayPool arrayPool, int startingMinimumLength)
19 | {
20 | this.arrayPool = arrayPool;
21 | this.buffer = arrayPool.Rent(startingMinimumLength);
22 | Memory = this.buffer.AsMemory();
23 | }
24 |
25 | public void EnsureLength(int length)
26 | {
27 | if (length <= this.buffer.Length)
28 | {
29 | return;
30 | }
31 |
32 | this.arrayPool.Return(this.buffer);
33 | this.buffer = this.arrayPool.Rent(length);
34 | Memory = this.buffer.AsMemory();
35 | }
36 |
37 | public void Dispose()
38 | {
39 | this.arrayPool.Return(this.buffer);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version200/NefsTocEntryFlags200.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version200;
4 |
5 | ///
6 | /// Bitfield flags for part 6 data.
7 | ///
8 | [Flags]
9 | public enum NefsTocEntryFlags200 : ushort
10 | {
11 | ///
12 | /// No flags set.
13 | ///
14 | None = 0x0,
15 |
16 | ///
17 | /// Indicates if the item's data is zlib compressed.
18 | ///
19 | IsZlib = 0x1,
20 |
21 | ///
22 | /// Indicates if the item's data is AES encrypted.
23 | ///
24 | IsAes = 0x2,
25 |
26 | ///
27 | /// A flag indicating whether this item is a directory.
28 | ///
29 | IsDirectory = 0x4,
30 |
31 | ///
32 | /// Indicates if item has duplicate entries with the same id.
33 | ///
34 | IsDuplicated = 0x8,
35 |
36 | ///
37 | /// Indicates if the entry is the last sibling in the directory.
38 | ///
39 | LastSibling = 0x10,
40 | }
41 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsTocEntry010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | public struct NefsTocEntry010 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public ulong Start;
14 | public ulong LinkOffset;
15 | public uint Size;
16 | public uint FirstBlock;
17 | public uint Volume;
18 | public uint Flags;
19 |
20 | public void ReverseEndianness()
21 | {
22 | this.Start = BinaryPrimitives.ReverseEndianness(this.Start);
23 | this.LinkOffset = BinaryPrimitives.ReverseEndianness(this.LinkOffset);
24 | this.Size = BinaryPrimitives.ReverseEndianness(this.Size);
25 | this.FirstBlock = BinaryPrimitives.ReverseEndianness(this.FirstBlock);
26 | this.Volume = BinaryPrimitives.ReverseEndianness(this.Volume);
27 | this.Flags = BinaryPrimitives.ReverseEndianness(this.Flags);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/NefsConstants.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | public static class NefsConstants
6 | {
7 | // NeFS
8 | public const uint FourCc = 0x5346654E;
9 |
10 | ///
11 | /// The intro header size. This should match the RSA key size.
12 | ///
13 | public const int IntroSize = 128;
14 |
15 | ///
16 | /// The block size for AES.
17 | ///
18 | public const int AesBlockSize = 128;
19 |
20 | ///
21 | /// The block index used to denote that there are no blocks for an entry.
22 | ///
23 | public const uint NoBlocksIndex = uint.MaxValue;
24 |
25 | ///
26 | /// Returns the given header as T or throws.
27 | ///
28 | public static T As(this INefsHeader header)
29 | where T : INefsHeader
30 | {
31 | if (header is not T headerAsT)
32 | {
33 | throw new ArgumentException($"Header must be of type {typeof(T).Name}", nameof(header));
34 | }
35 |
36 | return headerAsT;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocEntry150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocEntry150 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public ulong Start;
13 | public ushort Volume;
14 | public ushort Flags;
15 | public uint SharedInfo;
16 | public uint FirstBlock;
17 | public uint NextDuplicate;
18 |
19 | public void ReverseEndianness()
20 | {
21 | this.Start = BinaryPrimitives.ReverseEndianness(this.Start);
22 | this.Volume = BinaryPrimitives.ReverseEndianness(this.Volume);
23 | this.Flags = BinaryPrimitives.ReverseEndianness(this.Flags);
24 | this.SharedInfo = BinaryPrimitives.ReverseEndianness(this.SharedInfo);
25 | this.FirstBlock = BinaryPrimitives.ReverseEndianness(this.FirstBlock);
26 | this.NextDuplicate = BinaryPrimitives.ReverseEndianness(this.NextDuplicate);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/NefsArchive.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsLib;
7 |
8 | ///
9 | /// A NeFS archive.
10 | ///
11 | public sealed class NefsArchive
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The archive's header.
17 | /// List of items for this archive.
18 | public NefsArchive(INefsHeader header, NefsItemList items)
19 | {
20 | Header = header ?? throw new ArgumentNullException(nameof(header));
21 | Items = items ?? throw new ArgumentNullException(nameof(items));
22 | }
23 |
24 | ///
25 | /// NeFS file header.
26 | ///
27 | public INefsHeader Header { get; }
28 |
29 | ///
30 | /// List of items in this archive. This list should always be ordered by item id.
31 | ///
32 | public NefsItemList Items { get; }
33 | }
34 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/NefsEditCommandEventArgs.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Commands;
4 |
5 | ///
6 | /// Event arguments for when a command is executed.
7 | ///
8 | internal class NefsEditCommandEventArgs : EventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The kind of event that was executed.
14 | /// The command that was executed.
15 | public NefsEditCommandEventArgs(NefsEditCommandEventKind kind, INefsEditCommand command)
16 | {
17 | Kind = kind;
18 | Command = command ?? throw new ArgumentNullException(nameof(command));
19 | }
20 |
21 | ///
22 | /// Gets the command that was executed.
23 | ///
24 | public INefsEditCommand Command { get; }
25 |
26 | ///
27 | /// Gets the kind of command that was executed.
28 | ///
29 | public NefsEditCommandEventKind Kind { get; }
30 | }
31 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/Utility/StreamExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Utility;
4 | using Xunit;
5 |
6 | namespace VictorBush.Ego.NefsLib.Tests.Utility;
7 |
8 | public sealed class StreamExtensionsTests
9 | {
10 | [Fact]
11 | public async Task CopyPartialAsync_LengthGreaterThanCopyBuffer()
12 | {
13 | var inputLength = StreamExtensions.CopyBufferSize + 8;
14 | var input = new byte[inputLength];
15 | var copyLength = StreamExtensions.CopyBufferSize + 4;
16 |
17 | using (var inputStream = new MemoryStream(input))
18 | using (var outputStream = new MemoryStream())
19 | {
20 | await inputStream.CopyPartialAsync(outputStream, copyLength, CancellationToken.None);
21 |
22 | var output = new byte[outputStream.Length];
23 | await outputStream.ReadAsync(output, 0, (int)outputStream.Length);
24 |
25 | Assert.Equal(outputStream.Length, copyLength);
26 |
27 | for (var i = 0; i < outputStream.Length; ++i)
28 | {
29 | Assert.Equal(input[i], output[i]);
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocHeader150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocHeader150 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint Magic;
13 | public uint TocSize;
14 | public uint Version;
15 | public uint NumVolumes;
16 | public uint NumEntries;
17 | public uint BlockSize;
18 | public uint SplitSize;
19 | public uint EntryTableStart;
20 | public uint SharedEntryInfoTableStart;
21 | public uint NameTableStart;
22 | public uint BlockTableStart;
23 | public uint VolumeInfoTableStart;
24 | public AesKeyHexBuffer AesKey;
25 |
26 | public unsafe void ReverseEndianness()
27 | {
28 | var buffer = new Span(Unsafe.AsPointer(ref this), 12);
29 | for (var i = 0; i < buffer.Length; ++i)
30 | {
31 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder020.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version020;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder020(NefsHeader020 header, ILogger logger)
10 | : NefsItemListBuilder010Base(header, logger)
11 | {
12 | ///
13 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
14 | {
15 | var id = new NefsItemId(entryIndex);
16 | var entry = Header.EntryTable.Entries[id.Index];
17 | var link = Header.LinkTable.GetEntryByOffset(entry.LinkOffset);
18 | return BuildItem(id, entry, link, Header.EntryTable.Entries, itemList);
19 | }
20 |
21 | ///
22 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
23 | {
24 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
25 | return (block.End, block.Transformation, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder130.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version130;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder130(NefsHeader130 header, ILogger logger)
10 | : NefsItemListBuilder010Base(header, logger)
11 | {
12 | ///
13 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
14 | {
15 | var id = new NefsItemId(entryIndex);
16 | var entry = Header.EntryTable.Entries[id.Index];
17 | var link = Header.LinkTable.GetEntryByOffset(entry.LinkOffset);
18 | return BuildItem(id, entry, link, Header.EntryTable.Entries, itemList);
19 | }
20 |
21 | ///
22 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
23 | {
24 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
25 | return (block.End, block.Transformation, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder140.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version140;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder140(NefsHeader140 header, ILogger logger)
10 | : NefsItemListBuilder010Base(header, logger)
11 | {
12 | ///
13 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
14 | {
15 | var id = new NefsItemId(entryIndex);
16 | var entry = Header.EntryTable.Entries[id.Index];
17 | var link = Header.LinkTable.GetEntryByOffset(entry.LinkOffset);
18 | return BuildItem(id, entry, link, Header.EntryTable.Entries, itemList);
19 | }
20 |
21 | ///
22 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
23 | {
24 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
25 | return (block.End, block.Transformation, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/INefsReader.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.ArchiveSource;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsLib.IO;
7 |
8 | ///
9 | /// Reads NeFS archives.
10 | ///
11 | public interface INefsReader
12 | {
13 | ///
14 | /// Loads a self-contained NeFS archive from a single file. Archive version is auto-detected.
15 | ///
16 | /// File path to the archive.
17 | /// Progress information.
18 | /// The loaded .
19 | Task ReadArchiveAsync(string filePath, NefsProgress p);
20 |
21 | ///
22 | /// Reads a NeFS archive from the specified source.
23 | ///
24 | /// The archive source.
25 | /// Progress information.
26 | /// The loaded .
27 | Task ReadArchiveAsync(NefsArchiveSource source, NefsProgress p);
28 | }
29 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version150;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder150(NefsHeader150 header, ILogger logger)
10 | : NefsItemListBuilder150Base(header, logger)
11 | {
12 | ///
13 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
14 | {
15 | var id = new NefsItemId(entryIndex);
16 | var entry = Header.EntryTable.Entries[id.Index];
17 | var sharedEntryInfo = Header.SharedEntryInfoTable.Entries[Convert.ToInt32(entry.SharedInfo)];
18 | return BuildItem(id, entry, sharedEntryInfo, itemList);
19 | }
20 |
21 | ///
22 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
23 | {
24 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
25 | return (block.End, block.Transformation, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocEntryFlags150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header.Version150;
4 |
5 | [Flags]
6 | public enum NefsTocEntryFlags150 : ushort
7 | {
8 | ///
9 | /// No flags set.
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// Indicates if the entry's data has transforms applied (compressed, encrypted, etc).
15 | ///
16 | Transformed = 1 << 0,
17 |
18 | ///
19 | /// Indicates if the entry is a directory.
20 | ///
21 | Directory = 1 << 1,
22 |
23 | ///
24 | /// Indicates if the entry has duplicates.
25 | ///
26 | Duplicated = 1 << 2,
27 |
28 | ///
29 | /// Indicates if the entry is cacheable by game engine.
30 | ///
31 | Cacheable = 1 << 3,
32 |
33 | ///
34 | /// Indicates if the entry is the last sibling in the directory.
35 | ///
36 | LastSibling = 1 << 4,
37 |
38 | ///
39 | /// Indicates if item is patched by game engine.
40 | ///
41 | Patched = 1 << 5
42 | }
43 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/LogHelper.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using Microsoft.Extensions.Logging.Abstractions;
5 | using System.Runtime.CompilerServices;
6 |
7 | namespace VictorBush.Ego.NefsEdit.Utility;
8 |
9 | ///
10 | /// Log utilities.
11 | ///
12 | public class LogHelper
13 | {
14 | private static ILoggerFactory? loggerFactory;
15 |
16 | ///
17 | /// The logger factory to use.
18 | ///
19 | public static ILoggerFactory LoggerFactory
20 | {
21 | get
22 | {
23 | if (loggerFactory == null)
24 | {
25 | return new NullLoggerFactory();
26 | }
27 |
28 | return loggerFactory;
29 | }
30 |
31 | set
32 | {
33 | loggerFactory = value;
34 | }
35 | }
36 |
37 | ///
38 | /// Gets a logger.
39 | ///
40 | /// Name of file the logger is for.
41 | /// The log instance.
42 | public static ILogger GetLogger([CallerFilePath] string filename = "")
43 | {
44 | return LoggerFactory.CreateLogger(filename);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Progress/NefsProgressEventArgs.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Progress;
4 |
5 | ///
6 | /// Progress report information.
7 | ///
8 | public sealed class NefsProgressEventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// Current progress.
14 | /// Current message.
15 | /// Current sub-message.
16 | public NefsProgressEventArgs(float progress, string message, string subMessage)
17 | {
18 | Progress = progress;
19 | Message = message;
20 | SubMessage = subMessage;
21 | }
22 |
23 | ///
24 | /// Current status message.
25 | ///
26 | public string Message { get; }
27 |
28 | ///
29 | /// Percent complete in range [0.0, 1.0].
30 | ///
31 | public float Progress { get; }
32 |
33 | ///
34 | /// Current status sub-message.
35 | ///
36 | public string SubMessage { get; }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 victorbush
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/NefsLog.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using Microsoft.Extensions.Logging.Abstractions;
5 | using System.Runtime.CompilerServices;
6 |
7 | namespace VictorBush.Ego.NefsLib;
8 |
9 | ///
10 | /// Library configuration.
11 | ///
12 | public static class NefsLog
13 | {
14 | private static ILoggerFactory? logFactory;
15 |
16 | ///
17 | /// Gets or sets the logger factory used by the library.
18 | ///
19 | public static ILoggerFactory LoggerFactory
20 | {
21 | get
22 | {
23 | if (logFactory == null)
24 | {
25 | logFactory = new NullLoggerFactory();
26 | }
27 |
28 | return logFactory;
29 | }
30 |
31 | set
32 | {
33 | logFactory = value;
34 | }
35 | }
36 |
37 | ///
38 | /// Gets a logger.
39 | ///
40 | /// Name of file the logger is for.
41 | /// The log instance.
42 | public static ILogger GetLogger([CallerFilePath] string filename = "")
43 | {
44 | return LoggerFactory.CreateLogger(filename);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version130/NefsTocHeader130.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version130;
7 |
8 | public struct NefsTocHeader130 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Magic;
14 | public uint TocSize;
15 | public uint Version;
16 | public uint NumVolumes;
17 | public uint BlockSize;
18 | public uint SplitSize;
19 | public uint EntryTableStart;
20 | public uint LinkTableStart;
21 | public uint NameTableStart;
22 | public uint BlockTableStart;
23 | public uint VolumeSizeTableStart;
24 | public uint VolumeNameStartTableStart;
25 | public uint VolumeNameTableStart;
26 | public AesKeyHexBuffer AesKey;
27 |
28 | public unsafe void ReverseEndianness()
29 | {
30 | var buffer = new Span(Unsafe.AsPointer(ref this), 13);
31 | for (var i = 0; i < buffer.Length; ++i)
32 | {
33 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder151.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version150;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder151(NefsHeader151 header, ILogger logger) :
10 | NefsItemListBuilder150Base(header, logger)
11 | {
12 | ///
13 | protected override bool SupportsBlockChecksum => true;
14 |
15 | ///
16 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
17 | {
18 | var id = new NefsItemId(entryIndex);
19 | var entry = Header.EntryTable.Entries[id.Index];
20 | var sharedEntryInfo = Header.SharedEntryInfoTable.Entries[Convert.ToInt32(entry.SharedInfo)];
21 | return BuildItem(id, entry, sharedEntryInfo, itemList);
22 | }
23 |
24 | ///
25 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
26 | {
27 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
28 | return (block.End, block.Transformation, block.Checksum);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/TestArchives/TestArchiveNoItems.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header;
4 | using VictorBush.Ego.NefsLib.Header.Builder;
5 | using VictorBush.Ego.NefsLib.Header.Version160;
6 | using VictorBush.Ego.NefsLib.Header.Version200;
7 | using VictorBush.Ego.NefsLib.Item;
8 | using VictorBush.Ego.NefsLib.Progress;
9 | using Xunit;
10 |
11 | namespace VictorBush.Ego.NefsLib.Tests.TestArchives;
12 |
13 | internal class TestArchiveNoItems
14 | {
15 | ///
16 | /// Creates a test archive. Does not write an archive to disk. Just creates a object.
17 | ///
18 | /// The file path to use for the archive.
19 | /// A .
20 | public static NefsArchive Create(string filePath)
21 | {
22 | var items = new NefsItemList(filePath);
23 |
24 | Assert.Empty(items.EnumerateById());
25 | Assert.Empty(items.EnumerateDepthFirstByName());
26 |
27 | var builder = new NefsHeaderBuilder200();
28 | var header = builder.Build(new NefsHeader200(), items, new NefsProgress());
29 |
30 | return new NefsArchive(header, items);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/RemoveFileDuplicatesCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Item;
4 |
5 | namespace VictorBush.Ego.NefsEdit.Commands;
6 |
7 | ///
8 | /// Command to remove an item from the archive.
9 | ///
10 | internal class RemoveFileDuplicatesCommand : INefsEditCommand
11 | {
12 | ///
13 | /// The individual commands that make up this command.
14 | ///
15 | public IReadOnlyList Commands { get; }
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | /// The item and its duplicates to remove.
21 | public RemoveFileDuplicatesCommand(IReadOnlyList duplicates)
22 | {
23 | Commands = duplicates.Select(x => new RemoveFileCommand(x)).ToArray();
24 | }
25 |
26 | ///
27 | public void Do()
28 | {
29 | foreach (var command in Commands)
30 | {
31 | command.Do();
32 | }
33 | }
34 |
35 | ///
36 | public void Undo()
37 | {
38 | foreach (var command in Commands)
39 | {
40 | command.Undo();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace VictorBush.Ego.NefsEdit.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/Item/NefsItemTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header.Builder;
4 | using VictorBush.Ego.NefsLib.Header.Version200;
5 | using VictorBush.Ego.NefsLib.Item;
6 | using VictorBush.Ego.NefsLib.Tests.TestArchives;
7 | using Xunit;
8 |
9 | namespace VictorBush.Ego.NefsLib.Tests.Item;
10 |
11 | public class NefsItemTests
12 | {
13 | [Fact]
14 | public void Clone_ItemCloned()
15 | {
16 | var nefs = TestArchiveNotModified.Create(@"C:\archive.nefs");
17 | var builder = new NefsItemListBuilder200((NefsHeader200)nefs.Header, NefsLog.GetLogger());
18 | var item = builder.BuildItem(TestArchiveNotModified.File3ItemId, nefs.Items);
19 | item.UpdateState(NefsItemState.Replaced);
20 |
21 | var clone = item with {};
22 |
23 | Assert.Equal(item.CompressedSize, clone.CompressedSize);
24 | Assert.Same(item.DataSource, clone.DataSource);
25 | Assert.Equal(item.DirectoryId, clone.DirectoryId);
26 | Assert.Equal(item.ExtractedSize, clone.ExtractedSize);
27 | Assert.Equal(item.FileName, clone.FileName);
28 | Assert.Equal(item.Id, clone.Id);
29 | Assert.Equal(item.State, clone.State);
30 | Assert.Equal(item.Type, clone.Type);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version140/NefsTocHeader140.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version140;
7 |
8 | public struct NefsTocHeader140 : INefsTocData
9 | {
10 | ///
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Magic;
14 | public uint TocSize;
15 | public uint Version;
16 | public uint NumVolumes;
17 | public uint BlockSize;
18 | public uint SplitSize;
19 | public uint EntryTableStart;
20 | public uint LinkTableStart;
21 | public uint NameTableStart;
22 | public uint BlockTableStart;
23 | public uint VolumeSizeTableStart;
24 | public uint VolumeNameStartTableStart;
25 | public uint VolumeNameTableStart;
26 | public uint VolumeDataStartTableStart;
27 | public uint Unknown;
28 | public AesKeyHexBuffer AesKey;
29 |
30 | public unsafe void ReverseEndianness()
31 | {
32 | var buffer = new Span(Unsafe.AsPointer(ref this), 15);
33 | for (var i = 0; i < buffer.Length; ++i)
34 | {
35 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocSharedEntryInfo150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocSharedEntryInfo150 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint Parent;
13 | public uint NextSibling;
14 | public uint FirstChild;
15 | public uint NameOffset;
16 | public uint Size;
17 | public uint FirstDuplicate;
18 | public uint PatchedEntry;
19 |
20 | public void ReverseEndianness()
21 | {
22 | this.Parent = BinaryPrimitives.ReverseEndianness(this.Parent);
23 | this.NextSibling = BinaryPrimitives.ReverseEndianness(this.NextSibling);
24 | this.FirstChild = BinaryPrimitives.ReverseEndianness(this.FirstChild);
25 | this.NameOffset = BinaryPrimitives.ReverseEndianness(this.NameOffset);
26 | this.Size = BinaryPrimitives.ReverseEndianness(this.Size);
27 | this.FirstDuplicate = BinaryPrimitives.ReverseEndianness(this.FirstDuplicate);
28 | this.PatchedEntry = BinaryPrimitives.ReverseEndianness(this.PatchedEntry);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocEntry160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version160;
7 |
8 | public struct NefsTocEntry160 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | // likely split for proper contiguous struct alignment
13 | public uint StartA;
14 | public uint StartB;
15 | public uint SharedInfo;
16 | public uint FirstBlock;
17 | public uint NextDuplicate;
18 |
19 | public ulong Start
20 | {
21 | get => this.StartA | ((ulong)this.StartB << 32);
22 | set
23 | {
24 | this.StartA = (uint)value;
25 | this.StartB = (uint)(value >> 32);
26 | }
27 | }
28 |
29 | public void ReverseEndianness()
30 | {
31 | this.StartA = BinaryPrimitives.ReverseEndianness(this.StartA);
32 | this.StartB = BinaryPrimitives.ReverseEndianness(this.StartB);
33 | (this.StartA, this.StartB) = (this.StartB, this.StartA);
34 | this.SharedInfo = BinaryPrimitives.ReverseEndianness(this.SharedInfo);
35 | this.FirstBlock = BinaryPrimitives.ReverseEndianness(this.FirstBlock);
36 | this.NextDuplicate = BinaryPrimitives.ReverseEndianness(this.NextDuplicate);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/RemoveFileCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Item;
4 |
5 | namespace VictorBush.Ego.NefsEdit.Commands;
6 |
7 | ///
8 | /// Command to remove an item from the archive.
9 | ///
10 | internal class RemoveFileCommand : INefsEditCommand
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The item to remove.
16 | public RemoveFileCommand(NefsItem item)
17 | {
18 | Item = item ?? throw new ArgumentNullException(nameof(item));
19 | OldState = item.State;
20 | NewState = NefsItemState.Removed;
21 | }
22 |
23 | ///
24 | /// Gets the item the action is performed on.
25 | ///
26 | public NefsItem Item { get; }
27 |
28 | ///
29 | /// Gets the new item state.
30 | ///
31 | public NefsItemState NewState { get; }
32 |
33 | ///
34 | /// Gets the old item state.
35 | ///
36 | public NefsItemState OldState { get; }
37 |
38 | ///
39 | public void Do()
40 | {
41 | Item.UpdateState(NewState);
42 | }
43 |
44 | ///
45 | public void Undo()
46 | {
47 | Item.UpdateState(OldState);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsTocHeader151.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version150;
7 |
8 | public struct NefsTocHeader151 : INefsTocData
9 | {
10 | public static int ByteCount { get; } = Unsafe.SizeOf();
11 |
12 | public uint Magic = NefsConstants.FourCc;
13 | public uint TocSize;
14 | public uint Version;
15 | public uint NumVolumes;
16 | public uint NumEntries;
17 | public uint BlockSize;
18 | public uint SplitSize;
19 | public uint EntryTableStart;
20 | public uint SharedEntryInfoTableStart;
21 | public uint NameTableStart;
22 | public uint BlockTableStart;
23 | public uint VolumeInfoTableStart;
24 | public uint UserValue;
25 | public uint RandomPadding1;
26 | public uint Checksum;
27 | public AesKeyHexBuffer AesKey;
28 | public uint Unused;
29 |
30 | public NefsTocHeader151()
31 | {
32 | }
33 |
34 | public unsafe void ReverseEndianness()
35 | {
36 | var buffer = new Span(Unsafe.AsPointer(ref this), 15);
37 | for (var i = 0; i < buffer.Length; ++i)
38 | {
39 | buffer[i] = BinaryPrimitives.ReverseEndianness(buffer[i]);
40 | }
41 |
42 | // Leave Unused alone since it's not part of header
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/ReplaceFileDuplicatesCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.DataSource;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Commands;
7 |
8 | ///
9 | /// Command to replace an item's data source.
10 | ///
11 | internal class ReplaceFileDuplicatesCommand : INefsEditCommand
12 | {
13 | ///
14 | /// The individual commands that make up this command.
15 | ///
16 | public IReadOnlyList Commands { get; }
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The item and its duplicates to replace.
22 | /// The new data source.
23 | public ReplaceFileDuplicatesCommand(IReadOnlyList duplicates, INefsDataSource newDataSource)
24 | {
25 | Commands = duplicates.Select(x => new ReplaceFileCommand(x, newDataSource)).ToArray();
26 | }
27 |
28 | ///
29 | public void Do()
30 | {
31 | foreach (var command in Commands)
32 | {
33 | command.Do();
34 | }
35 | }
36 |
37 | ///
38 | public void Undo()
39 | {
40 | foreach (var command in Commands)
41 | {
42 | command.Undo();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsLib.Header.Version010;
5 | using VictorBush.Ego.NefsLib.Item;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Builder;
8 |
9 | internal class NefsItemListBuilder010(NefsHeader010 header, ILogger logger)
10 | : NefsItemListBuilder010Base(header, logger)
11 | {
12 | ///
13 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList itemList)
14 | {
15 | var id = new NefsItemId(entryIndex);
16 | var entry = Header.EntryTable.Entries[id.Index];
17 | var link = Header.LinkTable.GetEntryByOffset(entry.LinkOffset);
18 | return BuildItem(id, entry, link, Header.EntryTable.Entries, itemList);
19 | }
20 |
21 | ///
22 | protected override (uint End, uint Transformation, ushort Checksum) GetBlock(uint blockIndex)
23 | {
24 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)];
25 | return (block.End, block.Transformation, 0);
26 | }
27 |
28 | ///
29 | protected override NefsDataTransformType GetTransformType(uint blockTransformation)
30 | {
31 | return blockTransformation switch
32 | {
33 | 0 => NefsDataTransformType.None,
34 | 1 => NefsDataTransformType.Lzss,
35 | _ => (NefsDataTransformType)(-1)
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/AesKeyHexBuffer.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Runtime.CompilerServices;
4 | using System.Text;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header;
7 |
8 | [InlineArray(Size)]
9 | public struct AesKeyHexBuffer
10 | {
11 | ///
12 | /// The size of the buffer in bytes.
13 | ///
14 | public const int Size = 0x40;
15 |
16 | private byte element;
17 |
18 | ///
19 | /// Initializes a new instance of the struct.
20 | ///
21 | /// A string of hex data.
22 | public AesKeyHexBuffer(string hexString)
23 | {
24 | const string msg = "The hex string must have exactly 64 characters representing hex digits.";
25 | if (hexString.Length != Size)
26 | {
27 | throw new ArgumentException(msg);
28 | }
29 |
30 | var bytesEncoded = Encoding.ASCII.GetBytes(hexString, this);
31 | if (hexString.Length != bytesEncoded)
32 | {
33 | throw new ArgumentException(msg);
34 | }
35 | }
36 |
37 | ///
38 | /// Gets the AES-256 key for this header.
39 | ///
40 | /// A byte array with the AES key.
41 | public byte[] GetAesKey()
42 | {
43 | return Convert.FromHexString(ToString());
44 | }
45 |
46 | public override string ToString()
47 | {
48 | return Encoding.ASCII.GetString(this);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/VictorBush.Ego.NefsEdit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | WinExe
5 | true
6 | NefsEdit
7 | true
8 | enable
9 |
10 | NeFS Edit
11 |
12 |
13 |
14 | Component
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Always
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/INefsTocTable.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Header;
4 |
5 | ///
6 | /// A table in the NeFS table of contents header.
7 | ///
8 | public interface INefsTocTable where T : unmanaged, INefsTocData
9 | {
10 | ///
11 | /// The table's entries.
12 | ///
13 | IReadOnlyList Entries { get; }
14 |
15 | ///
16 | /// The size of the table in bytes.
17 | ///
18 | int ByteCount => Entries.Count * T.ByteCount;
19 |
20 | ///
21 | /// The size of an entry in bytes.
22 | ///
23 | static int EntryByteCount => T.ByteCount;
24 | }
25 |
26 | ///
27 | /// A table in the NeFS table of contents header.
28 | ///
29 | public interface INefsTocTable : INefsTocTable
30 | where T : INefsTocTable
31 | where TData : unmanaged, INefsTocData
32 | {
33 | ///
34 | /// Creates a table with the given entries.
35 | ///
36 | internal static abstract T Create(IReadOnlyList entries);
37 | }
38 |
39 | public static class NefsTocTableExtensions
40 | {
41 | ///
42 | /// The size of the table in bytes.
43 | ///
44 | public static int ByteCount(this INefsTocTable table)
45 | where T : unmanaged, INefsTocData
46 | {
47 | return table.ByteCount;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Settings/Settings.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Settings;
4 |
5 | ///
6 | /// Settings.
7 | ///
8 | [Serializable]
9 | public class Settings
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | public Settings()
15 | {
16 | RecentFiles = new List();
17 | OpenFileDialogState = new OpenFileDialogState();
18 | Dirt4Dir = "";
19 | DirtRally1Dir = "";
20 | DirtRally2Dir = "";
21 | QuickExtractDir = "";
22 | }
23 |
24 | public bool CheckForDatabaseUpdatesOnStartup { get; set; } = true;
25 |
26 | ///
27 | /// The directory for DiRT 4.
28 | ///
29 | public string Dirt4Dir { get; set; }
30 |
31 | ///
32 | /// Gets or sets the DiRT Rally 1 directory.
33 | ///
34 | public string DirtRally1Dir { get; set; }
35 |
36 | ///
37 | /// The directory for DiRT Rally 2.
38 | ///
39 | public string DirtRally2Dir { get; set; }
40 |
41 | ///
42 | /// Gets or sets the last state info for the open file dialog.
43 | ///
44 | public OpenFileDialogState OpenFileDialogState { get; set; }
45 |
46 | ///
47 | /// Quick extract.
48 | ///
49 | public string QuickExtractDir { get; set; }
50 |
51 | ///
52 | /// Gets or sets the list of recently opened files.
53 | ///
54 | public List RecentFiles { get; set; }
55 | }
56 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Services/ProgressService.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using Microsoft.Extensions.Logging;
4 | using VictorBush.Ego.NefsEdit.UI;
5 | using VictorBush.Ego.NefsEdit.Utility;
6 | using VictorBush.Ego.NefsLib.Progress;
7 |
8 | namespace VictorBush.Ego.NefsEdit.Services;
9 |
10 | ///
11 | /// Progress service implementation.
12 | ///
13 | internal class ProgressService : IProgressService
14 | {
15 | private static readonly ILogger Log = LogHelper.GetLogger();
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | /// The UI service to use.
21 | public ProgressService(IUiService uiService)
22 | {
23 | UiService = uiService ?? throw new ArgumentNullException(nameof(uiService));
24 | }
25 |
26 | private IUiService UiService { get; }
27 |
28 | ///
29 | public async Task RunModalTaskAsync(Func task)
30 | {
31 | // Create a progress dialog
32 | var progressForm = new ProgressDialogForm(UiService);
33 |
34 | // Show the progress dialog. Don't await this call. Need to allow dialog to show modally, but want to continue execution.
35 | var progressFormTask = progressForm.ShowDialogAsync();
36 |
37 | // Run the task
38 | try
39 | {
40 | await task(progressForm.ProgressInfo);
41 | }
42 | catch (Exception ex)
43 | {
44 | Log.LogError(ex.Message);
45 | }
46 |
47 | // Close the progress dialog
48 | progressForm.Close();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version160/NefsTocHeaderA160.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 | using VictorBush.Ego.NefsLib.Utility;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Version160;
8 |
9 | public struct NefsTocHeaderA160 : INefsTocData
10 | {
11 | public static int ByteCount { get; } = Unsafe.SizeOf();
12 |
13 | public uint Magic = NefsConstants.FourCc;
14 | public Sha256Hash Hash;
15 | public AesKeyHexBuffer AesKey;
16 | public uint TocSize;
17 | public uint Version;
18 | public uint NumEntries;
19 | public uint UserValue;
20 | public uint RandomPadding1;
21 | public uint RandomPadding2;
22 | public ushort RandomPadding3;
23 | public ushort Unused;
24 |
25 | public unsafe byte[] RandomPadding
26 | {
27 | get
28 | {
29 | var buffer = new ReadOnlySpan(Unsafe.AsPointer(ref this), ByteCount);
30 | return buffer[116..ByteCount].ToArray();
31 | }
32 | set
33 | {
34 | var buffer = new Span(Unsafe.AsPointer(ref this), ByteCount);
35 | value.CopyTo(buffer[116..ByteCount]);
36 | }
37 | }
38 |
39 | public NefsTocHeaderA160()
40 | {
41 | }
42 |
43 | public void ReverseEndianness()
44 | {
45 | this.Magic = BinaryPrimitives.ReverseEndianness(this.Magic);
46 | this.TocSize = BinaryPrimitives.ReverseEndianness(this.TocSize);
47 | this.Version = BinaryPrimitives.ReverseEndianness(this.Version);
48 | this.NumEntries = BinaryPrimitives.ReverseEndianness(this.NumEntries);
49 | this.UserValue = BinaryPrimitives.ReverseEndianness(this.UserValue);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/NefsVersion.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib;
4 |
5 | ///
6 | /// Supported version numbers.
7 | ///
8 | public enum NefsVersion
9 | {
10 | ///
11 | /// Version 0.1.0.
12 | ///
13 | Version010 = 0x00100,
14 |
15 | ///
16 | /// Version 0.2.0.
17 | ///
18 | Version020 = 0x00200,
19 |
20 | ///
21 | /// Version 1.3.0.
22 | ///
23 | Version130 = 0x10300,
24 |
25 | ///
26 | /// Version 1.4.0.
27 | ///
28 | Version140 = 0x10400,
29 |
30 | ///
31 | /// Version 1.5.0.
32 | ///
33 | Version150 = 0x10500,
34 |
35 | ///
36 | /// Version 1.5.1.
37 | ///
38 | Version151 = 0x10501,
39 |
40 | ///
41 | /// Version 1.6.0.
42 | ///
43 | Version160 = 0x10600,
44 |
45 | ///
46 | /// Version 2.0.0.
47 | ///
48 | Version200 = 0x20000,
49 | }
50 |
51 | public static class NefsVersionExtensions
52 | {
53 | public static string ToPrettyString(this NefsVersion version)
54 | {
55 | return version switch
56 | {
57 | NefsVersion.Version010 => "Version 0.1.0",
58 | NefsVersion.Version020 => "Version 0.2.0",
59 | NefsVersion.Version130 => "Version 1.3.0",
60 | NefsVersion.Version140 => "Version 1.4.0",
61 | NefsVersion.Version150 => "Version 1.5.0",
62 | NefsVersion.Version151 => "Version 1.5.1",
63 | NefsVersion.Version160 => "Version 1.6.0",
64 | NefsVersion.Version200 => "Version 2.0.0",
65 | _ => $"Version 0x{version:X}"
66 | };
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Item/NefsItemId.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Item;
4 |
5 | ///
6 | /// A (kind of) unique identifier for an item in a NeFS archive. Used to identify parent/sibling relationships. There
7 | /// can be items with duplicate ids in an archive though.
8 | ///
9 | public readonly record struct NefsItemId : IComparable
10 | {
11 | ///
12 | /// Initializes a new instance of the struct.
13 | ///
14 | /// The value of the id.
15 | public NefsItemId(uint id) : this(Convert.ToInt32(id))
16 | {
17 | }
18 |
19 | ///
20 | /// Initializes a new instance of the struct.
21 | ///
22 | /// The value of the index.
23 | public NefsItemId(int index)
24 | {
25 | Index = index;
26 | }
27 |
28 | ///
29 | /// Gets or sets the value of this id.
30 | ///
31 | public uint Value => (uint)Index;
32 |
33 | ///
34 | /// Gets the id in index form.
35 | ///
36 | public int Index { get; }
37 |
38 | ///
39 | public int CompareTo(NefsItemId other)
40 | {
41 | return Index.CompareTo(other.Index);
42 | }
43 |
44 | ///
45 | public override string ToString()
46 | {
47 | return Index.ToString();
48 | }
49 |
50 | public static bool operator >(NefsItemId x, NefsItemId y)
51 | {
52 | return x.Index > y.Index;
53 | }
54 |
55 | public static bool operator <(NefsItemId x, NefsItemId y)
56 | {
57 | return x.Index < y.Index;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Settings/OpenFileDialogState.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Settings;
4 |
5 | ///
6 | /// Stores recent state of open file dialog so the user can quickly reopen files.
7 | ///
8 | [Serializable]
9 | public class OpenFileDialogState
10 | {
11 | ///
12 | /// [GameDat] - The path to the data file.
13 | ///
14 | public string GameDatDataFilePath { get; set; } = "";
15 |
16 | ///
17 | /// [GameDat] - The path to the header file.
18 | ///
19 | public string GameDatHeaderFilePath { get; set; } = "";
20 |
21 | ///
22 | /// [GameDat] - The primary offset value.
23 | ///
24 | public string GameDatPrimaryOffset { get; set; } = "";
25 |
26 | ///
27 | /// [GameDat] - The primary size value.
28 | ///
29 | public string GameDatPrimarySize { get; set; } = "";
30 |
31 | ///
32 | /// [GameDat] - The secondary offset value.
33 | ///
34 | public string GameDatSecondaryOffset { get; set; } = "";
35 |
36 | ///
37 | /// [GameDat] - The secondary size value.
38 | ///
39 | public string GameDatSecondarySize { get; set; } = "";
40 |
41 | ///
42 | /// Gets or sets which mode the open file dialog was last in.
43 | ///
44 | public int LastMode { get; set; }
45 |
46 | ///
47 | /// [NeFS] - The path to the data file.
48 | ///
49 | public string NefsFilePath { get; set; } = "";
50 |
51 | public string HeadlessExePath { get; set; } = "";
52 |
53 | public string HeadlessDataDirPath { get; set; } = "";
54 | }
55 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/DataSource/NefsDataChunkTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.DataSource;
4 | using Xunit;
5 |
6 | namespace VictorBush.Ego.NefsLib.Tests.DataSource;
7 |
8 | public sealed class NefsDataChunkTests
9 | {
10 | [Fact]
11 | public void CreateChunkList_EmptyCumulativeSizes_EmptyListReturned()
12 | {
13 | var result = NefsDataChunk.CreateChunkList(Array.Empty(), new NefsDataTransform(0x10));
14 | Assert.Empty(result);
15 | }
16 |
17 | [Fact]
18 | public void CreateChunkList_MultipleCumulativeSize_MultipleChunksReturned()
19 | {
20 | var transform = new NefsDataTransform(0x50);
21 | var result = NefsDataChunk.CreateChunkList(new[] { 0x10u, 0x20u, 0x2Bu }, transform);
22 | Assert.Equal(3, result.Count);
23 |
24 | Assert.Equal(0x10u, result[0].Size);
25 | Assert.Equal(0x10u, result[0].CumulativeSize);
26 | Assert.Equal(transform, result[0].Transform);
27 |
28 | Assert.Equal(0x10u, result[1].Size);
29 | Assert.Equal(0x20u, result[1].CumulativeSize);
30 | Assert.Equal(transform, result[1].Transform);
31 |
32 | Assert.Equal(0xBu, result[2].Size);
33 | Assert.Equal(0x2Bu, result[2].CumulativeSize);
34 | Assert.Equal(transform, result[2].Transform);
35 | }
36 |
37 | [Fact]
38 | public void CreateChunkList_SingleCumulativeSize_SingleChunkReturned()
39 | {
40 | var transform = new NefsDataTransform(0x10);
41 | var result = NefsDataChunk.CreateChunkList(new[] { 0x10u }, transform);
42 | Assert.Single(result);
43 | Assert.Equal(0x10u, result[0].Size);
44 | Assert.Equal(0x10u, result[0].CumulativeSize);
45 | Assert.Equal(transform, result[0].Transform);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/Constants.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsEdit.Utility;
4 |
5 | ///
6 | /// Constant values.
7 | ///
8 | internal class Constants
9 | {
10 | ///
11 | /// Default executable name for DiRT 4.
12 | ///
13 | public const string Dirt4ExeName = "dirt4.exe";
14 |
15 | ///
16 | /// Default game.dat directory for DiRT 4.
17 | ///
18 | public const string Dirt4GameDatPath = "";
19 |
20 | ///
21 | /// Default path within the steam folder for DiRT 4.
22 | ///
23 | public const string Dirt4SteamPath = @"steamapps\common\DiRT 4";
24 |
25 | ///
26 | /// Default executable name for DiRT Rally.
27 | ///
28 | public const string DirtRally1ExeName = "drt.exe";
29 |
30 | ///
31 | /// Default game.bin directory for DiRT Rally.
32 | ///
33 | public const string DirtRally1GameBinPath = "";
34 |
35 | ///
36 | /// Default path within the steam folder for DiRT Rally.
37 | ///
38 | public const string DirtRally1SteamPath = @"steamapps\common\DiRT Rally";
39 |
40 | ///
41 | /// Default executable name for DiRT Rally 2.
42 | ///
43 | public const string DirtRally2ExeName = "dirtrally2.exe";
44 |
45 | ///
46 | /// Default game.dat directory for DiRT Rally 2.
47 | ///
48 | public const string DirtRally2GameDatPath = "game";
49 |
50 | ///
51 | /// Default path within the steam folder for DiRT Rally 2.
52 | ///
53 | public const string DirtRally2SteamPath = @"steamapps\common\DiRT Rally 2.0";
54 | }
55 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/INefsDataSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Abstractions;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsLib.DataSource;
7 |
8 | ///
9 | /// Defines the source of file data for an item in an archive.
10 | ///
11 | public interface INefsDataSource
12 | {
13 | ///
14 | /// The path of the file that contains the item's data.
15 | ///
16 | string FilePath { get; }
17 |
18 | ///
19 | /// A value indicating whether the data in this data source has had any applicable transformations applied. When
20 | /// replacing a file in an archive, the replacement data source will most likely set this to false. When the archive
21 | /// is saved, the transformation is applied.
22 | ///
23 | bool IsTransformed { get; }
24 |
25 | ///
26 | /// The offset in the source file where the data begins.
27 | ///
28 | long Offset { get; }
29 |
30 | ///
31 | /// The size information about the source data.
32 | ///
33 | NefsItemSize Size { get; }
34 |
35 | ///
36 | /// Opens the item data source stream for reading.
37 | ///
38 | /// The file system.
39 | /// The data source stream for reading.
40 | Stream OpenRead(IFileSystem fileSystem);
41 |
42 | ///
43 | /// Determines whether the item's data source files exist.
44 | ///
45 | /// The file system.
46 | /// True if the data source files exist, otherwise false.
47 | bool Exists(IFileSystem fileSystem);
48 | }
49 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/FileSystemExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Abstractions;
4 | using VictorBush.Ego.NefsLib.DataSource;
5 |
6 | namespace VictorBush.Ego.NefsLib.Utility;
7 |
8 | ///
9 | /// Extensions for .
10 | ///
11 | public static class FileSystemExtensions
12 | {
13 | ///
14 | /// Deletes the specified directory if it exists, then creates it.
15 | ///
16 | /// The file system.
17 | /// The directory path.
18 | public static void ResetOrCreateDirectory(this IFileSystem fs, string directoryPath)
19 | {
20 | if (fs.Directory.Exists(directoryPath))
21 | {
22 | fs.Directory.Delete(directoryPath, true);
23 | }
24 |
25 | fs.Directory.CreateDirectory(directoryPath);
26 | }
27 |
28 | ///
29 | /// Opens the item data source stream for reading.
30 | ///
31 | /// The file system.
32 | /// The data source.
33 | /// The data source stream for reading.
34 | public static Stream OpenRead(this IFileSystem fs, INefsDataSource dataSource)
35 | {
36 | return dataSource.OpenRead(fs);
37 | }
38 |
39 | ///
40 | /// Determines whether the item's data source files exist.
41 | ///
42 | /// The file system.
43 | /// The data source.
44 | /// True if the data source files exist, otherwise false.
45 | public static bool Exists(this IFileSystem fs, INefsDataSource dataSource)
46 | {
47 | return dataSource.Exists(fs);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Utility/RichTextWriter.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Utility;
7 |
8 | ///
9 | /// Provides ability for a TextWriter to write to a RichTextBox control.
10 | ///
11 | public class RichTextWriter : TextWriter
12 | {
13 | private RichTextBox textbox;
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | /// The textbox.
19 | public RichTextWriter(RichTextBox textbox)
20 | {
21 | this.textbox = textbox;
22 | }
23 |
24 | ///
25 | public override Encoding Encoding
26 | {
27 | get { return Encoding.ASCII; }
28 | }
29 |
30 | ///
31 | public override void Write(char value)
32 | {
33 | DoWrite(value.ToString());
34 | }
35 |
36 | ///
37 | public override void Write(string? value)
38 | {
39 | DoWrite(value);
40 | }
41 |
42 | ///
43 | /// Writes the provided value into the RichTextBox.
44 | ///
45 | /// The string to write.
46 | private void DoWrite(string? value)
47 | {
48 | if (this.textbox.IsHandleCreated)
49 | {
50 | // Window handle created, do an invoke. Invoke required to allow thread-safe modification of the textbox.
51 | this.textbox.Invoke((MethodInvoker)(() =>
52 | {
53 | this.textbox.Text += value;
54 | this.textbox.Select(this.textbox.Text.Length - 1, 0);
55 | this.textbox.ScrollToCaret();
56 | }));
57 | }
58 | else
59 | {
60 | // Window handle not yet created, just modify directly
61 | this.textbox.Text += value;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/Sha256Hash.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace VictorBush.Ego.NefsLib.Utility;
6 |
7 | ///
8 | /// A SHA-256 hash value.
9 | ///
10 | [InlineArray(Size)]
11 | public struct Sha256Hash : IEquatable
12 | {
13 | ///
14 | /// The size of a hash value in bytes.
15 | ///
16 | public const int Size = 0x20;
17 |
18 | private byte element;
19 |
20 | ///
21 | /// Initializes a new instance of the struct.
22 | ///
23 | /// The value of the hash.
24 | public Sha256Hash(byte[] value)
25 | {
26 | if (value.Length != Size)
27 | {
28 | throw new ArgumentException("The SHA-256 hash value must be 32 bytes.");
29 | }
30 |
31 | value.CopyTo(this);
32 | }
33 |
34 | public static bool operator !=(Sha256Hash left, Sha256Hash right)
35 | {
36 | return !(left == right);
37 | }
38 |
39 | public static bool operator ==(Sha256Hash left, Sha256Hash right)
40 | {
41 | return left.Equals(right);
42 | }
43 |
44 | ///
45 | public bool Equals(Sha256Hash other)
46 | {
47 | ReadOnlySpan self = this;
48 | return self.SequenceEqual(other);
49 | }
50 |
51 | ///
52 | public override bool Equals(object? obj)
53 | {
54 | return obj is Sha256Hash other && Equals(other);
55 | }
56 |
57 | ///
58 | public override int GetHashCode()
59 | {
60 | var h = new HashCode();
61 | h.AddBytes(this);
62 | return h.ToHashCode();
63 | }
64 |
65 | ///
66 | public override string ToString()
67 | {
68 | return Convert.ToHexString(this);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/NefsFileDataSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Abstractions;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsLib.DataSource;
7 |
8 | ///
9 | /// Defines an item data source from a file on disk.
10 | ///
11 | public class NefsFileDataSource : INefsDataSource
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The path of the file that contain's the data.
17 | /// The offset in the source file where the data begins.
18 | /// Size information about the item's data.
19 | ///
20 | /// Whether the data in this data source has already been transformed (encrypted, compressed, etc).
21 | ///
22 | public NefsFileDataSource(string filePath, long offset, NefsItemSize size, bool isTransformed)
23 | {
24 | FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
25 | Offset = offset;
26 | Size = size;
27 | IsTransformed = isTransformed;
28 | }
29 |
30 | ///
31 | public string FilePath { get; }
32 |
33 | ///
34 | public bool IsTransformed { get; }
35 |
36 | ///
37 | public long Offset { get; }
38 |
39 | ///
40 | public NefsItemSize Size { get; }
41 |
42 | ///
43 | public Stream OpenRead(IFileSystem fileSystem)
44 | {
45 | return fileSystem.File.OpenRead(FilePath);
46 | }
47 |
48 | ///
49 | public bool Exists(IFileSystem fileSystem)
50 | {
51 | return fileSystem.File.Exists(FilePath);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/IO/NefsReaderStrategy200Tests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header;
4 | using VictorBush.Ego.NefsLib.Header.Version160;
5 | using VictorBush.Ego.NefsLib.IO;
6 | using VictorBush.Ego.NefsLib.Progress;
7 | using Xunit;
8 |
9 | namespace VictorBush.Ego.NefsLib.Tests.IO;
10 |
11 | public class NefsReaderStrategy200Tests
12 | {
13 | private readonly NefsProgress p = new(CancellationToken.None);
14 |
15 | [Fact]
16 | public async Task ReadHeaderPart4Async_ValidData_DataRead()
17 | {
18 | // Setup data
19 | byte[] bytes =
20 | {
21 | // Offset
22 | 0xFF, 0xFF,
23 |
24 | // Item 1
25 | 0x11, 0x12, 0x13, 0x14,
26 | 0x15, 0x16, 0x17, 0x18,
27 |
28 | // Item 2
29 | 0x21, 0x22, 0x23, 0x24,
30 |
31 | // Item 5
32 | 0x31, 0x32, 0x33, 0x34,
33 | 0x35, 0x36, 0x37, 0x38,
34 | 0x39, 0x3A, 0x3B, 0x3C,
35 |
36 | // Last four bytes
37 | 0x01, 0x02, 0x03, 0x04,
38 | };
39 |
40 | var stream = new MemoryStream(bytes);
41 | using var br = new EndianBinaryReader(stream, true);
42 | var size = 28;
43 | var offset = 2;
44 |
45 | // Test
46 | var part4 = await NefsReaderStrategy200.ReadHeaderPart4Version20Async(br, offset, size, this.p);
47 |
48 | // Verify
49 | Assert.Equal(7, part4.Entries.Count);
50 |
51 | // Item 1
52 | Assert.Equal((uint)0x14131211, part4.Entries[0].End);
53 | Assert.Equal((uint)0x18171615, part4.Entries[1].End);
54 |
55 | // Item 2
56 | Assert.Equal((uint)0x24232221, part4.Entries[2].End);
57 |
58 | // Item 3
59 | Assert.Equal((uint)0x34333231, part4.Entries[3].End);
60 | Assert.Equal((uint)0x38373635, part4.Entries[4].End);
61 | Assert.Equal((uint)0x3C3B3A39, part4.Entries[5].End);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/NefsWriterStrategy160Base.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Security.Cryptography;
4 | using VictorBush.Ego.NefsLib.Header;
5 | using VictorBush.Ego.NefsLib.Progress;
6 | using VictorBush.Ego.NefsLib.Utility;
7 |
8 | namespace VictorBush.Ego.NefsLib.IO;
9 |
10 | internal abstract class NefsWriterStrategy160Base : NefsWriterStrategy
11 | where T : INefsHeader
12 | {
13 | ///
14 | /// Calculates the header hash.
15 | ///
16 | /// The writer with the stream containing the header.
17 | /// The offset to the header in the stream.
18 | /// The header size in bytes.
19 | /// Progress info.
20 | /// The header hash.
21 | protected static async Task ComputeHashAsync(
22 | EndianBinaryWriter writer,
23 | long primaryOffset,
24 | uint tocSize,
25 | NefsProgress p)
26 | {
27 | // The hash is of the entire header except for the expected hash
28 | var secondOffset = primaryOffset + 0x24;
29 | var headerSize = Convert.ToInt32(tocSize);
30 |
31 | // Seek to beginning of header
32 | var stream = writer.BaseStream;
33 | stream.Seek(primaryOffset, SeekOrigin.Begin);
34 |
35 | // Read magic num
36 | var dataToHash = new byte[headerSize - 0x20];
37 | await stream.ReadExactlyAsync(dataToHash, 0, 4).ConfigureAwait(false);
38 |
39 | // Skip expected hash and read rest of header
40 | stream.Seek(secondOffset, SeekOrigin.Begin);
41 | await stream.ReadExactlyAsync(dataToHash, 4, headerSize - 0x24).ConfigureAwait(false);
42 |
43 | // Compute the new expected hash
44 | return new Sha256Hash(SHA256.HashData(dataToHash));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Commands/ReplaceFileCommand.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.DataSource;
4 | using VictorBush.Ego.NefsLib.Item;
5 |
6 | namespace VictorBush.Ego.NefsEdit.Commands;
7 |
8 | ///
9 | /// Command to replace an item's data source.
10 | ///
11 | internal class ReplaceFileCommand : INefsEditCommand
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The item to replace.
17 | /// The new data source.
18 | public ReplaceFileCommand(NefsItem item, INefsDataSource newDataSource)
19 | {
20 | Item = item;
21 | OldDataSource = item.DataSource;
22 | OldState = item.State;
23 | NewDataSource = newDataSource;
24 | NewState = NefsItemState.Replaced;
25 | }
26 |
27 | ///
28 | /// Gets the item the action is performed on.
29 | ///
30 | public NefsItem Item { get; }
31 |
32 | ///
33 | /// Gets the new data source.
34 | ///
35 | public INefsDataSource NewDataSource { get; }
36 |
37 | ///
38 | /// Gets the new item state.
39 | ///
40 | public NefsItemState NewState { get; }
41 |
42 | ///
43 | /// Gets the old data source.
44 | ///
45 | public INefsDataSource OldDataSource { get; }
46 |
47 | ///
48 | /// Gets the old item state.
49 | ///
50 | public NefsItemState OldState { get; }
51 |
52 | ///
53 | public void Do()
54 | {
55 | Item.UpdateDataSource(NewDataSource, NewState);
56 | }
57 |
58 | ///
59 | public void Undo()
60 | {
61 | Item.UpdateDataSource(OldDataSource, OldState);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/StreamExtensions.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers;
4 |
5 | namespace VictorBush.Ego.NefsLib.Utility;
6 |
7 | ///
8 | /// Extension methods for .
9 | ///
10 | public static class StreamExtensions
11 | {
12 | ///
13 | /// The default value used Stream.CopyToAsync().
14 | ///
15 | public const int CopyBufferSize = 81920;
16 |
17 | ///
18 | /// Copies part of a stream to a destination stream.
19 | ///
20 | /// The input stream to copy from.
21 | /// The destination stream to write to.
22 | /// The number of bytes to copy.
23 | /// A cancellation token.
24 | /// An async task.
25 | public static async Task CopyPartialAsync(
26 | this Stream stream,
27 | Stream destination,
28 | long length,
29 | CancellationToken cancelToken)
30 | {
31 | // Use a temporary buffer to transfer chunks of data at a time
32 | var buffer = ArrayPool.Shared.Rent(CopyBufferSize);
33 | var bytesRemaining = length;
34 |
35 | try
36 | {
37 | while (bytesRemaining > 0)
38 | {
39 | // Read from input stream
40 | var bytesToRead = Math.Min(bytesRemaining, CopyBufferSize);
41 | var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, (int)bytesToRead), cancelToken);
42 | if (bytesRead == 0)
43 | {
44 | throw new EndOfStreamException();
45 | }
46 |
47 | // Copy to destination
48 | await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancelToken);
49 |
50 | bytesRemaining -= bytesRead;
51 | }
52 | }
53 | finally
54 | {
55 | ArrayPool.Shared.Return(buffer);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/README.md:
--------------------------------------------------------------------------------
1 | # NeFS Edit
2 |
3 | NeFS Edit is an application that supports browsing, extracting, and modifying contents of NeFS archives. These archive files are used by Ego Engine games such as DiRT 4 and DiRT Rally 2.
4 |
5 | Requires .NET 8 Desktop Runtime.
6 |
7 | ## Features
8 | - Open and browse NeFS archives (included encrypted archives).
9 | - Open and browse game.dat archives (DiRT Rally 2 and DiRT 4).
10 | - Extract files and directories from archives.
11 | - Replace files in an archive.
12 | - Save archives.
13 |
14 | ### Supported Versions
15 | A table showing which app version introduced support for each NeFS archive version.
16 |
17 | | NeFS Version | Read | Write |
18 | |--------------|------|-------|
19 | | 0.1.0 | 0.7 | X |
20 | | 0.2.0 | 0.7 | X |
21 | | 1.3.0 | 0.7 | X |
22 | | 1.4.0 | 0.7 | X |
23 | | 1.5.0 | 0.7 | X |
24 | | 1.5.1 | 0.7 | 0.7 |
25 | | 1.6.0 | 0.6 | 0.7 |
26 | | 2.0.0 | 0.1 | 0.1 |
27 |
28 | ## Limitations
29 | - Saving encrypted archives is not supported. This would require access to the private key originally used by the game developers.
30 | - Saving game.dat files is not supported. The header information for game.dat files is stored in the game executable. Modifying the executable will not be supported.
31 | - Creating new archives from scratch is not supported.
32 | - Adding new items to an archive is not supported.
33 | - Opening game.nefs files is supported, but some items in those archives do not get recognized properly. Saving game.nefs files will most likely result in a corrupted archive.
34 |
35 | ## Support
36 |
37 | This application is provided as-is, without any support; use at your own risk. However, feel free to report issues to the GitHub project: https://github.com/victorbush/ego.nefsedit.
38 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Item/NefsItemSize.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.DataSource;
4 |
5 | namespace VictorBush.Ego.NefsLib.Item;
6 |
7 | ///
8 | /// Stores information about a item's data size.
9 | ///
10 | public sealed class NefsItemSize
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The size of the item's data when extracted from the archive.
16 | ///
17 | /// List of cumulative block sizes. If the item does not have blocks this list should be empty.
18 | ///
19 | public NefsItemSize(uint extractedSize, IReadOnlyList chunks)
20 | {
21 | ExtractedSize = extractedSize;
22 | Chunks = chunks;
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the class. This constructor can be used if the item
27 | /// does not have blocks.
28 | ///
29 | /// The size of the item's data when extracted from the archive.
30 | public NefsItemSize(uint extractedSize)
31 | {
32 | ExtractedSize = extractedSize;
33 | Chunks = [];
34 | }
35 |
36 | ///
37 | /// List of metadata for the item's data blocks (in order).
38 | ///
39 | public IReadOnlyList Chunks { get; }
40 |
41 | ///
42 | /// Gets the size of the data after any transforms (compression, encryption) are undone.
43 | ///
44 | public uint ExtractedSize { get; }
45 |
46 | ///
47 | /// Gets the size of the data in bytes after transforms (compression, encrypted) have been applied.
48 | ///
49 | public uint TransformedSize => Chunks.Count == 0 ? ExtractedSize : Chunks[^1].CumulativeSize;
50 | }
51 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsHeaderBuilder.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Item;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Builder;
7 |
8 | internal abstract class NefsHeaderBuilder
9 | {
10 | private static readonly Dictionary Instances = new();
11 |
12 | public static NefsHeaderBuilder Get(NefsVersion version)
13 | {
14 | if (Instances.TryGetValue(version, out var inst))
15 | {
16 | return inst;
17 | }
18 |
19 | inst = version switch
20 | {
21 | NefsVersion.Version151 => new NefsHeaderBuilder151(),
22 | NefsVersion.Version160 => new NefsHeaderBuilder160(),
23 | NefsVersion.Version200 => new NefsHeaderBuilder200(),
24 | _ => throw new NotImplementedException($"Support for {version.ToPrettyString()} is not implemented.")
25 | };
26 |
27 | Instances.Add(version, inst);
28 | return inst;
29 | }
30 |
31 | public abstract INefsHeader Build(INefsHeader sourceHeader, NefsItemList items, NefsProgress p);
32 |
33 | public abstract uint ComputeDataOffset(INefsHeader sourceHeader, NefsItemList items);
34 | }
35 |
36 | ///
37 | internal abstract class NefsHeaderBuilder : NefsHeaderBuilder
38 | where T : INefsHeader
39 | {
40 | ///
41 | public override uint ComputeDataOffset(INefsHeader sourceHeader, NefsItemList items)
42 | {
43 | return ComputeDataOffset(sourceHeader.As(), items);
44 | }
45 |
46 | internal abstract uint ComputeDataOffset(T sourceHeader, NefsItemList items);
47 |
48 | ///
49 | public override INefsHeader Build(INefsHeader sourceHeader, NefsItemList items, NefsProgress p)
50 | {
51 |
52 | return Build(sourceHeader.As(), items, p);
53 | }
54 |
55 | internal abstract T Build(T sourceHeader, NefsItemList items, NefsProgress p);
56 | }
57 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/INefsHeader.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.IO;
4 | using VictorBush.Ego.NefsLib.Utility;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header;
7 |
8 | ///
9 | /// A NeFS archive header.
10 | ///
11 | public interface INefsHeader
12 | {
13 | ///
14 | /// Gets the header version.
15 | ///
16 | NefsVersion Version { get; }
17 |
18 | ///
19 | /// Gets the writer settings.
20 | ///
21 | NefsWriterSettings WriterSettings { get; }
22 |
23 | ///
24 | /// Gets whether the header is from a little-endian system.
25 | ///
26 | bool IsLittleEndian { get; }
27 |
28 | ///
29 | /// Gets whether the header is encrypted.
30 | ///
31 | bool IsEncrypted { get; }
32 |
33 | ///
34 | /// Gets the AES key.
35 | ///
36 | byte[] AesKey { get; }
37 |
38 | ///
39 | /// Gets the header hash.
40 | ///
41 | Sha256Hash Hash { get; }
42 |
43 | ///
44 | /// Gets the header size in bytes.
45 | ///
46 | uint Size { get; }
47 |
48 | ///
49 | /// Gets the data block size.
50 | ///
51 | uint BlockSize { get; }
52 |
53 | ///
54 | /// Gets the data file split size.
55 | ///
56 | uint SplitSize { get; }
57 |
58 | ///
59 | /// Gets the number of entries.
60 | ///
61 | uint NumEntries { get; }
62 |
63 | ///
64 | /// Gets the volumes described by the header.
65 | ///
66 | IReadOnlyList Volumes { get; }
67 |
68 | ///
69 | /// Gets the file name at the given offset.
70 | ///
71 | /// The name offset.
72 | /// The file name.
73 | string GetFileName(uint nameOffset);
74 | }
75 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | tab_width = 4
11 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
12 | dotnet_style_coalesce_expression = true:warning
13 | dotnet_style_null_propagation = true:suggestion
14 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
15 | dotnet_style_prefer_auto_properties = true:suggestion
16 | dotnet_style_object_initializer = true:suggestion
17 | dotnet_style_collection_initializer = true:suggestion
18 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
19 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
20 | dotnet_style_prefer_conditional_expression_over_return = true:none
21 | dotnet_style_explicit_tuple_names = true:warning
22 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
23 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
24 | dotnet_style_prefer_compound_assignment = true:suggestion
25 | dotnet_style_prefer_simplified_interpolation = true:suggestion
26 |
27 | [*.{xml,props,targets,csproj}]
28 | indent_size = 4
29 | indent_style = tab
30 |
31 | [*.cs]
32 | indent_size = 4
33 | indent_style = tab
34 | file_header_template = See LICENSE.txt for license information.
35 | csharp_prefer_braces = true:warning
36 | csharp_style_namespace_declarations = file_scoped:suggestion
37 | csharp_using_directive_placement = outside_namespace:warning
38 | dotnet_style_qualification_for_field = true:warning
39 | dotnet_style_qualification_for_property = false:warning
40 | dotnet_style_qualification_for_method = false:warning
41 | dotnet_style_qualification_for_event = false:warning
42 |
43 | # CS1591: Missing XML comment for publicly visible type or member
44 | dotnet_diagnostic.CS1591.severity = none
45 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/StructEx.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Runtime.InteropServices;
4 |
5 | namespace VictorBush.Ego.NefsLib.IO;
6 |
7 | public static class StructEx
8 | {
9 | ///
10 | /// Gets the alignment of the object.
11 | ///
12 | public static unsafe int AlignOf()
13 | where T : unmanaged
14 | {
15 | return sizeof(AlignOfHelper) - sizeof(T);
16 | }
17 |
18 | ///
19 | /// Align the start of the object.
20 | ///
21 | public static int Align(int offset)
22 | where T : unmanaged
23 | {
24 | var align = AlignOf();
25 | return Align(offset, align);
26 | }
27 |
28 | ///
29 | /// Align the start of the object.
30 | ///
31 | public static long Align(long offset)
32 | where T : unmanaged
33 | {
34 | var align = AlignOf();
35 | return Align(offset, align);
36 | }
37 |
38 | ///
39 | /// Align the offset using the given alignment.
40 | ///
41 | public static int Align(int offset, int alignment)
42 | {
43 | var aligned = (offset + alignment - 1) & -alignment;
44 | return aligned;
45 | }
46 |
47 | ///
48 | /// Align the offset using the given alignment.
49 | ///
50 | public static long Align(long offset, int alignment)
51 | {
52 | var aligned = (offset + alignment - 1) & -alignment;
53 | return aligned;
54 | }
55 |
56 | ///
57 | /// Gets the alignment padding necessary to align the start of the object.
58 | ///
59 | public static int AlignPadding(int offset)
60 | where T : unmanaged
61 | {
62 | var align = AlignOf();
63 | var padding = -offset & (align - 1);
64 | return padding;
65 | }
66 |
67 | [StructLayout(LayoutKind.Sequential)]
68 | private struct AlignOfHelper
69 | {
70 | private readonly byte _padding;
71 | private readonly T _value;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/IO/LzssDecompressTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Text;
4 | using VictorBush.Ego.NefsLib.IO;
5 | using Xunit;
6 |
7 | namespace VictorBush.Ego.NefsLib.Tests.IO;
8 |
9 | public class LzssDecompressTests
10 | {
11 | [Fact]
12 | public async Task Decompress_Test()
13 | {
14 | byte[] input = [
15 | 0xFF, 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0xFF, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D,
16 | 0x22, 0x31, 0xFF, 0x2E, 0x30, 0x22, 0x20, 0x73, 0x74, 0x61, 0x6E, 0xFF, 0x64, 0x61, 0x6C, 0x6F,
17 | 0x6E, 0x65, 0x3D, 0x27, 0xFF, 0x79, 0x65, 0x73, 0x27, 0x20, 0x3F, 0x3E, 0x0D, 0xFF, 0x0A, 0x3C,
18 | 0x53, 0x6B, 0x69, 0x70, 0x46, 0x72, 0xBF, 0x6F, 0x6E, 0x74, 0x45, 0x6E, 0x64, 0x14, 0x00, 0x09,
19 | 0xFF, 0x3C, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0xEF, 0x65, 0x72, 0x20, 0x6E, 0x2C, 0x00,
20 | 0x3D, 0x22, 0x73, 0xEE, 0x19, 0x00, 0x74, 0x6F, 0x67, 0x2C, 0x00, 0x22, 0x20, 0x76, 0x77, 0x61,
21 | 0x6C, 0x75, 0x36, 0x00, 0x74, 0x72, 0x75, 0x42, 0x00, 0x05, 0x2F, 0x14, 0x01, 0x2F, 0x18, 0x0A
22 | ];
23 | var expectedBytes = Encoding.ASCII.GetBytes("""
24 |
25 |
26 |
27 |
28 | """.ReplaceLineEndings("\r\n"));
29 |
30 | using var inputStream = new MemoryStream(input);
31 | var lzss = new LzssDecompress();
32 |
33 | // Test
34 | using var outputStream = new MemoryStream();
35 | await lzss.DecompressAsync(inputStream, outputStream, CancellationToken.None)
36 | .ConfigureAwait(false);
37 |
38 | // Verify
39 | var actualBytes = outputStream.ToArray();
40 | Assert.Equal(expectedBytes, actualBytes);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/EndianBinaryWriter.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Buffers;
4 | using System.Buffers.Binary;
5 | using System.Runtime.CompilerServices;
6 | using System.Runtime.InteropServices;
7 | using VictorBush.Ego.NefsLib.Header;
8 |
9 | namespace VictorBush.Ego.NefsLib.IO;
10 |
11 | public class EndianBinaryWriter(Stream stream, bool littleEndian, ResizableBuffer buffer) : IDisposable
12 | {
13 | private ResizableBuffer buffer = buffer;
14 |
15 | public Stream BaseStream { get; } = stream;
16 |
17 | public bool IsLittleEndian { get; } = littleEndian;
18 |
19 | public EndianBinaryWriter(Stream stream) : this(stream, BitConverter.IsLittleEndian)
20 | {
21 | }
22 |
23 | public EndianBinaryWriter(Stream stream, bool littleEndian) : this(stream, littleEndian,
24 | new ResizableBuffer(ArrayPool.Shared, 16))
25 | {
26 | }
27 |
28 | public ValueTask WriteAsync(uint value, CancellationToken cancellationToken = default)
29 | {
30 | if (IsLittleEndian)
31 | {
32 | BinaryPrimitives.WriteUInt32LittleEndian(this.buffer.Memory.Span, value);
33 | }
34 | else
35 | {
36 | BinaryPrimitives.WriteUInt32BigEndian(this.buffer.Memory.Span, value);
37 | }
38 |
39 | return BaseStream.WriteAsync(this.buffer.Memory[..4], cancellationToken);
40 | }
41 |
42 | public ValueTask WriteTocDataAsync(T data, CancellationToken cancellationToken = default)
43 | where T : unmanaged, INefsTocData
44 | {
45 | if (IsLittleEndian != BitConverter.IsLittleEndian)
46 | {
47 | data.ReverseEndianness();
48 | }
49 |
50 | var size = T.ByteCount;
51 | this.buffer.EnsureLength(size);
52 | var buff = this.buffer.Memory[..size];
53 | Unsafe.As(ref MemoryMarshal.GetReference(buff.Span)) = data;
54 | return BaseStream.WriteAsync(buff, cancellationToken);
55 | }
56 |
57 | public void Dispose()
58 | {
59 | this.buffer.Dispose();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/IO/NefsWriterStrategy151.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header.Version150;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsLib.IO;
7 |
8 | internal class NefsWriterStrategy151 : NefsWriterStrategy
9 | {
10 | ///
11 | protected override async Task WriteHeaderAsync(EndianBinaryWriter writer, NefsHeader151 header, long primaryOffset,
12 | NefsProgress p)
13 | {
14 | // Calc weight of each task (5 parts + intro)
15 | const float weight = 1.0f / 6.0f;
16 |
17 | // Get intro
18 | var intro = header.Intro;
19 |
20 | var stream = writer.BaseStream;
21 | using (p.BeginTask(weight, "Writing header"))
22 | {
23 | var offset = primaryOffset;
24 | await WriteTocEntryAsync(writer, offset, header.Intro, p).ConfigureAwait(false);
25 | }
26 |
27 | using (p.BeginTask(weight, "Writing entry table"))
28 | {
29 | var offset = primaryOffset + intro.EntryTableStart;
30 | await WriteTocTableAsync(writer, offset, header.EntryTable, p).ConfigureAwait(false);
31 | }
32 |
33 | using (p.BeginTask(weight, "Writing shared entry info table"))
34 | {
35 | var offset = primaryOffset + intro.SharedEntryInfoTableStart;
36 | await WriteTocTableAsync(writer, offset, header.SharedEntryInfoTable, p).ConfigureAwait(false);
37 | }
38 |
39 | using (p.BeginTask(weight, "Writing name table"))
40 | {
41 | var offset = primaryOffset + intro.NameTableStart;
42 | await WriteHeaderPart3Async(stream, offset, header.NameTable, p).ConfigureAwait(false);
43 | }
44 |
45 | using (p.BeginTask(weight, "Writing block table"))
46 | {
47 | var offset = primaryOffset + intro.BlockTableStart;
48 | await WriteTocTableAsync(writer, offset, header.BlockTable, p).ConfigureAwait(false);
49 | }
50 |
51 | using (p.BeginTask(weight, "Writing volume info table"))
52 | {
53 | var offset = primaryOffset + intro.VolumeInfoTableStart;
54 | await WriteTocTableAsync(writer, offset, header.VolumeInfoTable, p).ConfigureAwait(false);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit.Tests/TestHelpers.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib;
4 | using VictorBush.Ego.NefsLib.DataSource;
5 | using VictorBush.Ego.NefsLib.Header.Builder;
6 | using VictorBush.Ego.NefsLib.Header.Version200;
7 | using VictorBush.Ego.NefsLib.Item;
8 | using VictorBush.Ego.NefsLib.Progress;
9 |
10 | namespace VictorBush.Ego.NefsEdit.Tests;
11 |
12 | internal class TestHelpers
13 | {
14 | ///
15 | /// Creates a to be used for testing.
16 | ///
17 | /// The file path to associate with the archive.
18 | /// An archive object.
19 | ///
20 | internal static NefsArchive CreateTestArchive(string filePath)
21 | {
22 | var items = new NefsItemList(filePath);
23 |
24 | var transform = new NefsDataTransform(50, true);
25 |
26 | var file1Attributes = new NefsItemAttributes(v20IsZlib: true);
27 | var file1Chunks = NefsDataChunk.CreateChunkList(new List { 2, 3, 4 }, transform);
28 | var file1DataSource = new NefsVolumeDataSource(items, 100, new NefsItemSize(20, file1Chunks));
29 | var file1 = new NefsItem(new NefsItemId(0), "file1", new NefsItemId(0), file1DataSource, transform, file1Attributes);
30 | items.Add(file1);
31 |
32 | var dir1Attributes = new NefsItemAttributes(isDirectory: true);
33 | var dir1DataSource = new NefsEmptyDataSource();
34 | var dir1 = new NefsItem(new NefsItemId(1), "dir1", new NefsItemId(1), dir1DataSource, null, dir1Attributes);
35 | items.Add(dir1);
36 |
37 | var file2Attributes = new NefsItemAttributes(v20IsZlib: true);
38 | var file2Chunks = NefsDataChunk.CreateChunkList(new List { 5, 6, 7 }, transform);
39 | var file2DataSource = new NefsVolumeDataSource(items, 104, new NefsItemSize(15, file2Chunks));
40 | var file2 = new NefsItem(new NefsItemId(2), "file2", dir1.Id, file2DataSource, transform, file2Attributes);
41 | items.Add(file2);
42 |
43 | var builder = new NefsHeaderBuilder200();
44 | var header = builder.Build(new NefsHeader200(), items, new NefsProgress());
45 |
46 | return new NefsArchive(header, items);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/ArchiveSource/NefsArchiveSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.ArchiveSource;
4 |
5 | ///
6 | /// Defines location of a NeFS archive.
7 | ///
8 | public abstract class NefsArchiveSource
9 | {
10 | protected NefsArchiveSource(string filePath)
11 | {
12 | FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
13 | }
14 |
15 | ///
16 | /// The name of the archive file.
17 | ///
18 | public string FileName => Path.GetFileName(FilePath);
19 |
20 | ///
21 | /// Path to the archive file. containing the archive's file data.
22 | ///
23 | public string FilePath { get; }
24 |
25 | ///
26 | /// Header and file data are stored in separate files. Usually the header is split into two sections, a primary
27 | /// section (header parts 1-5, 8) and a secondary section (parts 6-7).
28 | ///
29 | ///
30 | /// Path of the data file. This contains the file data for the archive (e.g., "game.dat", "game.bin", "dr.nic").
31 | ///
32 | /// Path of the file that contains the archive's header data (i.e., the game executable).
33 | /// Offset into the header file to the primary header section.
34 | /// Size of the primary header section.
35 | /// Offset into the header file to the secondary header section.
36 | /// Size of the secondary header section.
37 | public static HeadlessSource Headless(string dataFilePath, string headerFilePath, long primaryOffset, int? primarySize, long secondaryOffset, int? secondarySize)
38 | {
39 | return new HeadlessSource(dataFilePath, headerFilePath, primaryOffset, primarySize, secondaryOffset, secondarySize);
40 | }
41 |
42 | ///
43 | /// Header and file data are both stored in the same file.
44 | ///
45 | /// Path of the archive file.
46 | public static StandardSource Standard(string filePath)
47 | {
48 | return new StandardSource(filePath);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/IO/NefsWriterTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Abstractions.TestingHelpers;
4 | using System.Runtime.InteropServices;
5 | using VictorBush.Ego.NefsLib.IO;
6 | using Xunit;
7 |
8 | namespace VictorBush.Ego.NefsLib.Tests.IO;
9 |
10 | public class NefsWriterTests
11 | {
12 | private const string TempDir = @"C:\temp";
13 | private readonly MockFileSystem fileSystem = new MockFileSystem();
14 | private readonly INefsTransformer transformer;
15 |
16 | public NefsWriterTests()
17 | {
18 | this.fileSystem.AddDirectory(TempDir);
19 | this.transformer = new NefsTransformer(this.fileSystem);
20 | }
21 |
22 | [Fact]
23 | public async Task EncodeXorIntro()
24 | {
25 | var input = Enumerable.Range(-32, 32).ToArray();
26 | var inputBuffer = MemoryMarshal.Cast(input).ToArray();
27 | var inputStream = new MemoryStream(inputBuffer);
28 | var expectedBuffer = new byte[]
29 | {
30 | 0xEC, 0xFF, 0xFF, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xEB, 0xFF, 0xFF, 0xFF, 0xE8, 0xFF,
31 | 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
32 | 0x08, 0x00, 0x00, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xEA, 0xFF,
33 | 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
34 | 0x1C, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x18, 0x00,
35 | 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
36 | 0x15, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF,
37 | 0xFF, 0xFF
38 | };
39 |
40 | // Act
41 | await NefsWriter.EncodeXorIntroAsync(inputStream, 0, CancellationToken.None).ConfigureAwait(false);
42 |
43 | // Assert
44 | var actualBuffer = inputStream.ToArray();
45 | //var str = string.Join(',', actualBuffer.Select(x => $"0x{x:X2}"));
46 | Assert.Equal(expectedBuffer, actualBuffer);
47 | }
48 |
49 | private NefsWriter CreateWriter()
50 | {
51 | return new NefsWriter(TempDir, this.fileSystem, this.transformer);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/PropertyGridForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsEdit.UI
2 | {
3 | partial class PropertyGridForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.propertyGrid = new System.Windows.Forms.PropertyGrid();
32 | this.SuspendLayout();
33 | //
34 | // propertyGrid
35 | //
36 | this.propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill;
37 | this.propertyGrid.LineColor = System.Drawing.SystemColors.ControlDark;
38 | this.propertyGrid.Location = new System.Drawing.Point(0, 0);
39 | this.propertyGrid.Name = "propertyGrid";
40 | this.propertyGrid.Size = new System.Drawing.Size(590, 649);
41 | this.propertyGrid.TabIndex = 0;
42 | //
43 | // PropertyGridForm
44 | //
45 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
46 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
47 | this.ClientSize = new System.Drawing.Size(590, 649);
48 | this.Controls.Add(this.propertyGrid);
49 | this.Name = "PropertyGridForm";
50 | this.Text = "PropertyGridForm";
51 | this.ResumeLayout(false);
52 |
53 | }
54 |
55 | #endregion
56 |
57 | private System.Windows.Forms.PropertyGrid propertyGrid;
58 | }
59 | }
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/NefsDataTransform.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.DataSource;
4 |
5 | ///
6 | /// Describes a transformation applied to a file before being put in the archive.
7 | ///
8 | public record NefsDataTransform
9 | {
10 | ///
11 | /// Whether the data is transformed.
12 | ///
13 | public bool IsTransformed => IsLzssCompressed || IsAesEncrypted || IsZlibCompressed;
14 |
15 | ///
16 | /// Whether the data is LZSS compressed.
17 | ///
18 | public bool IsLzssCompressed { get; init; }
19 |
20 | ///
21 | /// Whether the checksum should be computed for the blocks.
22 | ///
23 | public bool ComputeChecksum { get; init; }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// The chunk size to use.
29 | /// Whether to compress with zlib.
30 | /// The AES-256 key for encryption. Use null if not encrypted.
31 | public NefsDataTransform(uint chunkSize, bool isZlib, byte[]? aesKey = null)
32 | {
33 | ChunkSize = chunkSize;
34 | IsZlibCompressed = isZlib;
35 | Aes256Key = aesKey;
36 | }
37 |
38 | ///
39 | /// Initializes a new instance of the class. This transform does nothing (file is
40 | /// simply placed in the archive as-is).
41 | ///
42 | /// The file size.
43 | public NefsDataTransform(uint fileSize)
44 | {
45 | ChunkSize = fileSize;
46 | IsZlibCompressed = false;
47 | Aes256Key = null;
48 | }
49 |
50 | ///
51 | /// The AES 256 key. If the chunk is not encrypted, this will be null.
52 | ///
53 | public byte[]? Aes256Key { get; }
54 |
55 | ///
56 | /// The size of chunks to split the input file up into before transforming each chunk.
57 | ///
58 | public uint ChunkSize { get; }
59 |
60 | ///
61 | /// Whether data chunks are AES encrypted.
62 | ///
63 | public bool IsAesEncrypted => Aes256Key != null;
64 |
65 | ///
66 | /// Whether data chunks are zlib compressed.
67 | ///
68 | public bool IsZlibCompressed { get; }
69 | }
70 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/NefsDataChunk.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.DataSource;
4 |
5 | ///
6 | /// Describes a chunk of data in an archive.
7 | ///
8 | public class NefsDataChunk
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The size of the data chunk.
14 | /// The cumulative size of the data chunk.
15 | /// The transform that has been applied to this chunk.
16 | public NefsDataChunk(uint size, uint cumulativeSize, NefsDataTransform transform)
17 | {
18 | Size = size;
19 | CumulativeSize = cumulativeSize;
20 | Transform = transform ?? throw new ArgumentNullException(nameof(transform));
21 | }
22 |
23 | ///
24 | /// The cumulative size of the chunk. This is equal to the size of this chunk + the size of all the previous chunks.
25 | ///
26 | public uint CumulativeSize { get; }
27 |
28 | ///
29 | /// The size of the chunk.
30 | ///
31 | public uint Size { get; }
32 |
33 | ///
34 | /// The transform that has been applied to this chunk.
35 | ///
36 | public NefsDataTransform Transform { get; }
37 |
38 | ///
39 | /// The CRC16 checksum.
40 | ///
41 | public ushort Checksum { get; init; }
42 |
43 | ///
44 | /// Creats a list of chunks given a list of cumulative chunk sizes.
45 | ///
46 | /// List of cumulative chunk sizes.
47 | /// The transform applied to all chunks.
48 | /// A list of chunks.
49 | public static List CreateChunkList(
50 | IReadOnlyList cumulativeSizes,
51 | NefsDataTransform transform)
52 | {
53 | var chunks = new List();
54 |
55 | for (var i = 0; i < cumulativeSizes.Count; ++i)
56 | {
57 | var size = cumulativeSizes[i];
58 |
59 | // Get individual size by subtracting previous cumulative size
60 | if (i > 0)
61 | {
62 | size -= cumulativeSizes[i - 1];
63 | }
64 |
65 | var chunk = new NefsDataChunk(size, cumulativeSizes[i], transform);
66 | chunks.Add(chunk);
67 | }
68 |
69 | return chunks;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/Sha256HashBuilder.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Security.Cryptography;
4 |
5 | namespace VictorBush.Ego.NefsLib.Utility;
6 |
7 | ///
8 | /// A SHA-256 hash value.
9 | ///
10 | public sealed class Sha256HashBuilder
11 | {
12 | private readonly List buffer = new();
13 |
14 | ///
15 | /// Adds data to be hashed.
16 | ///
17 | /// Bytes of data to add to the buffer to be hashed.
18 | public void AddData(byte[] data)
19 | {
20 | this.buffer.AddRange(data);
21 | }
22 |
23 | ///
24 | /// Adds data to be hashed.
25 | ///
26 | /// The stream to read from.
27 | /// The offset from the beginning of the stream to read from.
28 | /// The number of bytes to read.
29 | /// Cancellation token.
30 | /// An async .
31 | public async Task AddDataAsync(Stream stream, long absoluteOffset, int count, CancellationToken cancellationToken = default)
32 | {
33 | stream.Seek(absoluteOffset, SeekOrigin.Begin);
34 | await AddDataAsync(stream, count, cancellationToken);
35 | }
36 |
37 | ///
38 | /// Adds data to be hashed.
39 | ///
40 | /// The stream to read from.
41 | /// The number of bytes to read.
42 | /// Cancellation token.
43 | /// An async .
44 | public async Task AddDataAsync(Stream stream, int count, CancellationToken cancellationToken = default)
45 | {
46 | var data = new byte[count];
47 | var bytesRead = await stream.ReadAsync(data, 0, count, cancellationToken);
48 | if (bytesRead != count)
49 | {
50 | throw new ArgumentException($"Expected to add {count} bytes to the hash, but only read {bytesRead}.");
51 | }
52 |
53 | this.buffer.AddRange(data);
54 | }
55 |
56 | ///
57 | /// Computes the hash of the current data buffer.
58 | ///
59 | /// The SHA-256 hash value.
60 | public Sha256Hash FinishHash()
61 | {
62 | using (var hash = SHA256.Create())
63 | {
64 | var value = hash.ComputeHash(this.buffer.ToArray());
65 | return new Sha256Hash(value);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/ArchiveSource/HeadlessSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.ArchiveSource;
4 |
5 | ///
6 | /// Header and file data are stored in separate files. Usually the header is split into two sections, a primary section
7 | /// (header parts 1-5, 8) and a secondary section (parts 6-7).
8 | ///
9 | public sealed class HeadlessSource : NefsArchiveSource
10 | {
11 | internal HeadlessSource(string dataFilePath, string headerFilePath, long primaryOffset, int? primarySize, long secondaryOffset, int? secondarySize)
12 | : base(dataFilePath)
13 | {
14 | HeaderFilePath = headerFilePath ?? throw new ArgumentNullException(nameof(headerFilePath));
15 | PrimaryOffset = primaryOffset;
16 | PrimarySize = primarySize;
17 | SecondaryOffset = secondaryOffset;
18 | SecondarySize = secondarySize;
19 | }
20 |
21 | ///
22 | /// Path to the file containing the file data (i.e., game.dat, game.bin, etc.).
23 | ///
24 | public string DataFilePath => FilePath;
25 |
26 | ///
27 | /// Path to the file containing the archive header (i.e., the game executable).
28 | ///
29 | public string HeaderFilePath { get; }
30 |
31 | ///
32 | /// Offset to the beginning of the header from the beginning of the file specified by .
33 | ///
34 | public long PrimaryOffset { get; }
35 |
36 | ///
37 | /// The size of the first chunk of header data. This includes the intro, table of contents, parts 1-5, and part 8.
38 | /// This size is not the same value as the total header size as stored within the header itself. Providing this
39 | /// value is optional. If not provided, the size of header part 8 will be deduced based on the total size of file
40 | /// data. The extra data from part 8 will not be loaded.
41 | ///
42 | public int? PrimarySize { get; }
43 |
44 | ///
45 | /// The offset to header part 6.
46 | ///
47 | public long SecondaryOffset { get; }
48 |
49 | ///
50 | /// The size of the part 6 and 7 chunk. Part 6 and 7 are stored together separately from the rest of the header
51 | /// data. This is optional. If not provided, the size of parts 6 and 7 will be deduced from the other header data
52 | /// and any extra data at the end will be ignored.
53 | ///
54 | public int? SecondarySize { get; }
55 | }
56 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Services/ISettingsService.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsEdit.Settings;
4 |
5 | namespace VictorBush.Ego.NefsEdit.Services;
6 |
7 | ///
8 | /// Settings service.
9 | ///
10 | internal interface ISettingsService
11 | {
12 | bool CheckForDatabaseUpdatesOnStartup { get; set; }
13 |
14 | ///
15 | /// Gets or sets the DiRT 4 directory.
16 | ///
17 | string Dirt4Dir { get; set; }
18 |
19 | ///
20 | /// Gets the DiRT 4 executable.
21 | ///
22 | string Dirt4Exe { get; }
23 |
24 | ///
25 | /// Gets the directory that contains game.dat files.
26 | ///
27 | string Dirt4GameDatDir { get; }
28 |
29 | ///
30 | /// Gets or sets the DiRT Rally 1 directory.
31 | ///
32 | string DirtRally1Dir { get; set; }
33 |
34 | ///
35 | /// Gets the DiRT Rally 1 executable.
36 | ///
37 | string DirtRally1Exe { get; }
38 |
39 | ///
40 | /// Gets the directory that contains game.bin files.
41 | ///
42 | string DirtRally1GameBinDir { get; }
43 |
44 | ///
45 | /// Gets or sets the DiRT Rally 2 directory.
46 | ///
47 | string DirtRally2Dir { get; set; }
48 |
49 | ///
50 | /// Gets the DiRT Rally 2 executable.
51 | ///
52 | string DirtRally2Exe { get; }
53 |
54 | ///
55 | /// Gets the directory that contains game.dat files.
56 | ///
57 | string DirtRally2GameDatDir { get; }
58 |
59 | ///
60 | /// Gets or sets the last state info for the open file dialog.
61 | ///
62 | OpenFileDialogState OpenFileDialogState { get; set; }
63 |
64 | ///
65 | /// Gets or sets the quick extract directory.
66 | ///
67 | string QuickExtractDir { get; set; }
68 |
69 | ///
70 | /// Gets or sets the list of recently opened files.
71 | ///
72 | List RecentFiles { get; set; }
73 |
74 | ///
75 | /// Shows a dialog to choose the quick extract dir.
76 | ///
77 | /// True if the directory was chosen.
78 | bool ChooseQuickExtractDir();
79 |
80 | ///
81 | /// Loads settings from disk or loads defaults.
82 | ///
83 | void Load();
84 |
85 | ///
86 | /// Saves the settings file to disk.
87 | ///
88 | void Save();
89 | }
90 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/DeflateHelper.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO.Compression;
4 |
5 | namespace VictorBush.Ego.NefsLib.Utility;
6 |
7 | ///
8 | /// Compression utilities.
9 | ///
10 | internal static class DeflateHelper
11 | {
12 | ///
13 | /// Takes data from an input file stream, compresses it, and writes it to the specified output file. Streams should
14 | /// already seek to the proper location before calling this function.
15 | ///
16 | /// The input file stream to read from.
17 | /// Number of bytes to read in.
18 | /// The output file stream to write compressed chunk to.
19 | /// Cancellation token.
20 | /// Number of bytes actually read from input file and the compressed size of the chunk that was written.
21 | public static async Task<(int BytesRead, int ChunkSize)> DeflateAsync(
22 | Stream inStream,
23 | int numBytes,
24 | Stream outStream,
25 | CancellationToken cancelToken)
26 | {
27 | // Read in the input data to compress
28 | var inData = new byte[numBytes];
29 | var bytesRead = await inStream.ReadAsync(inData, 0, numBytes, cancelToken);
30 | var chunkSize = 0;
31 |
32 | // Deflate stream doesn't write properly directly to a FileStream when doing this chunk business. So have to do
33 | // this multi-stream setup.
34 | using (var inMemStream = new MemoryStream())
35 | using (var outMemStream = new MemoryStream())
36 | using (var deflateStream = new DeflateStream(outMemStream, CompressionMode.Compress))
37 | {
38 | // Read input chunk into memory stream
39 | await inMemStream.WriteAsync(inData, 0, bytesRead, cancelToken);
40 | inMemStream.Seek(0, SeekOrigin.Begin);
41 |
42 | // Compress the chunk with deflate stream
43 | await inMemStream.CopyPartialAsync(deflateStream, bytesRead, cancelToken);
44 |
45 | // Close deflate stream to finalize compression - breaking the "using()" convention, but this is needed
46 | deflateStream.Close();
47 |
48 | // Write the compressed chunk to the output file
49 | var compressedData = outMemStream.ToArray();
50 | chunkSize = compressedData.Length;
51 | await outStream.WriteAsync(compressedData, 0, compressedData.Length, cancelToken);
52 | }
53 |
54 | return (bytesRead, chunkSize);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/ConsoleForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsEdit.UI
2 | {
3 | partial class ConsoleForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.richTextBox = new System.Windows.Forms.RichTextBox();
32 | this.SuspendLayout();
33 | //
34 | // richTextBox
35 | //
36 | this.richTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
37 | this.richTextBox.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
38 | this.richTextBox.Location = new System.Drawing.Point(0, 0);
39 | this.richTextBox.Name = "richTextBox";
40 | this.richTextBox.ReadOnly = true;
41 | this.richTextBox.Size = new System.Drawing.Size(705, 360);
42 | this.richTextBox.TabIndex = 0;
43 | this.richTextBox.Text = "";
44 | //
45 | // ConsoleForm
46 | //
47 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
48 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
49 | this.ClientSize = new System.Drawing.Size(705, 360);
50 | this.Controls.Add(this.richTextBox);
51 | this.Name = "ConsoleForm";
52 | this.Text = "ConsoleForm";
53 | this.ResumeLayout(false);
54 |
55 | }
56 |
57 | #endregion
58 |
59 | private System.Windows.Forms.RichTextBox richTextBox;
60 | }
61 | }
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Utility/StringHelper.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Runtime.InteropServices;
4 | using System.Text;
5 |
6 | namespace VictorBush.Ego.NefsLib.Utility;
7 |
8 | ///
9 | /// Some string utilities.
10 | ///
11 | public static class StringHelper
12 | {
13 | ///
14 | /// Prints a byte array to a string in hex format.
15 | ///
16 | /// The bytes to print.
17 | /// The string.
18 | public static string ByteArrayToString(ReadOnlySpan bytes)
19 | {
20 | if (bytes == null)
21 | {
22 | return "";
23 | }
24 |
25 | var sb = new StringBuilder();
26 | foreach (var b in bytes)
27 | {
28 | sb.Append("0x" + b.ToString("X") + ", ");
29 | }
30 |
31 | return sb.ToString();
32 | }
33 |
34 | ///
35 | /// Takes a string representation of a hexademical value and converts it to a byte array.
36 | ///
37 | /// The hex string.
38 | /// The byte array.
39 | public static byte[] FromHexString(string hex)
40 | {
41 | byte[] raw = new byte[hex.Length / 2];
42 | for (int i = 0; i < raw.Length; i++)
43 | {
44 | raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
45 | }
46 |
47 | return raw;
48 | }
49 |
50 | ///
51 | /// Takes an integer and converts it to a string representation in hexadecimal format.
52 | ///
53 | /// The value to convert.
54 | /// Whether to prefix with '0x'.
55 | /// The hex string.
56 | public static string ToHexString(this uint value, bool prefix = true)
57 | {
58 | if (prefix)
59 | {
60 | return string.Format("0x{0:X}", value);
61 | }
62 | else
63 | {
64 | return string.Format("{0:X}", value);
65 | }
66 | }
67 |
68 | ///
69 | /// Tries to read a null terminated ASCII string. If no null-terminator is found, an empty string is returned.
70 | ///
71 | /// The input bytes.
72 | /// The string.
73 | public static string TryReadNullTerminatedAscii(ReadOnlySpan bytes)
74 | {
75 | // Find the next null terminator
76 | var nullOffset = bytes.IndexOf((byte)0);
77 | return nullOffset == -1 ?
78 | // No null terminator found
79 | "" :
80 | // Get the string
81 | Encoding.ASCII.GetString(bytes[..nullOffset]);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version010/NefsHeader010.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.IO;
4 | using VictorBush.Ego.NefsLib.Utility;
5 |
6 | namespace VictorBush.Ego.NefsLib.Header.Version010;
7 |
8 | ///
9 | public sealed class NefsHeader010 : INefsHeader
10 | {
11 | public NefsWriterSettings WriterSettings { get; }
12 | public NefsTocHeader010 Intro { get; }
13 | public NefsHeaderEntryTable010 EntryTable { get; }
14 | public NefsHeaderLinkTable010 LinkTable { get; }
15 | public NefsHeaderNameTable NameTable { get; }
16 | public NefsHeaderBlockTable010 BlockTable { get; }
17 | public NefsHeaderVolumeSizeTable010 VolumeSizeTable { get; }
18 |
19 | ///
20 | public NefsVersion Version => (NefsVersion)Intro.Version;
21 |
22 | ///
23 | public bool IsLittleEndian => WriterSettings.IsLittleEndian;
24 |
25 | ///
26 | public bool IsEncrypted => WriterSettings.IsEncrypted;
27 |
28 | ///
29 | public byte[] AesKey => [];
30 |
31 | ///
32 | public Sha256Hash Hash => new();
33 |
34 | ///
35 | public uint Size => Intro.TocSize;
36 |
37 | ///
38 | public uint BlockSize => Intro.BlockSize;
39 |
40 | ///
41 | public uint SplitSize => 0;
42 |
43 | ///
44 | public uint NumEntries => (uint)EntryTable.Entries.Count;
45 |
46 | ///
47 | public IReadOnlyList Volumes { get; }
48 |
49 | ///
50 | /// Initializes a new instance of the class.
51 | ///
52 | public NefsHeader010(
53 | NefsWriterSettings writerSettings,
54 | NefsTocHeader010 header,
55 | NefsHeaderEntryTable010 entryTable,
56 | NefsHeaderLinkTable010 sharedEntryInfoTable,
57 | NefsHeaderNameTable nameTable,
58 | NefsHeaderBlockTable010 blockTable,
59 | NefsHeaderVolumeSizeTable010 volumeSizeTable)
60 | {
61 | WriterSettings = writerSettings;
62 | Intro = header;
63 | EntryTable = entryTable;
64 | LinkTable = sharedEntryInfoTable;
65 | NameTable = nameTable;
66 | BlockTable = blockTable;
67 | VolumeSizeTable = volumeSizeTable;
68 |
69 | Volumes = VolumeSizeTable.Entries.Select((x, i) => new VolumeInfo
70 | {
71 | Size = x.Size,
72 | Name = i.ToString(),
73 | DataOffset = i == 0 ? Intro.TocSize : 0
74 | }).ToArray();
75 | }
76 |
77 | ///
78 | public string GetFileName(uint nameOffset)
79 | {
80 | return NameTable.FileNamesByOffset[nameOffset];
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/IO/NefsReaderStrategy150Tests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header.Version150;
4 | using VictorBush.Ego.NefsLib.IO;
5 | using VictorBush.Ego.NefsLib.Progress;
6 | using Xunit;
7 |
8 | namespace VictorBush.Ego.NefsLib.Tests.IO;
9 |
10 | public class NefsReaderStrategy150Tests
11 | {
12 | private readonly NefsProgress p = new(CancellationToken.None);
13 |
14 | [Fact]
15 | public async Task Read150HeaderPart1Async_ExtraBytesAtEnd_ExtraBytesIgnored()
16 | {
17 | byte[] bytes =
18 | {
19 | // 5 bytes offset
20 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
21 |
22 | // Entry 1
23 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
24 | 0x41, 0x42,
25 | 0x43, 0x44,
26 | 0x11, 0x12, 0x13, 0x14,
27 | 0x21, 0x22, 0x23, 0x24,
28 | 0x31, 0x32, 0x33, 0x34,
29 |
30 | // Extra bytes
31 | 0xFF, 0xFF,
32 | };
33 |
34 | var stream = new MemoryStream(bytes);
35 | using var br = new EndianBinaryReader(stream, true);
36 | var size = NefsTocEntry150.ByteCount + 2;
37 | var offset = 5;
38 |
39 | // Test
40 | var part1 = await NefsReaderStrategy150.Read150HeaderPart1Async(br, offset, size, this.p);
41 |
42 | // Verify
43 | Assert.Single(part1.Entries);
44 |
45 | var e1 = part1.Entries[0];
46 | Assert.Equal((ulong)0x0807060504030201, e1.Start);
47 | Assert.Equal(0x4241u, e1.Volume);
48 | Assert.Equal(0x4443u, (ushort)e1.Flags);
49 | Assert.Equal((uint)0x14131211, e1.SharedInfo);
50 | Assert.Equal((uint)0x24232221, e1.FirstBlock);
51 | Assert.Equal((uint)0x34333231, e1.NextDuplicate);
52 | }
53 |
54 | [Fact]
55 | public async Task ReadHeaderPart5Async_ValidData_DataRead()
56 | {
57 | byte[] bytes =
58 | {
59 | // 5 bytes offset
60 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
61 |
62 | // Archive size
63 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
64 |
65 | // Offset into part 3 for archive name
66 | 0x21, 0x22, 0x23, 0x24,
67 |
68 | // First data offset
69 | 0x25, 0x26, 0x27, 0x28,
70 | };
71 |
72 | var stream = new MemoryStream(bytes);
73 | var size = 16;
74 | var offset = 5;
75 | using var endianReader = new EndianBinaryReader(stream, true);
76 |
77 | // Test
78 | var part5 = await NefsReaderStrategy150.ReadHeaderPart5Async(endianReader, offset, size, this.p);
79 |
80 | // Verify
81 | Assert.Single(part5.Entries);
82 | Assert.Equal((ulong)0x1817161514131211, part5.Entries[0].Size);
83 | Assert.Equal((uint)0x24232221, part5.Entries[0].NameOffset);
84 | Assert.Equal((uint)0x28272625, part5.Entries[0].DataOffset);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version020/NefsHeader020.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header.Version010;
4 | using VictorBush.Ego.NefsLib.IO;
5 | using VictorBush.Ego.NefsLib.Utility;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Version020;
8 |
9 | ///
10 | public sealed class NefsHeader020 : INefsHeader
11 | {
12 | public NefsWriterSettings WriterSettings { get; }
13 | public NefsTocHeader020 Intro { get; }
14 | public NefsHeaderEntryTable010 EntryTable { get; }
15 | public NefsHeaderLinkTable010 LinkTable { get; }
16 | public NefsHeaderNameTable NameTable { get; }
17 | public NefsHeaderBlockTable010 BlockTable { get; }
18 | public NefsHeaderVolumeSizeTable010 VolumeSizeTable { get; }
19 |
20 | ///
21 | public NefsVersion Version => (NefsVersion)Intro.Version;
22 |
23 | ///
24 | public bool IsLittleEndian => WriterSettings.IsLittleEndian;
25 |
26 | ///
27 | public bool IsEncrypted => WriterSettings.IsEncrypted;
28 |
29 | ///
30 | public byte[] AesKey => Intro.AesKey.GetAesKey();
31 |
32 | ///
33 | public Sha256Hash Hash => new();
34 |
35 | ///
36 | public uint Size => Intro.TocSize;
37 |
38 | ///
39 | public uint BlockSize => Intro.BlockSize;
40 |
41 | ///
42 | public uint SplitSize => 0;
43 |
44 | ///
45 | public uint NumEntries => (uint)EntryTable.Entries.Count;
46 |
47 | ///
48 | public IReadOnlyList Volumes { get; }
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | public NefsHeader020(
54 | NefsWriterSettings writerSettings,
55 | NefsTocHeader020 header,
56 | NefsHeaderEntryTable010 entryTable,
57 | NefsHeaderLinkTable010 sharedEntryInfoTable,
58 | NefsHeaderNameTable nameTable,
59 | NefsHeaderBlockTable010 blockTable,
60 | NefsHeaderVolumeSizeTable010 volumeSizeTable)
61 | {
62 | WriterSettings = writerSettings;
63 | Intro = header;
64 | EntryTable = entryTable;
65 | LinkTable = sharedEntryInfoTable;
66 | NameTable = nameTable;
67 | BlockTable = blockTable;
68 | VolumeSizeTable = volumeSizeTable;
69 |
70 | Volumes = VolumeSizeTable.Entries.Select((x, i) => new VolumeInfo
71 | {
72 | Size = x.Size,
73 | Name = i.ToString(),
74 | DataOffset = i == 0 ? Intro.TocSize : 0
75 | }).ToArray();
76 | }
77 |
78 | ///
79 | public string GetFileName(uint nameOffset)
80 | {
81 | return NameTable.FileNamesByOffset[nameOffset];
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/ItemDebugForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsEdit.UI
2 | {
3 | partial class ItemDebugForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.richTextBox = new System.Windows.Forms.RichTextBox();
32 | this.SuspendLayout();
33 | //
34 | // richTextBox
35 | //
36 | this.richTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
37 | this.richTextBox.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
38 | this.richTextBox.Location = new System.Drawing.Point(0, 0);
39 | this.richTextBox.Margin = new System.Windows.Forms.Padding(2);
40 | this.richTextBox.Name = "richTextBox";
41 | this.richTextBox.ReadOnly = true;
42 | this.richTextBox.Size = new System.Drawing.Size(415, 388);
43 | this.richTextBox.TabIndex = 1;
44 | this.richTextBox.Text = "";
45 | this.richTextBox.WordWrap = false;
46 | //
47 | // ItemDebugForm
48 | //
49 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
50 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
51 | this.ClientSize = new System.Drawing.Size(415, 388);
52 | this.Controls.Add(this.richTextBox);
53 | this.Name = "ItemDebugForm";
54 | this.Text = "Item Debug";
55 | this.Load += new System.EventHandler(this.ArchiveDebugForm_Load);
56 | this.ResumeLayout(false);
57 |
58 | }
59 |
60 | #endregion
61 |
62 | private System.Windows.Forms.RichTextBox richTextBox;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/ArchiveDebugForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsEdit.UI
2 | {
3 | partial class ArchiveDebugForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.richTextBox = new System.Windows.Forms.RichTextBox();
32 | this.SuspendLayout();
33 | //
34 | // richTextBox
35 | //
36 | this.richTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
37 | this.richTextBox.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
38 | this.richTextBox.Location = new System.Drawing.Point(0, 0);
39 | this.richTextBox.Margin = new System.Windows.Forms.Padding(2);
40 | this.richTextBox.Name = "richTextBox";
41 | this.richTextBox.ReadOnly = true;
42 | this.richTextBox.Size = new System.Drawing.Size(415, 388);
43 | this.richTextBox.TabIndex = 1;
44 | this.richTextBox.Text = "";
45 | this.richTextBox.WordWrap = false;
46 | //
47 | // ArchiveDebugForm
48 | //
49 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
50 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
51 | this.ClientSize = new System.Drawing.Size(415, 388);
52 | this.Controls.Add(this.richTextBox);
53 | this.Name = "ArchiveDebugForm";
54 | this.Text = "Archive Debug";
55 | this.Load += new System.EventHandler(this.ArchiveDebugForm_Load);
56 | this.ResumeLayout(false);
57 |
58 | }
59 |
60 | #endregion
61 |
62 | private System.Windows.Forms.RichTextBox richTextBox;
63 | }
64 | }
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Item/NefsItemAttributes.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.Item;
4 |
5 | ///
6 | /// Additional attributes that describe an item.
7 | ///
8 | public readonly record struct NefsItemAttributes
9 | {
10 | ///
11 | /// Initializes a new instance of the struct.
12 | ///
13 | /// Indicates if item is cacheable.
14 | /// Indicates if item is directory.
15 | /// Indicates if item has duplicates.
16 | /// Indicates if item is patched.
17 | /// Version 2.0 - Indicates if item's data is zlib compressed.
18 | /// Version 2.0 - Indicates if item's data is AES encrypted.
19 | public NefsItemAttributes(
20 | bool isCacheable = false,
21 | bool isDirectory = false,
22 | bool isDuplicated = false,
23 | bool isPatched = false,
24 | bool v20IsZlib = false,
25 | bool v20IsAes = false)
26 | {
27 | IsCacheable = isCacheable;
28 | IsDirectory = isDirectory;
29 | IsDuplicated = isDuplicated;
30 | IsPatched = isPatched;
31 | V20IsZlib = v20IsZlib;
32 | V20IsAes = v20IsAes;
33 | }
34 |
35 | ///
36 | /// A flag indicating whether this item is cacheable (presumably by the game engine).
37 | ///
38 | public bool IsCacheable { get; }
39 |
40 | ///
41 | /// A flag indicating whether this item is a directory.
42 | ///
43 | public bool IsDirectory { get; }
44 |
45 | ///
46 | /// A flag indicating whether this item is duplicated.
47 | ///
48 | public bool IsDuplicated { get; }
49 |
50 | ///
51 | /// Whether this item is the last sibling within its parent.
52 | ///
53 | public bool IsLastSibling { get; init; }
54 |
55 | ///
56 | /// A flag indicating whether this item is patched (meaning unknown).
57 | ///
58 | public bool IsPatched { get; }
59 |
60 | ///
61 | /// The volume containing the item.
62 | ///
63 | public ushort Volume { get; init; }
64 |
65 | ///
66 | /// A flag indicating whether the item's data has transforms applied (compressed, encrypted, etc).
67 | ///
68 | public bool IsTransformed { get; init; }
69 |
70 | ///
71 | /// Version 2.0 - indicates whether the item's data is AES encrypted.
72 | ///
73 | public bool V20IsAes { get; }
74 |
75 | ///
76 | /// Version 2.0 - indicates whether the item's data is zlib compressed.
77 | ///
78 | public bool V20IsZlib { get; }
79 | }
80 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib.Tests/IO/NefsReaderStrategyTests.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.IO;
4 | using VictorBush.Ego.NefsLib.Progress;
5 | using Xunit;
6 |
7 | namespace VictorBush.Ego.NefsLib.Tests.IO;
8 |
9 | public class NefsReaderStrategyTests
10 | {
11 | private readonly NefsProgress p = new(CancellationToken.None);
12 |
13 | [Fact]
14 | public async Task ReadHeaderPart3Async_DoubleTerminators_StringsRead()
15 | {
16 | byte[] bytes =
17 | {
18 | // Offset
19 | 0xFF, 0xFF,
20 |
21 | // Entries
22 | 0x41, 0x42, 0x00,
23 | 0x43, 0x00, 0x00, // Double terminators
24 | 0x45, 0x46, 0x00,
25 | };
26 |
27 | var stream = new MemoryStream(bytes);
28 | var size = 9;
29 | var offset = 2;
30 |
31 | // Test
32 | var part3 = await NefsReaderStrategy.ReadHeaderPart3Async(stream, offset, size, this.p);
33 |
34 | // Verify
35 | Assert.Equal(4, part3.FileNamesByOffset.Count);
36 | Assert.Equal(4, part3.OffsetsByFileName.Count);
37 | Assert.Equal("AB", part3.FileNamesByOffset[0]);
38 | Assert.Equal("C", part3.FileNamesByOffset[3]);
39 | Assert.Equal("", part3.FileNamesByOffset[5]);
40 | Assert.Equal("EF", part3.FileNamesByOffset[6]);
41 | }
42 |
43 | [Fact]
44 | public async Task ReadHeaderPart3Async_NoEndingTerminator_StringsRead()
45 | {
46 | byte[] bytes =
47 | {
48 | // Offset
49 | 0xFF, 0xFF,
50 |
51 | // Entries
52 | 0x41, 0x42, 0x00,
53 | 0x43, 0x44, 0x00,
54 | 0x45, 0x46, 0x47, // No ending terminator
55 | };
56 |
57 | var stream = new MemoryStream(bytes);
58 | var size = 9;
59 | var offset = 2;
60 |
61 | // Test
62 | var part3 = await NefsReaderStrategy.ReadHeaderPart3Async(stream, offset, size, this.p);
63 |
64 | // Verify
65 | Assert.Equal(2, part3.FileNamesByOffset.Count);
66 | Assert.Equal(2, part3.OffsetsByFileName.Count);
67 | Assert.Equal("AB", part3.FileNamesByOffset[0]);
68 | Assert.Equal("CD", part3.FileNamesByOffset[3]);
69 | }
70 |
71 | [Fact]
72 | public async Task ReadHeaderPart3Async_ValidData_StringsRead()
73 | {
74 | byte[] bytes =
75 | {
76 | // Offset
77 | 0xFF, 0xFF,
78 |
79 | // Entries
80 | 0x41, 0x42, 0x00,
81 | 0x43, 0x44, 0x00,
82 | 0x45, 0x46, 0x00,
83 | };
84 |
85 | var stream = new MemoryStream(bytes);
86 | var size = 9;
87 | var offset = 2;
88 |
89 | // Test
90 | var part3 = await NefsReaderStrategy.ReadHeaderPart3Async(stream, offset, size, this.p);
91 |
92 | // Verify
93 | Assert.Equal(3, part3.FileNamesByOffset.Count);
94 | Assert.Equal(3, part3.OffsetsByFileName.Count);
95 | Assert.Equal("AB", part3.FileNamesByOffset[0]);
96 | Assert.Equal("CD", part3.FileNamesByOffset[3]);
97 | Assert.Equal("EF", part3.FileNamesByOffset[6]);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Program.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.IO;
4 | using System.IO.Abstractions;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 | using Microsoft.Extensions.Logging;
8 | using Serilog;
9 | using VictorBush.Ego.NefsEdit.Services;
10 | using VictorBush.Ego.NefsEdit.UI;
11 | using VictorBush.Ego.NefsEdit.Utility;
12 | using VictorBush.Ego.NefsEdit.Workspace;
13 | using VictorBush.Ego.NefsLib;
14 | using VictorBush.Ego.NefsLib.IO;
15 |
16 | // Expose NefsEdit classes to test project
17 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("VictorBush.Ego.NefsEdit.Tests")]
18 |
19 | // Required for mocking in the test project
20 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")]
21 |
22 | namespace VictorBush.Ego.NefsEdit;
23 |
24 | ///
25 | /// The NefsEdit application.
26 | ///
27 | internal static class Program
28 | {
29 | ///
30 | /// Gets the directory where the application exe is located.
31 | ///
32 | internal static string ExeDirectory => Application.StartupPath;
33 |
34 | ///
35 | /// Gets the directory used by the application for writing temporary files.
36 | ///
37 | internal static string TempDirectory => Path.Combine(ExeDirectory, "temp");
38 |
39 | ///
40 | /// The main entry point for the application.
41 | ///
42 | [STAThread]
43 | internal static void Main()
44 | {
45 | // Logging
46 | LogHelper.LoggerFactory = new LoggerFactory();
47 | var logConfig = new LoggerConfiguration()
48 | .WriteTo.Console()
49 | .CreateLogger();
50 | LogHelper.LoggerFactory.AddSerilog(logConfig);
51 | NefsLog.LoggerFactory = LogHelper.LoggerFactory;
52 |
53 | // Setup workspace and services
54 | var host = new HostBuilder()
55 | .ConfigureLogging(x => x.AddSerilog(logConfig))
56 | .ConfigureServices(x =>
57 | {
58 | x.AddSingleton();
59 | x.AddSingleton();
60 | x.AddSingleton(_ => System.Windows.Threading.Dispatcher.CurrentDispatcher);
61 | x.AddSingleton();
62 | x.AddSingleton();
63 | x.AddSingleton();
64 | x.AddSingleton();
65 | x.AddSingleton();
66 | x.AddSingleton(x => new NefsWriter(TempDirectory, x.GetRequiredService(), x.GetRequiredService()));
67 | x.AddSingleton();
68 | x.AddSingleton();
69 | }).Build();
70 |
71 | // Run application
72 | ApplicationConfiguration.Initialize();
73 | Application.Run(host.Services.GetRequiredService());
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/BrowseAllForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace VictorBush.Ego.NefsEdit.UI
2 | {
3 | partial class BrowseAllForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.itemsListView = new System.Windows.Forms.ListView();
32 | this.SuspendLayout();
33 | //
34 | // itemsListView
35 | //
36 | this.itemsListView.Dock = System.Windows.Forms.DockStyle.Fill;
37 | this.itemsListView.FullRowSelect = true;
38 | this.itemsListView.GridLines = true;
39 | this.itemsListView.Location = new System.Drawing.Point(0, 0);
40 | this.itemsListView.Margin = new System.Windows.Forms.Padding(2);
41 | this.itemsListView.Name = "itemsListView";
42 | this.itemsListView.Size = new System.Drawing.Size(609, 499);
43 | this.itemsListView.TabIndex = 0;
44 | this.itemsListView.UseCompatibleStateImageBehavior = false;
45 | this.itemsListView.View = System.Windows.Forms.View.Details;
46 | this.itemsListView.SelectedIndexChanged += new System.EventHandler(this.ItemsListView_SelectedIndexChanged);
47 | this.itemsListView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ItemsListView_MouseUp);
48 | //
49 | // BrowseAllForm
50 | //
51 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
52 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
53 | this.ClientSize = new System.Drawing.Size(609, 499);
54 | this.Controls.Add(this.itemsListView);
55 | this.Margin = new System.Windows.Forms.Padding(2);
56 | this.Name = "BrowseAllForm";
57 | this.Text = "Debug View";
58 | this.ResumeLayout(false);
59 |
60 | }
61 |
62 | #endregion
63 |
64 | private System.Windows.Forms.ListView itemsListView;
65 | }
66 | }
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/DataSource/NefsVolumeSource.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | namespace VictorBush.Ego.NefsLib.DataSource;
4 |
5 | public class NefsVolumeSource
6 | {
7 | private const int PrimaryFileNumber = -1;
8 | private readonly string baseFilePath;
9 |
10 | ///
11 | /// The primary volume file path.
12 | ///
13 | public string FilePath { get; }
14 |
15 | ///
16 | /// The offset to the data in the volume.
17 | ///
18 | public uint DataOffset { get; }
19 |
20 | ///
21 | /// The volume data split size.
22 | ///
23 | public uint SplitSize { get; }
24 |
25 | ///
26 | /// Whether the volume data is split.
27 | ///
28 | public bool IsSplit => SplitSize != 0;
29 |
30 | ///
31 | /// Initializes a new instance of .
32 | ///
33 | /// The primary volume file path.
34 | /// The offset to the data in the volume.
35 | /// The volume data split size.
36 | internal NefsVolumeSource(string filePath, uint dataOffset, uint splitSize)
37 | {
38 | FilePath = filePath;
39 | DataOffset = dataOffset;
40 | SplitSize = splitSize;
41 |
42 | this.baseFilePath = Path.Combine(Path.GetDirectoryName(filePath) ?? string.Empty,
43 | Path.GetFileNameWithoutExtension(filePath)) + ".";
44 | }
45 |
46 | ///
47 | /// Gets the path at the given volume byte position.
48 | ///
49 | /// The byte position in the volume.
50 | /// The split file path, or the primary path at the given position.
51 | internal string GetPathAtPosition(long position)
52 | {
53 | return GetPathAtFileNumber(GetFileNumberAtPosition(position));
54 | }
55 |
56 | ///
57 | /// Gets the path for the given split file number. If the volume is not split, the primary path is returned.
58 | ///
59 | /// The split file number.
60 | /// The split file number path, or the primary path if number is -1 or not split.
61 | internal string GetPathAtFileNumber(int number)
62 | {
63 | if (!IsSplit || number == PrimaryFileNumber)
64 | {
65 | return FilePath;
66 | }
67 |
68 | return this.baseFilePath + number.ToString("D3");
69 | }
70 |
71 | ///
72 | /// Gets the split file number at the given volume byte position.
73 | ///
74 | /// The byte position in the volume.
75 | /// The split file number, or -1 to represent the primary path.
76 | internal int GetFileNumberAtPosition(long position)
77 | {
78 | return !IsSplit || position < DataOffset
79 | ? PrimaryFileNumber
80 | : Convert.ToInt32((position - DataOffset) / SplitSize);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/ProgressDialogForm.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsEdit.Services;
4 | using VictorBush.Ego.NefsLib.Progress;
5 |
6 | namespace VictorBush.Ego.NefsEdit.UI;
7 |
8 | ///
9 | /// Form that monitors progress reporting for the provided progress reporter.
10 | ///
11 | ///
12 | ///
13 | /// Usage: var progressDialog = new ProgressDialogForm(); var progressInfo = progressDialog.ProgressInfo;
14 | ///
15 | /// // Show the modal dialog asynchronously (just using ShowDialog() would block // and prevent the async operation from
16 | /// executing) var progressDialogTask = progressDialog.ShowDialogAsync();
17 | ///
18 | /// // Do the async operation var task = await doSomethingAsync(progressInfo);
19 | ///
20 | /// // Close the dialog progressDialog.Close();
21 | ///
22 | /// //.
23 | ///
24 | ///
25 | internal partial class ProgressDialogForm : Form
26 | {
27 | private CancellationTokenSource cancelSource;
28 |
29 | ///
30 | /// Initializes a new instance of the class.
31 | ///
32 | /// The UI service to use.
33 | public ProgressDialogForm(IUiService uiService)
34 | {
35 | InitializeComponent();
36 | UiService = uiService ?? throw new ArgumentNullException(nameof(uiService));
37 |
38 | /* Setup cancellation */
39 | this.cancelSource = new CancellationTokenSource();
40 |
41 | /* Create a progress reporter */
42 | ProgressInfo = new NefsProgress(this.cancelSource.Token);
43 | ProgressInfo.ProgressChanged += OnProgress;
44 | }
45 |
46 | ///
47 | /// Gets the progress info used by the dialog.
48 | ///
49 | public NefsProgress ProgressInfo { get; }
50 |
51 | private IUiService UiService { get; }
52 |
53 | ///
54 | /// Sets the progress bar style.
55 | ///
56 | /// The style.
57 | public void SetStyle(ProgressBarStyle style)
58 | {
59 | this.progressBar.Style = style;
60 | }
61 |
62 | private void CancelButton_Click(object? sender, EventArgs e)
63 | {
64 | /* Set the cancellation token to cancel */
65 | this.cancelSource.Cancel();
66 | }
67 |
68 | private void OnProgress(object? sender, NefsProgressEventArgs e)
69 | {
70 | /* Constrain the progress percentage to appropriate range */
71 | var value = Math.Min((int)(e.Progress * 100), this.progressBar.Maximum);
72 | value = Math.Max(value, 0);
73 |
74 | /* Update the form controls - must do on UI thread */
75 | if (UiService.Dispatcher.CheckAccess())
76 | {
77 | Action();
78 | }
79 | else
80 | {
81 | UiService.Dispatcher.Invoke(Action);
82 | }
83 |
84 | return;
85 |
86 | void Action()
87 | {
88 | this.progressBar.Value = value;
89 | this.statusLabel.Text = $"{e.Message}{Environment.NewLine}{e.SubMessage}";
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CodeMaid.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | False
12 |
13 |
14 | True
15 |
16 |
17 | 120
18 |
19 |
20 | <?xml version="1.0" encoding="utf-16"?>
21 | <ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
22 | <string>ReSharper disable </string>
23 | <string>ReSharper enable </string>
24 | </ArrayOfString>
25 |
26 |
27 | 1
28 |
29 |
30 |
31 |
32 |
33 |
35 | False
36 |
37 |
38 | Methods||8||Methods
39 |
40 |
41 | Properties||6||Properties
42 |
43 |
44 | Enums||9||Enums
45 |
46 |
47 | Indexers||7||Indexers
48 |
49 |
50 | Interfaces||10||Interfaces
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Version150/NefsHeader150.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using VictorBush.Ego.NefsLib.Header.Version010;
4 | using VictorBush.Ego.NefsLib.IO;
5 | using VictorBush.Ego.NefsLib.Utility;
6 |
7 | namespace VictorBush.Ego.NefsLib.Header.Version150;
8 |
9 | ///
10 | public sealed class NefsHeader150 : INefsHeader
11 | {
12 | public NefsWriterSettings WriterSettings { get; }
13 | public NefsTocHeader150 Intro { get; }
14 | public NefsHeaderEntryTable150 EntryTable { get; }
15 | public NefsHeaderSharedEntryInfoTable150 SharedEntryInfoTable { get; }
16 | public NefsHeaderNameTable NameTable { get; }
17 | public NefsHeaderBlockTable010 BlockTable { get; }
18 | public NefsHeaderVolumeInfoTable150 VolumeInfoTable { get; }
19 |
20 | ///
21 | public NefsVersion Version => (NefsVersion)Intro.Version;
22 |
23 | ///
24 | public bool IsLittleEndian => WriterSettings.IsLittleEndian;
25 |
26 | ///
27 | public bool IsEncrypted => WriterSettings.IsEncrypted;
28 |
29 | ///
30 | public byte[] AesKey => Intro.AesKey.GetAesKey();
31 |
32 | ///
33 | public Sha256Hash Hash => new();
34 |
35 | ///
36 | public uint Size => Intro.TocSize;
37 |
38 | ///
39 | public uint BlockSize => Intro.BlockSize;
40 |
41 | ///
42 | public uint SplitSize => Intro.SplitSize;
43 |
44 | ///
45 | public uint NumEntries => Intro.NumEntries;
46 |
47 | ///
48 | public IReadOnlyList Volumes { get; }
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | public NefsHeader150(
54 | NefsWriterSettings writerSettings,
55 | NefsTocHeader150 header,
56 | NefsHeaderEntryTable150 entryTable,
57 | NefsHeaderSharedEntryInfoTable150 sharedEntryInfoTable,
58 | NefsHeaderNameTable nameTable,
59 | NefsHeaderBlockTable010 blockTable,
60 | NefsHeaderVolumeInfoTable150 volumeInfoTable)
61 | {
62 | WriterSettings = writerSettings;
63 | Intro = header;
64 | EntryTable = entryTable ?? throw new ArgumentNullException(nameof(entryTable));
65 | SharedEntryInfoTable = sharedEntryInfoTable ?? throw new ArgumentNullException(nameof(sharedEntryInfoTable));
66 | NameTable = nameTable ?? throw new ArgumentNullException(nameof(nameTable));
67 | BlockTable = blockTable ?? throw new ArgumentNullException(nameof(blockTable));
68 | VolumeInfoTable = volumeInfoTable ?? throw new ArgumentNullException(nameof(volumeInfoTable));
69 |
70 | Volumes = VolumeInfoTable.Entries.Select(x => new VolumeInfo
71 | {
72 | Size = x.Size,
73 | Name = NameTable.FileNamesByOffset[x.NameOffset],
74 | DataOffset = x.DataOffset
75 | }).ToArray();
76 | }
77 |
78 | ///
79 | public string GetFileName(uint nameOffset)
80 | {
81 | return NameTable.FileNamesByOffset[nameOffset];
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/UI/OpenFileForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | text/microsoft-resx
50 |
51 |
52 | 2.0
53 |
54 |
55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
56 |
57 |
58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
59 |
60 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsLib/Header/Builder/NefsItemListBuilder150Base.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Diagnostics;
4 | using Microsoft.Extensions.Logging;
5 | using VictorBush.Ego.NefsLib.DataSource;
6 | using VictorBush.Ego.NefsLib.Header.Version150;
7 | using VictorBush.Ego.NefsLib.Item;
8 |
9 | namespace VictorBush.Ego.NefsLib.Header.Builder;
10 |
11 | internal abstract class NefsItemListBuilder150Base(T header, ILogger logger)
12 | : NefsItemListBuilder(header, logger)
13 | where T : INefsHeader
14 | {
15 | protected NefsItem BuildItem(
16 | NefsItemId id,
17 | NefsTocEntry150 entry,
18 | NefsTocSharedEntryInfo150 sharedEntryInfo,
19 | NefsItemList itemList)
20 | {
21 | // Gather attributes
22 | var attributes = CreateAttributes(entry);
23 |
24 | // Data source
25 | INefsDataSource dataSource;
26 | NefsDataTransform? transform = null;
27 | if (attributes.IsDirectory)
28 | {
29 | // Item is a directory
30 | dataSource = new NefsEmptyDataSource();
31 | }
32 | else
33 | {
34 | // Offset and size
35 | var dataOffset = Convert.ToInt64(entry.Start);
36 | var extractedSize = sharedEntryInfo.Size;
37 |
38 | var numBlocks = GetNumBlocks(extractedSize);
39 | var blocks = BuildBlockList(entry.FirstBlock, numBlocks, attributes.IsTransformed ? null : GetTransform(0));
40 | transform = blocks.FirstOrDefault()?.Transform ?? GetTransform(0);
41 | var size = new NefsItemSize(extractedSize, blocks);
42 | dataSource = new NefsVolumeDataSource(itemList.Volumes[entry.Volume], dataOffset, size);
43 | }
44 |
45 | // Create item
46 | var duplicateId = new NefsItemId(sharedEntryInfo.FirstDuplicate);
47 | var parentId = new NefsItemId(sharedEntryInfo.Parent);
48 | var fileName = Header.GetFileName(sharedEntryInfo.NameOffset);
49 | return new NefsItem(id, duplicateId, fileName, parentId, dataSource, transform, attributes);
50 |
51 | static NefsItemAttributes CreateAttributes(NefsTocEntry150 entry)
52 | {
53 | Debug.Assert((entry.Flags & 0xFFE0) == 0);
54 | var flags = (NefsTocEntryFlags150)entry.Flags;
55 | return new NefsItemAttributes(
56 | isDirectory: flags.HasFlag(NefsTocEntryFlags150.Directory),
57 | isDuplicated: flags.HasFlag(NefsTocEntryFlags150.Duplicated),
58 | isCacheable: flags.HasFlag(NefsTocEntryFlags150.Cacheable),
59 | isPatched: flags.HasFlag(NefsTocEntryFlags150.Patched))
60 | {
61 | IsTransformed = flags.HasFlag(NefsTocEntryFlags150.Transformed),
62 | IsLastSibling = flags.HasFlag(NefsTocEntryFlags150.LastSibling),
63 | Volume = entry.Volume
64 | };
65 | }
66 | }
67 |
68 | ///
69 | protected override NefsDataTransformType GetTransformType(uint blockTransformation)
70 | {
71 | return blockTransformation switch
72 | {
73 | 0 => NefsDataTransformType.None,
74 | 1 => NefsDataTransformType.Lzss,
75 | 4 => NefsDataTransformType.Aes,
76 | 7 => NefsDataTransformType.Zlib,
77 | _ => (NefsDataTransformType)(-1)
78 | };
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/VictorBush.Ego.NefsEdit/Services/IUiService.cs:
--------------------------------------------------------------------------------
1 | // See LICENSE.txt for license information.
2 |
3 | using System.Windows.Threading;
4 | using VictorBush.Ego.NefsLib.ArchiveSource;
5 | using VictorBush.Ego.NefsLib.IO;
6 |
7 | namespace VictorBush.Ego.NefsEdit.Services;
8 |
9 | ///
10 | /// Provides user interface dialogs and other services.
11 | ///
12 | internal interface IUiService
13 | {
14 | ///
15 | /// Gets the dispatcher for the UI thread.
16 | ///
17 | Dispatcher Dispatcher { get; }
18 |
19 | ///
20 | /// Shows a folder browser dialog.
21 | ///
22 | /// A message to show in the dialog.
23 | /// The dialog result and the folder path (if applicable).
24 | (DialogResult Result, string Path) ShowFolderBrowserDialog(string message);
25 |
26 | ///
27 | /// Shows a message box.
28 | ///
29 | /// The message to display.
30 | /// The title of the message box.
31 | /// Buttons to show.
32 | /// Icon to display.
33 | /// The dialog result.
34 | DialogResult ShowMessageBox(
35 | string message,
36 | string? title = null,
37 | MessageBoxButtons buttons = MessageBoxButtons.OK,
38 | MessageBoxIcon icon = MessageBoxIcon.None);
39 |
40 | ///
41 | /// Shows the NeFS Edit dialog for opening an archive file.
42 | ///
43 | /// The settings service.
44 | /// The progress service.
45 | /// The nefs reader.
46 | /// The exe header finder.
47 | /// The dialog result and the archive source (if applicable).
48 | (DialogResult Result, NefsArchiveSource? Source) ShowNefsEditOpenFileDialog(
49 | ISettingsService settingsService,
50 | IProgressService progressService,
51 | INefsReader reader,
52 | INefsExeHeaderFinder exeHeaderFinder);
53 |
54 | ///
55 | /// Shows an open file dialog.
56 | ///
57 | /// Filter for dialog.
58 | /// The dialog result and the file name (if applicable).
59 | (DialogResult Result, string FileName) ShowOpenFileDialog(string? filter = null);
60 |
61 | ///
62 | /// Shows a save file dialog.
63 | ///
64 | /// The default file name.
65 | /// Filter for dialog.
66 | /// The dialog result and the file name (if applicable).
67 | (DialogResult Result, string FileName) ShowSaveFileDialog(string defaultName, string? filter = null);
68 |
69 | ///
70 | /// Shows the settings dialog.
71 | ///
72 | /// The settings service to use.
73 | /// The dialog result.
74 | DialogResult ShowSettingsDialog(ISettingsService settingsService);
75 | }
76 |
--------------------------------------------------------------------------------