├── .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 | --------------------------------------------------------------------------------