├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── CHANGES.md ├── CodeMaid.config ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── LICENSE ├── NeFSedit.sln ├── README.md ├── TODO.md ├── VictorBush.Ego.NefsEdit.Tests ├── .gitignore ├── Source │ ├── Commands │ │ ├── TestCommand.cs │ │ └── UndoBufferTests.cs │ ├── Services │ │ └── InvisibleProgressService.cs │ ├── TestHelpers.cs │ └── Workspace │ │ └── NefsEditWorkspaceTests.cs ├── VictorBush.Ego.NefsEdit.Tests.csproj └── VictorBush.Ego.NefsEdit.Tests.csproj.user ├── VictorBush.Ego.NefsEdit ├── .gitignore ├── App.config ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── README.md ├── Resources │ ├── arrow_Forward_16xLG.png │ ├── arrow_Up_16xLG.png │ ├── arrow_back_16xLG.png │ └── question.ico ├── Source │ ├── Commands │ │ ├── INefsEditCommand.cs │ │ ├── NefsEditCommandEventArgs.cs │ │ ├── NefsEditCommandEventKind.cs │ │ ├── RemoveFileCommand.cs │ │ ├── RemoveFileDuplicatesCommand.cs │ │ ├── ReplaceFileCommand.cs │ │ ├── ReplaceFileDuplicatesCommand.cs │ │ └── UndoBuffer.cs │ ├── Program.cs │ ├── Services │ │ ├── IProgressService.cs │ │ ├── ISettingsService.cs │ │ ├── IUiService.cs │ │ ├── ProgressService.cs │ │ ├── SettingsService.cs │ │ └── UiService.cs │ ├── Settings │ │ ├── OpenFileDialogState.cs │ │ ├── RecentFile.cs │ │ └── Settings.cs │ ├── UI │ │ ├── ArchiveDebugForm.Designer.cs │ │ ├── ArchiveDebugForm.cs │ │ ├── ArchiveDebugForm.resx │ │ ├── BrowseAllForm.Designer.cs │ │ ├── BrowseAllForm.cs │ │ ├── BrowseAllForm.resx │ │ ├── BrowseTreeForm.Designer.cs │ │ ├── BrowseTreeForm.cs │ │ ├── BrowseTreeForm.resx │ │ ├── ConsoleForm.Designer.cs │ │ ├── ConsoleForm.cs │ │ ├── ConsoleForm.resx │ │ ├── EditorForm.Designer.cs │ │ ├── EditorForm.cs │ │ ├── EditorForm.resx │ │ ├── ItemDebugForm.Designer.cs │ │ ├── ItemDebugForm.cs │ │ ├── ItemDebugForm.resx │ │ ├── OpenFileForm.Designer.cs │ │ ├── OpenFileForm.cs │ │ ├── OpenFileForm.resx │ │ ├── ProgressDialogForm.Designer.cs │ │ ├── ProgressDialogForm.cs │ │ ├── ProgressDialogForm.resx │ │ ├── PropertyGridForm.Designer.cs │ │ ├── PropertyGridForm.cs │ │ ├── PropertyGridForm.resx │ │ ├── SettingsForm.Designer.cs │ │ ├── SettingsForm.cs │ │ ├── SettingsForm.resx │ │ └── TablessControl.cs │ ├── Utility │ │ ├── Constants.cs │ │ ├── DialogExtensions.cs │ │ ├── HexStringTypeConverter.cs │ │ ├── ListViewColumnSorter.cs │ │ ├── LogHelper.cs │ │ ├── RichTextWriter.cs │ │ ├── TextBoxExtensions.cs │ │ └── TreeNodeExtensions.cs │ └── Workspace │ │ ├── INefsEditWorkspace.cs │ │ └── NefsEditWorkspace.cs └── VictorBush.Ego.NefsEdit.csproj ├── VictorBush.Ego.NefsLib.Tests ├── .gitignore ├── Header │ └── Builder │ │ ├── NefsHeaderBuilder160Tests.cs │ │ ├── NefsHeaderBuilder200Tests.cs │ │ └── NefsItemListBuilder200Tests.cs ├── IO │ ├── NefsReaderStrategy150Tests.cs │ ├── NefsReaderStrategy160Tests.cs │ ├── NefsReaderStrategy200Tests.cs │ ├── NefsReaderStrategyTests.cs │ ├── NefsWriterStrategy160Tests.cs │ └── NefsWriterStrategy200Tests.cs ├── Source │ ├── Integration │ │ └── DirtRally2Tests.cs │ ├── TestArchives │ │ ├── TestArchiveModified.cs │ │ ├── TestArchiveNoItems.cs │ │ └── TestArchiveNotModified.cs │ ├── TestHelpers.cs │ └── Tests │ │ ├── DataSource │ │ └── NefsDataChunkTests.cs │ │ ├── DataTypes │ │ ├── ByteArrayTypeTests.cs │ │ ├── DataTypeTests.cs │ │ ├── FileDataTests.cs │ │ ├── ListTypeTests.cs │ │ ├── UInt32TypeTests.cs │ │ └── UInt64TypeTests.cs │ │ ├── Header │ │ └── NefsHeaderNameTableTests.cs │ │ ├── IO │ │ ├── LzssDecompressTests.cs │ │ ├── NefsReaderTests.cs │ │ ├── NefsTransformerTests.cs │ │ └── NefsWriterTests.cs │ │ ├── Item │ │ ├── NefsItemListTests.cs │ │ └── NefsItemTests.cs │ │ ├── Progress │ │ └── NefsProgressTests.cs │ │ └── Utility │ │ ├── HashHelperTests.cs │ │ └── StreamExtensionsTests.cs └── VictorBush.Ego.NefsLib.Tests.csproj └── VictorBush.Ego.NefsLib ├── .gitignore ├── Header ├── AesKeyHexBuffer.cs ├── Builder │ ├── NefsHeaderBuilder.cs │ ├── NefsHeaderBuilder160.cs │ ├── NefsHeaderBuilder160Base.cs │ ├── NefsHeaderBuilder200.cs │ ├── NefsItemListBuilder.cs │ ├── NefsItemListBuilder150.cs │ ├── NefsItemListBuilder150Base.cs │ ├── NefsItemListBuilder151.cs │ ├── NefsItemListBuilder160.cs │ └── NefsItemListBuilder200.cs ├── INefsHeader.cs ├── INefsTocData.cs ├── INefsTocTable.cs ├── NefsConstants.cs ├── NefsDataTransformType.cs ├── NefsHeaderNameTable.cs ├── NefsInjectHeader.cs ├── Version150 │ ├── NefsHeader150.cs │ ├── NefsHeader151.cs │ ├── NefsHeaderBlockTable150.cs │ ├── NefsHeaderBlockTable151.cs │ ├── NefsHeaderEntryTable150.cs │ ├── NefsHeaderSharedEntryInfoTable150.cs │ ├── NefsHeaderVolumeInfoTable150.cs │ ├── NefsTocBlock150.cs │ ├── NefsTocBlock151.cs │ ├── NefsTocEntry150.cs │ ├── NefsTocEntryFlags150.cs │ ├── NefsTocHeader150.cs │ ├── NefsTocHeader151.cs │ ├── NefsTocSharedEntryInfo150.cs │ └── NefsTocVolumeInfo150.cs ├── Version160 │ ├── NefsHeader160.cs │ ├── NefsHeaderEntryTable160.cs │ ├── NefsHeaderHashDigestTable160.cs │ ├── NefsHeaderSharedEntryInfoTable160.cs │ ├── NefsHeaderWriteableEntryTable160.cs │ ├── NefsHeaderWriteableSharedEntryInfoTable160.cs │ ├── NefsTocEntry160.cs │ ├── NefsTocEntryWriteable160.cs │ ├── NefsTocHashDigest160.cs │ ├── NefsTocHeaderA160.cs │ ├── NefsTocHeaderB160.cs │ ├── NefsTocSharedEntryInfo160.cs │ └── NefsTocSharedEntryInfoWriteable160.cs ├── Version200 │ ├── NefsHeader200.cs │ ├── NefsHeaderBlockTable200.cs │ ├── NefsTocBlock200.cs │ ├── NefsTocEntryFlags200.cs │ └── NefsTocHeaderB200.cs └── VolumeInfo.cs ├── IO ├── EndianBinaryReader.cs ├── EndianBinaryWriter.cs ├── INefsReader.cs ├── INefsTransformer.cs ├── INefsWriter.cs ├── LzssDecompress.cs ├── NefsReader.cs ├── NefsReaderStrategy.cs ├── NefsReaderStrategy150.cs ├── NefsReaderStrategy151.cs ├── NefsReaderStrategy160.cs ├── NefsReaderStrategy200.cs ├── NefsRsaKeys.cs ├── NefsTransformer.cs ├── NefsWriter.cs ├── NefsWriterSettings.cs ├── NefsWriterStrategy.cs ├── NefsWriterStrategy160.cs ├── NefsWriterStrategy160Base.cs ├── NefsWriterStrategy200.cs ├── ResizableBuffer.cs └── StructEx.cs ├── NefsArchive.cs ├── NefsLog.cs ├── NefsVersion.cs ├── README.md ├── Source ├── ArchiveSource │ ├── HeadlessSource.cs │ ├── NefsArchiveSource.cs │ ├── NefsInjectSource.cs │ └── StandardSource.cs ├── DataSource │ ├── INefsDataSource.cs │ ├── NefsDataChunk.cs │ ├── NefsDataTransform.cs │ ├── NefsEmptyDataSource.cs │ ├── NefsFileDataSource.cs │ └── NefsItemListDataSource.cs ├── DataTypes │ ├── ByteArrayType.cs │ ├── DataType.cs │ ├── FileData.cs │ ├── ListType.cs │ ├── UInt16Type.cs │ ├── UInt32Type.cs │ ├── UInt64Type.cs │ └── UInt8Type.cs ├── Item │ ├── NefsItem.cs │ ├── NefsItemAttributes.cs │ ├── NefsItemId.cs │ ├── NefsItemList.cs │ ├── NefsItemSize.cs │ ├── NefsItemState.cs │ └── NefsItemType.cs ├── Progress │ ├── NefsProgress.cs │ ├── NefsProgressEventArgs.cs │ └── NefsProgressTask.cs └── Utility │ ├── DeflateHelper.cs │ ├── DictionaryExtensions.cs │ ├── FileSystemExtensions.cs │ ├── HashHelper.cs │ ├── IsExternalInit.cs │ ├── PeHelper.cs │ ├── Sha256Hash.cs │ ├── Sha256HashBuilder.cs │ ├── StreamExtensions.cs │ └── StringHelper.cs └── VictorBush.Ego.NefsLib.csproj /.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 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.cs text diff=csharp 4 | *.csproj text merge=union 5 | *.sln text merge=union -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs/ 2 | /TestResults/ 3 | /releases/ 4 | *.csproj.user 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "VictorBush.Ego.NefsCommon"] 2 | path = VictorBush.Ego.NefsCommon 3 | url = https://github.com/victorbush/ego.nefscommon.git 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.6.0 4 | - Added support for NeFS version 1.6. 5 | - Added recent files list and improved open file dialog. 6 | - Added support for handling duplicate items in archives. 7 | - Improved finding and loading NeFS headers from executables. 8 | - Fixed various loading bugs for NeFS version 2.0 headers. 9 | 10 | ## Version 0.5.0 11 | - Added ability to open game.dat files (archives with separate header/data files). 12 | - Added ability to close an archive. 13 | - Added archive and item debug output views. 14 | - Fixed "Extract To" to extract directly to the location specified. 15 | - Fixed/improved intepretation of various NeFS header fields. 16 | - Replaced Archive Details pane with an Archive Debug view. 17 | - Refactored NefsEdit and NefsLib substantially. 18 | - Improved application/library separation. 19 | - Added various unit tests. 20 | - Switched to Serilog for logging. 21 | 22 | ## Version 0.4.0 23 | - Added ability to open encrypted NeFS archives. 24 | - Fixed issue with extracting non-compressed files. 25 | 26 | ## Version 0.3.0 27 | - Added ability to extract multiple files at once. 28 | - Added ability to extract directories. 29 | - Added Quick Extract feature. 30 | - Fixed some issues with loading and replacing items from game.nefs. 31 | 32 | ## Version 0.2.0 33 | - Added context menu when right-clicking items. 34 | - Added keyboard shortcut keys. 35 | - Added ability to save archive in place (in addition to Save As). 36 | - Using .NET compression library for extraction now. 37 | - Removed dependency on offzip and packzip. 38 | 39 | ## Version 0.1.2 40 | - Using .NET compression library instead of spawning packzip processes. 41 | 42 | ## Version 0.1.1 43 | - Fixed some issues related to replacing files in archive with larger files. 44 | 45 | ## Version 0.1.0 46 | - Initial release. Limited functionality and little polish. 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | latest 4 | true 5 | 6 | Copyright © VictorBush 2021 7 | 0.7.0 8 | 9 | 10 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | - Checksum in block entries and 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 | - Allow associating .nefs files with the app, and allow opening an archive via command line. 10 | - Add protection against saving over an archive in a game's directory (or always force a "Save As"). 11 | - Support reading and writing multiple volumes 12 | - Support reading and writing split archives across multiple files 13 | - Add support for versions 0.1.0, 0.2.0, 1.3.0, 1.4.0 14 | - Bring back auto-search of nefs headers in game exe 15 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit.Tests/.gitignore: -------------------------------------------------------------------------------- 1 | /obj/ 2 | /bin/ 3 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit.Tests/Source/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.Tests/Source/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.NefsEdit.Tests/Source/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 NefsItemListDataSource(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 NefsItemListDataSource(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.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.NefsEdit.Tests/VictorBush.Ego.NefsEdit.Tests.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ProjectFiles 5 | 6 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/.gitignore: -------------------------------------------------------------------------------- 1 | /obj/ 2 | /bin/ 3 | -------------------------------------------------------------------------------- /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.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.NefsEdit/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 6. 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 | ## Limitations 15 | - Saving encrypted archives is not supported. This would require access to the private key originally used by the game developers. 16 | - 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. 17 | - Creating new archives from scratch is not supported. 18 | - Adding new items to an archive is not supported. 19 | - 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. 20 | 21 | ## Support 22 | 23 | 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. 24 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Resources/arrow_Forward_16xLG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/446179f253ab5cecb1d833e8eeb98b4cba279fc9/VictorBush.Ego.NefsEdit/Resources/arrow_Forward_16xLG.png -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Resources/arrow_Up_16xLG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/446179f253ab5cecb1d833e8eeb98b4cba279fc9/VictorBush.Ego.NefsEdit/Resources/arrow_Up_16xLG.png -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Resources/arrow_back_16xLG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/446179f253ab5cecb1d833e8eeb98b4cba279fc9/VictorBush.Ego.NefsEdit/Resources/arrow_back_16xLG.png -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Resources/question.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoEngineModding/ego.nefsedit/446179f253ab5cecb1d833e8eeb98b4cba279fc9/VictorBush.Ego.NefsEdit/Resources/question.ico -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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/Source/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.NefsEdit/Source/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/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsCommon.InjectionDatabase; 10 | using VictorBush.Ego.NefsEdit.Services; 11 | using VictorBush.Ego.NefsEdit.UI; 12 | using VictorBush.Ego.NefsEdit.Utility; 13 | using VictorBush.Ego.NefsEdit.Workspace; 14 | using VictorBush.Ego.NefsLib; 15 | using VictorBush.Ego.NefsLib.IO; 16 | 17 | // Expose NefsEdit classes to test project 18 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("VictorBush.Ego.NefsEdit.Tests")] 19 | 20 | // Required for mocking in the test project 21 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")] 22 | 23 | namespace VictorBush.Ego.NefsEdit; 24 | 25 | /// 26 | /// The NefsEdit application. 27 | /// 28 | internal static class Program 29 | { 30 | /// 31 | /// Gets the directory where the application exe is located. 32 | /// 33 | internal static string ExeDirectory => Application.StartupPath; 34 | 35 | /// 36 | /// Gets the directory used by the application for writing temporary files. 37 | /// 38 | internal static string TempDirectory => Path.Combine(ExeDirectory, "temp"); 39 | 40 | /// 41 | /// The main entry point for the application. 42 | /// 43 | [STAThread] 44 | internal static void Main() 45 | { 46 | // Logging 47 | LogHelper.LoggerFactory = new LoggerFactory(); 48 | var logConfig = new LoggerConfiguration() 49 | .WriteTo.Console() 50 | .CreateLogger(); 51 | LogHelper.LoggerFactory.AddSerilog(logConfig); 52 | NefsLog.LoggerFactory = LogHelper.LoggerFactory; 53 | 54 | // Setup workspace and services 55 | var host = new HostBuilder() 56 | .ConfigureLogging(x => x.AddSerilog(logConfig)) 57 | .ConfigureServices(x => 58 | { 59 | x.AddSingleton(); 60 | x.AddSingleton(); 61 | x.AddSingleton(_ => System.Windows.Threading.Dispatcher.CurrentDispatcher); 62 | x.AddSingleton(); 63 | x.AddSingleton(); 64 | x.AddSingleton(); 65 | x.AddSingleton(); 66 | x.AddSingleton(); 67 | x.AddSingleton(x => new NefsWriter(TempDirectory, x.GetRequiredService(), x.GetRequiredService())); 68 | x.AddSingleton(); 69 | x.AddSingleton(); 70 | x.AddSingleton(); 71 | }).Build(); 72 | 73 | // Run application 74 | ApplicationConfiguration.Initialize(); 75 | Application.Run(host.Services.GetRequiredService()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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 dialog result and the archive source (if applicable). 47 | (DialogResult Result, NefsArchiveSource? Source) ShowNefsEditOpenFileDialog( 48 | ISettingsService settingsService, 49 | IProgressService progressService, 50 | INefsReader reader); 51 | 52 | /// 53 | /// Shows an open file dialog. 54 | /// 55 | /// Filter for dialog. 56 | /// The dialog result and the file name (if applicable). 57 | (DialogResult Result, string FileName) ShowOpenFileDialog(string? filter = null); 58 | 59 | /// 60 | /// Shows a save file dialog. 61 | /// 62 | /// The default file name. 63 | /// Filter for dialog. 64 | /// The dialog result and the file name (if applicable). 65 | (DialogResult Result, string FileName) ShowSaveFileDialog(string defaultName, string? filter = null); 66 | 67 | /// 68 | /// Shows the settings dialog. 69 | /// 70 | /// The settings service to use. 71 | /// The dialog result. 72 | DialogResult ShowSettingsDialog(ISettingsService settingsService); 73 | } 74 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Source/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.NefsEdit/Source/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 NefsInjectDataFilePath { get; set; } = ""; 52 | 53 | public string NefsInjectFilePath { get; set; } = ""; 54 | 55 | public string HeadlessExePath { get; set; } = ""; 56 | } 57 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Source/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/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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/Source/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/Source/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.NefsEdit/Source/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 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.NefsEdit/Source/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.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 | bin\Debug\NefsEdit.xml 15 | 16 | 17 | 18 | Component 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib.Tests/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | -------------------------------------------------------------------------------- /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.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.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.NefsLib.Tests/Source/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.NefsLib.Tests/Source/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.NefsLib.Tests/Source/Tests/DataTypes/FileDataTests.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.DataTypes; 4 | using Xunit; 5 | 6 | namespace VictorBush.Ego.NefsLib.Tests.DataTypes; 7 | 8 | public sealed class FileDataTests 9 | { 10 | [Fact] 11 | public void GetDataList_DataReturned() 12 | { 13 | var test = new TestClass(); 14 | var data = FileData.GetDataList(test).ToList(); 15 | Assert.Equal(5, data.Count); 16 | Assert.Equal(1, test.OtherVariable); 17 | 18 | // Properties come first, then fields 19 | Assert.Same(test.Data_0x4, data[0]); 20 | Assert.Same(test.Data_0x8, data[1]); 21 | Assert.Same(test.Data_0xA, data[2]); 22 | Assert.Same(test.Data_0xC, data[3]); 23 | Assert.Same(test.Data_0x0, data[4]); 24 | } 25 | 26 | [Fact] 27 | public void GetDataList_Interface_DataReturned() 28 | { 29 | ITestInterface test = new TestClass(); 30 | var data = FileData.GetDataList(test).ToList(); 31 | Assert.Equal(5, data.Count); 32 | Assert.Equal(1, test.OtherVariable); 33 | 34 | // Properties come first, then fields 35 | Assert.Same(test.Data_0x4, data[0]); 36 | Assert.Same(test.Data_0x8, data[1]); 37 | Assert.Same(test.Data_0xA, data[2]); 38 | Assert.Same(test.Data_0xC, data[3]); 39 | Assert.Same(test.Data_0x0, data[4]); 40 | } 41 | 42 | private interface ITestInterface 43 | { 44 | DataType Data_0x0 { get; } 45 | DataType Data_0x4 { get; } 46 | DataType Data_0x8 { get; } 47 | DataType Data_0xA { get; } 48 | DataType Data_0xC { get; } 49 | int OtherVariable { get; } 50 | } 51 | 52 | private class TestClass : ITestInterface 53 | { 54 | private readonly int otherVariable = 1; 55 | 56 | [FileData] 57 | private UInt32Type data0x0 = new UInt32Type(0x0); 58 | 59 | public DataType Data_0x0 => this.data0x0; 60 | public DataType Data_0x4 => Data0x4; 61 | public DataType Data_0x8 => Data0x8; 62 | public DataType Data_0xA => Data0xA; 63 | public DataType Data_0xC => Data0xC; 64 | 65 | [FileData] 66 | public UInt32Type Data0x4 { get; } = new UInt32Type(0x4); 67 | 68 | public int OtherVariable => this.otherVariable; 69 | 70 | [FileData] 71 | private UInt16Type Data0x8 { get; } = new UInt16Type(0x8); 72 | 73 | [FileData] 74 | private UInt16Type Data0xA { get; } = new UInt16Type(0xA); 75 | 76 | [FileData] 77 | private UInt16Type Data0xC { get; } = new UInt16Type(0xC); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib.Tests/Source/Tests/DataTypes/ListTypeTests.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.DataTypes; 4 | using VictorBush.Ego.NefsLib.Progress; 5 | using Xunit; 6 | 7 | namespace VictorBush.Ego.NefsLib.Tests.DataTypes; 8 | 9 | public sealed class ListTypeTests 10 | { 11 | [Fact] 12 | public async Task ListType_ListHasData_ItemsLoaded() 13 | { 14 | var itemSize = 0x2; 15 | var testBytes = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }; 16 | 17 | using (var stream = new MemoryStream(testBytes)) 18 | { 19 | var data = new ListType(0, itemSize, 3, bytes => bytes.ToArray(), bytes => bytes); 20 | await data.ReadAsync(stream, 0, new NefsProgress()); 21 | 22 | Assert.Equal(3, data.ItemCount); 23 | Assert.True(data.Items[0].SequenceEqual(new byte[] { 0x1, 0x2 })); 24 | Assert.True(data.Items[1].SequenceEqual(new byte[] { 0x3, 0x4 })); 25 | Assert.True(data.Items[2].SequenceEqual(new byte[] { 0x5, 0x6 })); 26 | Assert.True(testBytes.SequenceEqual(data.GetBytes())); 27 | } 28 | } 29 | 30 | [Fact] 31 | public void SetItems_ItemsAreValid_ItemsLoaded() 32 | { 33 | var itemSize = 0x2; 34 | var testBytes = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }; 35 | var testItems = new List 36 | { 37 | new byte[] { 0x1, 0x2 }, 38 | new byte[] { 0x3, 0x4 }, 39 | new byte[] { 0x5, 0x6 }, 40 | }; 41 | 42 | var data = new ListType(0, itemSize, 3, bytes => bytes.ToArray(), bytes => bytes); 43 | data.SetItems(testItems); 44 | 45 | Assert.Equal(itemSize, data.ItemSize); 46 | Assert.Equal(3, data.ItemCount); 47 | Assert.Equal(3, data.Items.Count); 48 | Assert.True(data.Items[0].SequenceEqual(new byte[] { 0x1, 0x2 })); 49 | Assert.True(data.Items[1].SequenceEqual(new byte[] { 0x3, 0x4 })); 50 | Assert.True(data.Items[2].SequenceEqual(new byte[] { 0x5, 0x6 })); 51 | Assert.True(testBytes.SequenceEqual(data.GetBytes())); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib.Tests/Source/Tests/DataTypes/UInt32TypeTests.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.DataTypes; 4 | using VictorBush.Ego.NefsLib.Progress; 5 | using Xunit; 6 | 7 | namespace VictorBush.Ego.NefsLib.Tests.DataTypes; 8 | 9 | public sealed class UInt32TypeTests 10 | { 11 | [Fact] 12 | public async Task Read_NegativeOffset_DataRead() 13 | { 14 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 15 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 16 | { 17 | var data = new UInt32Type(-8); 18 | await data.ReadAsync(file, 8, new NefsProgress()); 19 | Assert.Equal((uint)0x05060708, data.Value); 20 | Assert.Equal("0x5060708", data.ToString()); 21 | } 22 | } 23 | 24 | [Fact] 25 | public async Task Read_PositiveOffset_DataRead() 26 | { 27 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 28 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 29 | { 30 | var data = new UInt32Type(8); 31 | await data.ReadAsync(file, 0, new NefsProgress()); 32 | Assert.Equal((uint)0x15161718, data.Value); 33 | Assert.Equal("0x15161718", data.ToString()); 34 | } 35 | } 36 | 37 | [Fact] 38 | public async Task Read_ZeroOffset_DataRead() 39 | { 40 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 41 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 42 | { 43 | var data = new UInt32Type(0x0); 44 | await data.ReadAsync(file, 0, new NefsProgress()); 45 | Assert.Equal((uint)0x05060708, data.Value); 46 | Assert.Equal("0x5060708", data.ToString()); 47 | } 48 | } 49 | 50 | [Fact] 51 | public void Size_4bytes() 52 | { 53 | var data = new UInt32Type(0); 54 | Assert.Equal(4, data.Size); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib.Tests/Source/Tests/DataTypes/UInt64TypeTests.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.DataTypes; 4 | using VictorBush.Ego.NefsLib.Progress; 5 | using Xunit; 6 | 7 | namespace VictorBush.Ego.NefsLib.Tests.DataTypes; 8 | 9 | public sealed class UInt64TypeTests 10 | { 11 | [Fact] 12 | public async Task Read_NegativeOffset_DataRead() 13 | { 14 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 15 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 16 | { 17 | var data = new UInt64Type(-8); 18 | await data.ReadAsync(file, 8, new NefsProgress()); 19 | Assert.Equal((ulong)0x0102030405060708, data.Value); 20 | Assert.Equal("0x102030405060708", data.ToString()); 21 | } 22 | } 23 | 24 | [Fact] 25 | public async Task Read_PositveOffset_DataRead() 26 | { 27 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 28 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 29 | { 30 | var data = new UInt64Type(8); 31 | await data.ReadAsync(file, 0, new NefsProgress()); 32 | Assert.Equal((ulong)0x1112131415161718, data.Value); 33 | Assert.Equal("0x1112131415161718", data.ToString()); 34 | } 35 | } 36 | 37 | [Fact] 38 | public async Task Read_ZeroOffset_DataRead() 39 | { 40 | var fs = TestHelpers.CreateDataTypesTestFileSystem(); 41 | using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) 42 | { 43 | var data = new UInt64Type(0x0); 44 | await data.ReadAsync(file, 0, new NefsProgress()); 45 | Assert.Equal((ulong)0x0102030405060708, data.Value); 46 | Assert.Equal("0x102030405060708", data.ToString()); 47 | } 48 | } 49 | 50 | [Fact] 51 | public void Size_8bytes() 52 | { 53 | var data = new UInt64Type(0); 54 | Assert.Equal(8, data.Size); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib.Tests/Source/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.Tests/Source/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.NefsLib.Tests/Source/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.Tests/Source/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.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/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | -------------------------------------------------------------------------------- /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(64)] 9 | public struct AesKeyHexBuffer 10 | { 11 | private byte element; 12 | 13 | /// 14 | /// Gets the AES-256 key for this header. 15 | /// 16 | /// A byte array with the AES key. 17 | public byte[] GetAesKey() 18 | { 19 | return Convert.FromHexString(ToString()); 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return Encoding.ASCII.GetString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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.Version160 => new NefsHeaderBuilder160(), 22 | NefsVersion.Version200 => new NefsHeaderBuilder200(), 23 | _ => throw new NotImplementedException($"Support for {version.ToPrettyString()} is not implemented.") 24 | }; 25 | 26 | Instances.Add(version, inst); 27 | return inst; 28 | } 29 | 30 | public abstract INefsHeader Build(INefsHeader sourceHeader, NefsItemList items, NefsProgress p); 31 | 32 | public abstract uint ComputeDataOffset(INefsHeader sourceHeader, NefsItemList items); 33 | } 34 | 35 | /// 36 | internal abstract class NefsHeaderBuilder : NefsHeaderBuilder 37 | where T : INefsHeader 38 | { 39 | /// 40 | public override uint ComputeDataOffset(INefsHeader sourceHeader, NefsItemList items) 41 | { 42 | return ComputeDataOffset(sourceHeader.As(), items); 43 | } 44 | 45 | internal abstract uint ComputeDataOffset(T sourceHeader, NefsItemList items); 46 | 47 | /// 48 | public override INefsHeader Build(INefsHeader sourceHeader, NefsItemList items, NefsProgress p) 49 | { 50 | 51 | return Build(sourceHeader.As(), items, p); 52 | } 53 | 54 | internal abstract T Build(T sourceHeader, NefsItemList items, NefsProgress p); 55 | } 56 | -------------------------------------------------------------------------------- /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 dataSourceList) 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, dataSourceList); 19 | } 20 | 21 | protected override (uint End, uint Transformation) GetBlock(uint blockIndex) 22 | { 23 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)]; 24 | return (block.End, block.Transformation); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 dataSourceList) 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, null); 40 | transform = blocks.FirstOrDefault()?.Transform ?? GetTransform(0); 41 | var size = new NefsItemSize(extractedSize, blocks); 42 | dataSource = new NefsItemListDataSource(dataSourceList, 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 | v16IsTransformed: flags.HasFlag(NefsTocEntryFlags150.Transformed), 57 | isDirectory: flags.HasFlag(NefsTocEntryFlags150.Directory), 58 | isDuplicated: flags.HasFlag(NefsTocEntryFlags150.Duplicated), 59 | isCacheable: flags.HasFlag(NefsTocEntryFlags150.Cacheable), 60 | isPatched: flags.HasFlag(NefsTocEntryFlags150.Patched), 61 | part6Volume: entry.Volume) 62 | { 63 | IsLastSibling = flags.HasFlag(NefsTocEntryFlags150.LastSibling) 64 | }; 65 | } 66 | } 67 | 68 | protected override NefsDataTransformType GetTransformType(uint blockTransformation) 69 | { 70 | return blockTransformation switch 71 | { 72 | 0 => NefsDataTransformType.None, 73 | 1 => NefsDataTransformType.Lzss, 74 | 4 => NefsDataTransformType.Aes, 75 | _ => (NefsDataTransformType)(-1) 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /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 | internal override NefsItem BuildItem(uint entryIndex, NefsItemList dataSourceList) 13 | { 14 | var id = new NefsItemId(entryIndex); 15 | var entry = Header.EntryTable.Entries[id.Index]; 16 | var sharedEntryInfo = Header.SharedEntryInfoTable.Entries[Convert.ToInt32(entry.SharedInfo)]; 17 | return BuildItem(id, entry, sharedEntryInfo, dataSourceList); 18 | } 19 | 20 | protected override (uint End, uint Transformation) GetBlock(uint blockIndex) 21 | { 22 | var block = Header.BlockTable.Entries[Convert.ToInt32(blockIndex)]; 23 | return (block.End, block.Transformation); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Header/INefsHeader.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Utility; 4 | 5 | namespace VictorBush.Ego.NefsLib.Header; 6 | 7 | /// 8 | /// A NeFS archive header. 9 | /// 10 | public interface INefsHeader 11 | { 12 | /// 13 | /// Gets the header version. 14 | /// 15 | NefsVersion Version { get; } 16 | 17 | /// 18 | /// Gets whether the header is from a little-endian system. 19 | /// 20 | bool IsLittleEndian { get; } 21 | 22 | /// 23 | /// Gets whether the header is encrypted. 24 | /// 25 | bool IsEncrypted { get; } 26 | 27 | /// 28 | /// Gets the AES key. 29 | /// 30 | byte[] AesKey { get; } 31 | 32 | /// 33 | /// Gets the header hash. 34 | /// 35 | Sha256Hash Hash { get; } 36 | 37 | /// 38 | /// Gets the header size in bytes. 39 | /// 40 | uint Size { get; } 41 | 42 | /// 43 | /// Gets the data block size. 44 | /// 45 | uint BlockSize { get; } 46 | 47 | /// 48 | /// Gets the number of entries. 49 | /// 50 | uint NumEntries { get; } 51 | 52 | /// 53 | /// Gets the volumes described by the header. 54 | /// 55 | IReadOnlyList Volumes { get; } 56 | 57 | /// 58 | /// Gets the file name at the given offset. 59 | /// 60 | /// The name offset. 61 | /// The file name. 62 | string GetFileName(uint nameOffset); 63 | } 64 | -------------------------------------------------------------------------------- /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/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 | public static class NefsTocTableExtensions 27 | { 28 | /// 29 | /// The size of the table in bytes. 30 | /// 31 | public static int ByteCount(this INefsTocTable table) 32 | where T : unmanaged, INefsTocData 33 | { 34 | return table.ByteCount; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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/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/Header/NefsHeaderNameTable.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Item; 4 | 5 | namespace VictorBush.Ego.NefsLib.Header; 6 | 7 | /// 8 | /// The name table. 9 | /// 10 | public sealed class NefsHeaderNameTable 11 | { 12 | private readonly SortedDictionary fileNamesByOffset = new(); 13 | 14 | private readonly Dictionary offsetsByFileName = new(); 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | internal NefsHeaderNameTable() 20 | { 21 | // for testing 22 | Init(["game.nefs"]); 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// A unique list of strings. 29 | internal NefsHeaderNameTable(IEnumerable entries) 30 | { 31 | Init(entries); 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | /// The list of items in the archive. 38 | internal NefsHeaderNameTable(NefsItemList items) 39 | { 40 | // Add the archive file name to the list and sort strings alphabetically 41 | var strings = items.EnumerateById().Select(i => i.FileName) 42 | .Append(items.DataFileName) 43 | .Distinct() 44 | .OrderBy(i => i.ToLowerInvariant(), StringComparer.Ordinal); 45 | 46 | Init(strings); 47 | } 48 | 49 | /// 50 | /// Gets the list of file names sorted in correct order. 51 | /// 52 | public IEnumerable FileNames => this.fileNamesByOffset.Values; 53 | 54 | /// 55 | /// The dictionary of strings in the strings table, keyed by offset. The key is the offset to the string relative to 56 | /// the beginning of header part 3. The value is the string from the table. 57 | /// 58 | public IReadOnlyDictionary FileNamesByOffset => this.fileNamesByOffset; 59 | 60 | /// 61 | /// The dictionary of strings in the strings table, keyed by string. The key is the string from the table. The value 62 | /// is the offset to the string relative to the beginning of header part 3. 63 | /// 64 | public IReadOnlyDictionary OffsetsByFileName => this.offsetsByFileName; 65 | 66 | /// 67 | /// The current size of header part 3. 68 | /// 69 | public int Size { get; private set; } 70 | 71 | /// 72 | /// Rebuilds the string table from a list of strings. The strings must be unique. 73 | /// 74 | /// A unique list of strings. 75 | private void Init(IEnumerable strings) 76 | { 77 | var offset = 0; 78 | foreach (var s in strings) 79 | { 80 | this.fileNamesByOffset.Add((uint)offset, s); 81 | this.offsetsByFileName.Add(s, (uint)offset); 82 | 83 | // Increase offset by string length plus a null terminator 84 | offset += s.Length + 1; 85 | } 86 | 87 | // Update header size 88 | Size = offset; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Header/NefsInjectHeader.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.DataTypes; 4 | 5 | namespace VictorBush.Ego.NefsLib.Header; 6 | 7 | public sealed class NefsInjectHeader 8 | { 9 | public const int ExpectedMagicNumber = 4484; 10 | public const int Size = 0x20; 11 | 12 | public NefsInjectHeader() 13 | { 14 | } 15 | 16 | public NefsInjectHeader(long primaryOffset, int primarySize, long secondaryOffset, int secondarySize) 17 | { 18 | Data0x00_MagicNum.Value = ExpectedMagicNumber; 19 | Data0x04_Version.Value = 1; 20 | Data0x08_PrimaryOffset.Value = (ulong)primaryOffset; 21 | Data0x10_PrimarySize.Value = (uint)primarySize; 22 | Data0x14_SecondaryOffset.Value = (ulong)secondaryOffset; 23 | Data0x1C_SecondarySize.Value = (uint)secondarySize; 24 | } 25 | 26 | /// 27 | /// The NefsInject header magic number. 28 | /// 29 | public int MagicNumber => (int)Data0x00_MagicNum.Value; 30 | 31 | /// 32 | /// Offset to the primary header section. This section contains header intro/toc, parts 1-5 and 8. 33 | /// 34 | public long PrimaryOffset => (long)Data0x08_PrimaryOffset.Value; 35 | 36 | /// 37 | /// Size of the primary header section. 38 | /// 39 | public int PrimarySize => (int)Data0x10_PrimarySize.Value; 40 | 41 | /// 42 | /// Offset to the secondary header section. This section contains header parts 6 and 7. 43 | /// 44 | public long SecondaryOffset => (long)Data0x14_SecondaryOffset.Value; 45 | 46 | /// 47 | /// Size of the secondary header section. 48 | /// 49 | public int SecondarySize => (int)Data0x1C_SecondarySize.Value; 50 | 51 | /// 52 | /// NefsInject header version. 53 | /// 54 | public int Version => (int)Data0x04_Version.Value; 55 | 56 | [FileData] 57 | private UInt32Type Data0x00_MagicNum { get; } = new UInt32Type(0x00); 58 | 59 | [FileData] 60 | private UInt32Type Data0x04_Version { get; } = new UInt32Type(0x04); 61 | 62 | [FileData] 63 | private UInt64Type Data0x08_PrimaryOffset { get; } = new UInt64Type(0x08); 64 | 65 | [FileData] 66 | private UInt32Type Data0x10_PrimarySize { get; } = new UInt32Type(0x10); 67 | 68 | [FileData] 69 | private UInt64Type Data0x14_SecondaryOffset { get; } = new UInt64Type(0x14); 70 | 71 | [FileData] 72 | private UInt32Type Data0x1C_SecondarySize { get; } = new UInt32Type(0x1C); 73 | } 74 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Header/Version150/NefsHeader150.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.Version150; 7 | 8 | /// 9 | public sealed class NefsHeader150 : INefsHeader 10 | { 11 | public NefsWriterSettings WriterSettings { get; } 12 | public NefsTocHeader150 Intro { get; } 13 | public NefsHeaderEntryTable150 EntryTable { get; } 14 | public NefsHeaderSharedEntryInfoTable150 SharedEntryInfoTable { get; } 15 | public NefsHeaderNameTable NameTable { get; } 16 | public NefsHeaderBlockTable150 BlockTable { get; } 17 | public NefsHeaderVolumeInfoTable150 VolumeInfoTable { 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 => Intro.AesKey.GetAesKey(); 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 NumEntries => Intro.NumEntries; 42 | 43 | /// 44 | public IReadOnlyList Volumes { get; } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | public NefsHeader150( 50 | NefsWriterSettings writerSettings, 51 | NefsTocHeader150 header, 52 | NefsHeaderEntryTable150 entryTable, 53 | NefsHeaderSharedEntryInfoTable150 sharedEntryInfoTable, 54 | NefsHeaderNameTable nameTable, 55 | NefsHeaderBlockTable150 blockTable, 56 | NefsHeaderVolumeInfoTable150 volumeInfoTable) 57 | { 58 | WriterSettings = writerSettings; 59 | Intro = header; 60 | EntryTable = entryTable ?? throw new ArgumentNullException(nameof(entryTable)); 61 | SharedEntryInfoTable = sharedEntryInfoTable ?? throw new ArgumentNullException(nameof(sharedEntryInfoTable)); 62 | NameTable = nameTable ?? throw new ArgumentNullException(nameof(nameTable)); 63 | BlockTable = blockTable ?? throw new ArgumentNullException(nameof(blockTable)); 64 | VolumeInfoTable = volumeInfoTable ?? throw new ArgumentNullException(nameof(volumeInfoTable)); 65 | 66 | Volumes = VolumeInfoTable.Entries.Select(x => new VolumeInfo 67 | { 68 | Size = x.Size, 69 | Name = NameTable.FileNamesByOffset[x.NameOffset], 70 | DataOffset = x.DataOffset 71 | }).ToArray(); 72 | } 73 | 74 | /// 75 | public string GetFileName(uint nameOffset) 76 | { 77 | return NameTable.FileNamesByOffset[nameOffset]; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Header/Version150/NefsHeader151.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.Version150; 7 | 8 | /// 9 | public sealed class NefsHeader151 : INefsHeader 10 | { 11 | public NefsWriterSettings WriterSettings { get; } 12 | public NefsTocHeader151 Intro { get; } 13 | public NefsHeaderEntryTable150 EntryTable { get; } 14 | public NefsHeaderSharedEntryInfoTable150 SharedEntryInfoTable { get; } 15 | public NefsHeaderNameTable NameTable { get; } 16 | public NefsHeaderBlockTable151 BlockTable { get; } 17 | public NefsHeaderVolumeInfoTable150 VolumeInfoTable { 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 => Intro.AesKey.GetAesKey(); 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 NumEntries => Intro.NumEntries; 42 | 43 | /// 44 | public IReadOnlyList Volumes { get; } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// Writer settings. 50 | /// Header intro. 51 | /// Header part 1. 52 | /// Header part 2. 53 | /// Header part 3. 54 | /// Header part 4. 55 | /// Header part 5. 56 | public NefsHeader151( 57 | NefsWriterSettings writerSettings, 58 | NefsTocHeader151 header, 59 | NefsHeaderEntryTable150 entryTable, 60 | NefsHeaderSharedEntryInfoTable150 sharedEntryInfoTable, 61 | NefsHeaderNameTable nameTable, 62 | NefsHeaderBlockTable151 blockTable, 63 | NefsHeaderVolumeInfoTable150 volumeInfoTable) 64 | { 65 | WriterSettings = writerSettings; 66 | Intro = header; 67 | EntryTable = entryTable ?? throw new ArgumentNullException(nameof(entryTable)); 68 | SharedEntryInfoTable = sharedEntryInfoTable ?? throw new ArgumentNullException(nameof(sharedEntryInfoTable)); 69 | NameTable = nameTable ?? throw new ArgumentNullException(nameof(nameTable)); 70 | BlockTable = blockTable ?? throw new ArgumentNullException(nameof(blockTable)); 71 | VolumeInfoTable = volumeInfoTable ?? throw new ArgumentNullException(nameof(volumeInfoTable)); 72 | 73 | Volumes = VolumeInfoTable.Entries.Select(x => new VolumeInfo 74 | { 75 | Size = x.Size, 76 | Name = NameTable.FileNamesByOffset[x.NameOffset], 77 | DataOffset = x.DataOffset 78 | }).ToArray(); 79 | } 80 | 81 | /// 82 | public string GetFileName(uint nameOffset) 83 | { 84 | return NameTable.FileNamesByOffset[nameOffset]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Header/Version150/NefsHeaderBlockTable150.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 NefsHeaderBlockTable150 : 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 NefsHeaderBlockTable150(IReadOnlyList entries) 18 | { 19 | Entries = entries; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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/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/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/Header/Version150/NefsTocBlock150.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 NefsTocBlock150 : INefsTocData 9 | { 10 | public static int ByteCount { get; } = Unsafe.SizeOf(); 11 | 12 | public uint End; 13 | public uint Transformation; 14 | 15 | public void ReverseEndianness() 16 | { 17 | this.End = BinaryPrimitives.ReverseEndianness(this.End); 18 | this.Transformation = BinaryPrimitives.ReverseEndianness(this.Transformation); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/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/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.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/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; 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 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 | // Leave Unknown4 alone for now. Not sure if actually part of header 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.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/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/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/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.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.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.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/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/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/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/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/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.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/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/Version200/NefsTocHeaderB200.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 NefsTocHeaderB200 : INefsTocData 9 | { 10 | public static int ByteCount { get; } = Unsafe.SizeOf(); 11 | 12 | public ushort NumVolumes; 13 | public ushort HashBlockSizeLo; 14 | public uint EntryTableStart; 15 | public uint WritableEntryTableStart; 16 | public uint SharedEntryInfoTableStart; 17 | public uint WritableSharedEntryInfoTableStart; 18 | public uint NameTableStart; 19 | public uint BlockTableStart; 20 | public uint VolumeInfoTableStart; 21 | public uint HashDigestTableStart; 22 | public uint RandomPadding1; 23 | public uint RandomPadding2; 24 | public uint RandomPadding3; 25 | public uint RandomPadding4; 26 | public uint RandomPadding5; 27 | public uint RandomPadding6; 28 | public uint RandomPadding7; 29 | public uint RandomPadding8; 30 | public uint RandomPadding9; 31 | public uint RandomPadding10; 32 | public uint RandomPadding11; 33 | public uint RandomPadding12; 34 | public uint RandomPadding13; 35 | public uint RandomPadding14; 36 | public uint RandomPadding15; 37 | public uint RandomPadding16; 38 | public uint RandomPadding17; 39 | public uint RandomPadding18; 40 | public uint RandomPadding19; 41 | public uint RandomPadding20; 42 | public uint RandomPadding21; 43 | public uint RandomPadding22; 44 | public uint RandomPadding23; 45 | 46 | public uint HashBlockSize 47 | { 48 | get => (uint)this.HashBlockSizeLo << 15; 49 | set => this.HashBlockSizeLo = Convert.ToUInt16(value >>> 15); 50 | } 51 | 52 | public unsafe byte[] RandomPadding 53 | { 54 | get 55 | { 56 | var buffer = new ReadOnlySpan(Unsafe.AsPointer(ref this), ByteCount); 57 | return buffer[36..ByteCount].ToArray(); 58 | } 59 | set 60 | { 61 | var buffer = new Span(Unsafe.AsPointer(ref this), ByteCount); 62 | value.CopyTo(buffer[36..ByteCount]); 63 | } 64 | } 65 | 66 | public void ReverseEndianness() 67 | { 68 | this.NumVolumes = BinaryPrimitives.ReverseEndianness(this.NumVolumes); 69 | this.HashBlockSizeLo = BinaryPrimitives.ReverseEndianness(this.HashBlockSizeLo); 70 | this.EntryTableStart = BinaryPrimitives.ReverseEndianness(this.EntryTableStart); 71 | this.WritableEntryTableStart = BinaryPrimitives.ReverseEndianness(this.WritableEntryTableStart); 72 | this.SharedEntryInfoTableStart = BinaryPrimitives.ReverseEndianness(this.SharedEntryInfoTableStart); 73 | this.WritableSharedEntryInfoTableStart = BinaryPrimitives.ReverseEndianness(this.WritableSharedEntryInfoTableStart); 74 | this.NameTableStart = BinaryPrimitives.ReverseEndianness(this.NameTableStart); 75 | this.BlockTableStart = BinaryPrimitives.ReverseEndianness(this.BlockTableStart); 76 | this.VolumeInfoTableStart = BinaryPrimitives.ReverseEndianness(this.VolumeInfoTableStart); 77 | this.HashDigestTableStart = BinaryPrimitives.ReverseEndianness(this.HashDigestTableStart); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /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.NefsLib/IO/EndianBinaryReader.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 EndianBinaryReader(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 EndianBinaryReader(Stream stream) : this(stream, BitConverter.IsLittleEndian) 20 | { 21 | } 22 | 23 | public EndianBinaryReader(Stream stream, bool littleEndian) : this(stream, littleEndian, 24 | new ResizableBuffer(ArrayPool.Shared, 16)) 25 | { 26 | } 27 | 28 | public async ValueTask ReadUInt32Async(CancellationToken cancellationToken = default) 29 | { 30 | await BaseStream.ReadExactlyAsync(this.buffer.Memory[..4], cancellationToken).ConfigureAwait(false); 31 | return IsLittleEndian 32 | ? BinaryPrimitives.ReadUInt32LittleEndian(this.buffer.Memory.Span) 33 | : BinaryPrimitives.ReadUInt32BigEndian(this.buffer.Memory.Span); 34 | } 35 | 36 | public async ValueTask ReadTocDataAsync(CancellationToken cancellationToken = default) 37 | where T : unmanaged, INefsTocData 38 | { 39 | var size = T.ByteCount; 40 | this.buffer.EnsureLength(size); 41 | var buff = this.buffer.Memory[..size]; 42 | 43 | await BaseStream.ReadExactlyAsync(buff, cancellationToken).ConfigureAwait(false); 44 | var data = Unsafe.As(ref MemoryMarshal.GetReference(this.buffer.Memory.Span)); 45 | if (IsLittleEndian != BitConverter.IsLittleEndian) 46 | { 47 | data.ReverseEndianness(); 48 | } 49 | 50 | return data; 51 | } 52 | 53 | public void Dispose() 54 | { 55 | this.buffer.Dispose(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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/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/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 | /// 23 | /// Writes the archive in NefsInject format. Archive header metadata is stored in a separate file (a NefsInject 24 | /// file) from the archive item data. 25 | /// 26 | /// The path to write the item data file to. 27 | /// The path to write the NefsInject file to. 28 | /// The archive to write. 29 | /// Progress info. 30 | /// An updated archive object containing updated metadata. 31 | Task<(NefsArchive Archive, NefsArchiveSource Source)> WriteNefsInjectArchiveAsync(string dataFilePath, string nefsInjectFilePath, NefsArchive nefs, NefsProgress p); 32 | } 33 | -------------------------------------------------------------------------------- /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/IO/NefsWriterStrategy160.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Header.Version160; 4 | using VictorBush.Ego.NefsLib.Progress; 5 | 6 | namespace VictorBush.Ego.NefsLib.IO; 7 | 8 | internal class NefsWriterStrategy160 : NefsWriterStrategy160Base 9 | { 10 | /// 11 | protected override Task WriteHeaderAsync(EndianBinaryWriter writer, NefsHeader160 header, long primaryOffset, NefsProgress p) 12 | { 13 | throw new NotImplementedException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/IO/NefsWriterStrategy160Base.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Header; 4 | 5 | namespace VictorBush.Ego.NefsLib.IO; 6 | 7 | internal abstract class NefsWriterStrategy160Base : NefsWriterStrategy 8 | where T : INefsHeader 9 | { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /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/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/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.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/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/Source/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.NefsLib/Source/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 | /// Similar to a headless archive, but the header sections are stored in the NefsInject format. 44 | /// 45 | /// Path of the data file. 46 | /// Path of the NefsInject file. 47 | public static NefsInjectSource NefsInject(string dataFilePath, string nefsInjectFilePath) 48 | { 49 | return new NefsInjectSource(dataFilePath, nefsInjectFilePath); 50 | } 51 | 52 | /// 53 | /// Header and file data are both stored in the same file. 54 | /// 55 | /// Path of the archive file. 56 | public static StandardSource Standard(string filePath) 57 | { 58 | return new StandardSource(filePath); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/ArchiveSource/NefsInjectSource.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | namespace VictorBush.Ego.NefsLib.ArchiveSource; 4 | 5 | /// 6 | /// Similar to a headless archive, but the header sections are stored in the NefsInject format. 7 | /// 8 | public sealed class NefsInjectSource : NefsArchiveSource 9 | { 10 | internal NefsInjectSource(string dataFilePath, string nefsInjectFilePath) 11 | : base(dataFilePath) 12 | { 13 | NefsInjectFilePath = nefsInjectFilePath ?? throw new ArgumentNullException(nameof(nefsInjectFilePath)); 14 | } 15 | 16 | /// 17 | /// Path to the file containing the file data (i.e., game.dat, game.bin, etc.). 18 | /// 19 | public string DataFilePath => FilePath; 20 | 21 | /// 22 | /// Path to the NefsInject file that contains the archive header and related offsets. 23 | /// 24 | public string NefsInjectFilePath { get; } 25 | } 26 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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/Source/DataSource/INefsDataSource.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Item; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataSource; 6 | 7 | /// 8 | /// Defines the source of file data for an item in an archive. 9 | /// 10 | public interface INefsDataSource 11 | { 12 | /// 13 | /// The path of the file that contains the item's data. 14 | /// 15 | string FilePath { get; } 16 | 17 | /// 18 | /// A value indicating whether the data in this data source has had any applicable transformations applied. When 19 | /// replacing a file in an archive, the replacement data source will most likely set this to false. When the archive 20 | /// is saved, the transformation is applied. 21 | /// 22 | bool IsTransformed { get; } 23 | 24 | /// 25 | /// The offset in the source file where the data begins. 26 | /// 27 | long Offset { get; } 28 | 29 | /// 30 | /// The size information about the source data. 31 | /// 32 | NefsItemSize Size { get; } 33 | } 34 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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 | /// Creats a list of chunks given a list of cumulative chunk sizes. 40 | /// 41 | /// List of cumulative chunk sizes. 42 | /// The transform applied to all chunks. 43 | /// A list of chunks. 44 | public static List CreateChunkList( 45 | IReadOnlyList cumulativeSizes, 46 | NefsDataTransform transform) 47 | { 48 | var chunks = new List(); 49 | 50 | for (var i = 0; i < cumulativeSizes.Count; ++i) 51 | { 52 | var size = cumulativeSizes[i]; 53 | 54 | // Get individual size by subtracting previous cumulative size 55 | if (i > 0) 56 | { 57 | size -= cumulativeSizes[i - 1]; 58 | } 59 | 60 | var chunk = new NefsDataChunk(size, cumulativeSizes[i], transform); 61 | chunks.Add(chunk); 62 | } 63 | 64 | return chunks; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataSource/NefsDataTransform.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | namespace VictorBush.Ego.NefsLib.DataSource; 4 | 5 | /// 6 | /// Describes a transformation that is applied to a file before being put in the archive.. 7 | /// 8 | public class NefsDataTransform 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The chunk size to use. 14 | /// Whether to compress with zlib. 15 | /// The AES-256 key for encryption. Use null if not encrypted. 16 | public NefsDataTransform(uint chunkSize, bool isZlib, byte[]? aesKey = null) 17 | { 18 | ChunkSize = chunkSize; 19 | IsZlibCompressed = isZlib; 20 | Aes256Key = aesKey; 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. This transform does nothing (file is 25 | /// simply placed in the archive as-is). 26 | /// 27 | /// The file size. 28 | public NefsDataTransform(uint fileSize) 29 | { 30 | ChunkSize = fileSize; 31 | IsZlibCompressed = false; 32 | Aes256Key = null; 33 | } 34 | 35 | /// 36 | /// The AES 256 key. If the chunk is not encrypted, this will be null. 37 | /// 38 | public byte[]? Aes256Key { get; } 39 | 40 | /// 41 | /// The size of chunks to split the input file up into before transforming each chunk. 42 | /// 43 | public uint ChunkSize { get; } 44 | 45 | public bool IsLzssCompressed { get; init; } 46 | 47 | /// 48 | /// Whether data chunks are AES encrypted. 49 | /// 50 | public bool IsAesEncrypted => Aes256Key != null; 51 | 52 | /// 53 | /// Whether data chunks are zlib compressed. 54 | /// 55 | public bool IsZlibCompressed { get; } 56 | } 57 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataSource/NefsEmptyDataSource.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Item; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataSource; 6 | 7 | /// 8 | /// Data source that has no data. 9 | /// 10 | public class NefsEmptyDataSource : INefsDataSource 11 | { 12 | /// 13 | public string FilePath => ""; 14 | 15 | /// 16 | public bool IsTransformed => true; 17 | 18 | /// 19 | public long Offset => 0; 20 | 21 | /// 22 | public NefsItemSize Size => new(0); 23 | } 24 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataSource/NefsFileDataSource.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Item; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataSource; 6 | 7 | /// 8 | /// Defines an item data source from a file on disk. 9 | /// 10 | public class NefsFileDataSource : INefsDataSource 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The path of the file that contain's the data. 16 | /// The offset in the source file where the data begins. 17 | /// Size information about the item's data. 18 | /// 19 | /// Whether the data in this data source has already been transformed (encrypted, compressed, etc). 20 | /// 21 | public NefsFileDataSource(string filePath, long offset, NefsItemSize size, bool isTransformed) 22 | { 23 | FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath)); 24 | Offset = offset; 25 | Size = size; 26 | IsTransformed = isTransformed; 27 | } 28 | 29 | /// 30 | public string FilePath { get; } 31 | 32 | /// 33 | public bool IsTransformed { get; } 34 | 35 | /// 36 | public long Offset { get; } 37 | 38 | /// 39 | public NefsItemSize Size { get; } 40 | } 41 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataSource/NefsItemListDataSource.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Item; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataSource; 6 | 7 | /// 8 | /// Defines an item data source from an existing archive. The item list defines the path to the file that contains the 9 | /// item data content. 10 | /// 11 | /// 12 | /// When saving an archive, the majority of items are typically unchanged. In this case, the item data is copied from 13 | /// the existing archive into the new archive. The allows an item to have access to 14 | /// the file path that contains the item's data without having to have a copy of the file path string in every item. 15 | /// 16 | public class NefsItemListDataSource : INefsDataSource 17 | { 18 | /// 19 | /// Item list that defines the source file path for item data. 20 | /// 21 | private readonly NefsItemList items; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The items list that specifies the file the item's data is stored in. 27 | /// The offset in the source file where the data begins. 28 | /// The size of the item's data in bytes. 29 | public NefsItemListDataSource( 30 | NefsItemList items, 31 | long offset, 32 | NefsItemSize size) 33 | { 34 | this.items = items ?? throw new ArgumentNullException(nameof(items)); 35 | Offset = offset; 36 | Size = size; 37 | } 38 | 39 | /// 40 | public string FilePath => this.items.DataFilePath; 41 | 42 | /// 43 | /// For an item list data source, the data is already transformed. 44 | public bool IsTransformed => true; 45 | 46 | /// 47 | public long Offset { get; } 48 | 49 | /// 50 | public NefsItemSize Size { get; } 51 | } 52 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataTypes/ByteArrayType.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Progress; 4 | using VictorBush.Ego.NefsLib.Utility; 5 | 6 | namespace VictorBush.Ego.NefsLib.DataTypes; 7 | 8 | /// 9 | /// An array of bytes. 10 | /// 11 | public sealed class ByteArrayType : DataType 12 | { 13 | private byte[] value; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// See . 19 | /// The size of the array in bytes. 20 | public ByteArrayType(int offset, int size) 21 | : base(offset) 22 | { 23 | if (size == 0) 24 | { 25 | throw new ArgumentOutOfRangeException("ByteArrayType must have size greater than 0 bytes."); 26 | } 27 | 28 | Size = size; 29 | this.value = new byte[size]; 30 | } 31 | 32 | /// 33 | /// The size of the array in bytes. 34 | /// 35 | public override int Size { get; } 36 | 37 | /// 38 | /// The current data value. 39 | /// 40 | public byte[] Value 41 | { 42 | get => this.value; 43 | set 44 | { 45 | if (value.Length != Size) 46 | { 47 | throw new ArgumentException($"Byte array had length of {value.Length}, but expected {Size}. Value of {nameof(ByteArrayType)} must not change length."); 48 | } 49 | 50 | this.value = value; 51 | } 52 | } 53 | 54 | /// 55 | public override byte[] GetBytes() => Value; 56 | 57 | /// 58 | /// Gets a 32-bit unsigned integer from the array. 59 | /// 60 | /// The offset from the beginning of the array to get the integer from. 61 | public uint GetUInt32(long offset) 62 | { 63 | if (offset >= Size) 64 | { 65 | throw new ArgumentOutOfRangeException("Offset outside of byte array."); 66 | } 67 | 68 | if (Value.Length - (int)offset < 4) 69 | { 70 | throw new ArgumentOutOfRangeException("Offset must be at least 4 bytes from the end of the array."); 71 | } 72 | 73 | return BitConverter.ToUInt32(Value, (int)offset); 74 | } 75 | 76 | /// 77 | public override async Task ReadAsync(Stream file, long baseOffset, NefsProgress p) 78 | { 79 | Value = await DoReadAsync(file, baseOffset, p); 80 | } 81 | 82 | /// 83 | public override string ToString() => StringHelper.ByteArrayToString(Value); 84 | } 85 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataTypes/UInt16Type.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Progress; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataTypes; 6 | 7 | /// 8 | /// 16-bit unsigned integer. 9 | /// 10 | public class UInt16Type : DataType 11 | { 12 | private const int UInt16TypeSize = 2; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// See . 18 | public UInt16Type(int offset) 19 | : base(offset) 20 | { 21 | } 22 | 23 | /// 24 | public override int Size => UInt16TypeSize; 25 | 26 | /// 27 | /// The current data value. 28 | /// 29 | public ushort Value { get; set; } 30 | 31 | /// 32 | public override byte[] GetBytes() => BitConverter.GetBytes(Value); 33 | 34 | /// 35 | public override async Task ReadAsync(Stream file, long baseOffset, NefsProgress p) 36 | { 37 | var temp = await DoReadAsync(file, baseOffset, p); 38 | Value = BitConverter.ToUInt16(temp, 0); 39 | } 40 | 41 | /// 42 | public override string ToString() 43 | { 44 | /* Return value in hex */ 45 | return "0x" + Value.ToString("X"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataTypes/UInt32Type.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Progress; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataTypes; 6 | 7 | /// 8 | /// 32-bit unsigned integer. 9 | /// 10 | public class UInt32Type : DataType 11 | { 12 | private const int UInt32TypeSize = 4; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// See . 18 | public UInt32Type(int offset) 19 | : base(offset) 20 | { 21 | } 22 | 23 | /// 24 | public override int Size => UInt32TypeSize; 25 | 26 | /// 27 | /// The current data value. 28 | /// 29 | public uint Value { get; set; } 30 | 31 | /// 32 | public override byte[] GetBytes() => BitConverter.GetBytes(Value); 33 | 34 | /// 35 | public override async Task ReadAsync(Stream file, long baseOffset, NefsProgress p) 36 | { 37 | var temp = await DoReadAsync(file, baseOffset, p); 38 | Value = BitConverter.ToUInt32(temp, 0); 39 | } 40 | 41 | /// 42 | public override string ToString() 43 | { 44 | /* Return value in hex */ 45 | return "0x" + Value.ToString("X"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataTypes/UInt64Type.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Progress; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataTypes; 6 | 7 | /// 8 | /// 64-bit unsigned integer. 9 | /// 10 | public class UInt64Type : DataType 11 | { 12 | private const int UInt64Size = 8; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// See . 18 | public UInt64Type(int offset) 19 | : base(offset) 20 | { 21 | } 22 | 23 | /// 24 | public override int Size => UInt64Size; 25 | 26 | /// 27 | /// The current data value. 28 | /// 29 | public ulong Value { get; set; } 30 | 31 | /// 32 | public override byte[] GetBytes() => BitConverter.GetBytes(Value); 33 | 34 | /// 35 | public override async Task ReadAsync(Stream file, long baseOffset, NefsProgress p) 36 | { 37 | var temp = await DoReadAsync(file, baseOffset, p); 38 | Value = BitConverter.ToUInt64(temp, 0); 39 | } 40 | 41 | /// 42 | public override string ToString() 43 | { 44 | return "0x" + Value.ToString("X"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/DataTypes/UInt8Type.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using VictorBush.Ego.NefsLib.Progress; 4 | 5 | namespace VictorBush.Ego.NefsLib.DataTypes; 6 | 7 | /// 8 | /// 8-bit unsigned integer. 9 | /// 10 | public class UInt8Type : DataType 11 | { 12 | private const int UInt8TypeSize = 1; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// See . 18 | public UInt8Type(int offset) 19 | : base(offset) 20 | { 21 | } 22 | 23 | /// 24 | public override int Size => UInt8TypeSize; 25 | 26 | /// 27 | /// The current data value. 28 | /// 29 | public byte Value { get; set; } 30 | 31 | /// 32 | public override byte[] GetBytes() => [Value]; 33 | 34 | /// 35 | public override async Task ReadAsync(Stream file, long baseOffset, NefsProgress p) 36 | { 37 | var temp = await DoReadAsync(file, baseOffset, p); 38 | Value = temp[0]; 39 | } 40 | 41 | /// 42 | public override string ToString() 43 | { 44 | /* Return value in hex */ 45 | return "0x" + Value.ToString("X"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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 | /// Indicates if item's data has transformations applied. 18 | /// Version 2.0 - Indicates if item's data is zlib compressed. 19 | /// Version 2.0 - Indicates if item's data is AES encrypted. 20 | /// Item volume. 21 | public NefsItemAttributes( 22 | bool isCacheable = false, 23 | bool isDirectory = false, 24 | bool isDuplicated = false, 25 | bool isPatched = false, 26 | bool v16IsTransformed = false, 27 | bool v20IsZlib = false, 28 | bool v20IsAes = false, 29 | ushort part6Volume = 0) 30 | { 31 | IsCacheable = isCacheable; 32 | IsDirectory = isDirectory; 33 | IsDuplicated = isDuplicated; 34 | IsPatched = isPatched; 35 | V16IsTransformed = v16IsTransformed; 36 | V20IsZlib = v20IsZlib; 37 | V20IsAes = v20IsAes; 38 | Part6Volume = part6Volume; 39 | } 40 | 41 | /// 42 | /// A flag indicating whether this item is cacheable (presumably by the game engine). 43 | /// 44 | public bool IsCacheable { get; } 45 | 46 | /// 47 | /// A flag indicating whether this item is a directory. 48 | /// 49 | public bool IsDirectory { get; } 50 | 51 | /// 52 | /// A flag indicating whether this item is duplicated. 53 | /// 54 | public bool IsDuplicated { get; } 55 | 56 | /// 57 | /// Whether this item is the last sibling within its parent. 58 | /// 59 | public bool IsLastSibling { get; init; } 60 | 61 | /// 62 | /// A flag indicating whether this item is patched (meaning unknown). 63 | /// 64 | public bool IsPatched { get; } 65 | 66 | /// 67 | /// Meaning unknown (from part 6). 68 | /// 69 | public ushort Part6Volume { get; } 70 | 71 | /// 72 | /// Version 1.6 - A flag indicating whether the item's data has transforms applied (compressed, encrypted, etc). 73 | /// 74 | public bool V16IsTransformed { get; } 75 | 76 | /// 77 | /// Version 2.0 - indicates whether the item's data is AES encrypted. 78 | /// 79 | public bool V20IsAes { get; } 80 | 81 | /// 82 | /// Version 2.0 - indicates whether the item's data is zlib compressed. 83 | /// 84 | public bool V20IsZlib { get; } 85 | } 86 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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.NefsLib/Source/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/Source/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/Source/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 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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.NefsLib/Source/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.NefsLib/Source/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/Source/Utility/FileSystemExtensions.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using System.IO.Abstractions; 4 | 5 | namespace VictorBush.Ego.NefsLib.Utility; 6 | 7 | /// 8 | /// Extensions for . 9 | /// 10 | public static class FileSystemExtensions 11 | { 12 | /// 13 | /// Deletes the specified directory if it exists, then creates it. 14 | /// 15 | /// The file system. 16 | /// The directory path. 17 | public static void ResetOrCreateDirectory(this IFileSystem fs, string directoryPath) 18 | { 19 | if (fs.Directory.Exists(directoryPath)) 20 | { 21 | fs.Directory.Delete(directoryPath, true); 22 | } 23 | 24 | fs.Directory.CreateDirectory(directoryPath); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/Source/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/Source/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/Source/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/Source/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.NefsLib/Source/Utility/StringHelper.cs: -------------------------------------------------------------------------------- 1 | // See LICENSE.txt for license information. 2 | 3 | using System.Text; 4 | 5 | namespace VictorBush.Ego.NefsLib.Utility; 6 | 7 | /// 8 | /// Some string utilities. 9 | /// 10 | public static class StringHelper 11 | { 12 | /// 13 | /// Prints a byte array to a string in hex format. 14 | /// 15 | /// The bytes to print. 16 | /// The string. 17 | public static string ByteArrayToString(ReadOnlySpan bytes) 18 | { 19 | if (bytes == null) 20 | { 21 | return ""; 22 | } 23 | 24 | var sb = new StringBuilder(); 25 | foreach (var b in bytes) 26 | { 27 | sb.Append("0x" + b.ToString("X") + ", "); 28 | } 29 | 30 | return sb.ToString(); 31 | } 32 | 33 | /// 34 | /// Takes a string representation of a hexademical value and converts it to a byte array. 35 | /// 36 | /// The hex string. 37 | /// The byte array. 38 | public static byte[] FromHexString(string hex) 39 | { 40 | byte[] raw = new byte[hex.Length / 2]; 41 | for (int i = 0; i < raw.Length; i++) 42 | { 43 | raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); 44 | } 45 | 46 | return raw; 47 | } 48 | 49 | /// 50 | /// Takes an integer and converts it to a string representation in hexadecimal format. 51 | /// 52 | /// The value to convert. 53 | /// Whether to prefix with '0x'. 54 | /// The hex string. 55 | public static string ToHexString(this uint value, bool prefix = true) 56 | { 57 | if (prefix) 58 | { 59 | return string.Format("0x{0:X}", value); 60 | } 61 | else 62 | { 63 | return string.Format("{0:X}", value); 64 | } 65 | } 66 | 67 | /// 68 | /// Tries to read a null terminated ASCII string. If no null-terminator is found, an empty string is returned. 69 | /// 70 | /// The input bytes. 71 | /// Offset into the input array. 72 | /// Max number of characters long. 73 | /// The string. 74 | public static string TryReadNullTerminatedAscii(byte[] bytes, int startOffset, int maxSize) 75 | { 76 | // Find the next null terminator 77 | var endOffset = startOffset + maxSize; 78 | var nullOffset = endOffset; 79 | for (var i = startOffset; i < endOffset; ++i) 80 | { 81 | if (bytes[i] == 0) 82 | { 83 | nullOffset = i; 84 | break; 85 | } 86 | } 87 | 88 | if (nullOffset == endOffset) 89 | { 90 | // No null terminator found, assume end of part 3. There can be a few garbage bytes at the end of this part. 91 | return ""; 92 | } 93 | 94 | // Get the string 95 | return Encoding.ASCII.GetString(bytes, startOffset, nullOffset - startOffset); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /VictorBush.Ego.NefsLib/VictorBush.Ego.NefsLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | Library 5 | enable 6 | true 7 | 8 | 9 | 10 | bin\Debug\VictorBush.Ego.NefsLib.xml 11 | 12 | 13 | 14 | <_Parameter1>$(MSBuildProjectName).Tests 15 | 16 | 17 | <_Parameter1>VictorBush.Ego.NefsEdit.Tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------