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