Compare commits

...

43 Commits
3.0.1 ... 3.3.0

Author SHA1 Message Date
Matt Nadareski
de17b0c59c Bump version 2026-11-11 17:41:51 +01:00
gmipf
1663345195 Add a UDF reader and wrapper (#100)
* Add a UDF reader

Reads the Volume Recognition Sequence, the Anchor Volume Descriptor
Pointer (sector 256, then the end of the volume), the Main or Reserve
Volume Descriptor Sequence, the File Set Descriptor and the directory
hierarchy. Type 1 and Metadata Partitions are mapped, Virtual and
Sparable partitions are not.

File Entries, Allocation Extent Descriptors and directories are read in
ascending sector order with wrap-around, adjacent sectors merged into
one request. FindFileEntry follows one path and reads only the
directories on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a UDF wrapper

Prints the volume and file set structures and the directory hierarchy,
and extracts the files. CreateDiscImageWrapper tries UDF after ISO 9660
for a stream at offset 0, so discs with both file systems still open as
ISO 9660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Treat the stream position as the start of the UDF volume

The UDF reader addresses logical sectors from the start of the stream,
so the wrapper hands it a view that starts at the current position and
keeps that offset, like the other wrappers. CreateDiscImageWrapper no
longer skips UDF for a stream that is not at offset 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review comments on the UDF code

- Move the descriptor tag check to UDFExtensions, with tests
- Parse Volume Structure Descriptors in their own method
- Use the existing extensions for numbers, the partition type identifier
  and printing entity identifiers
- Use Array.Exists in IsSafeName
- Make Extract non-virtual and remove the printing test
- Use collection expressions and string interpolation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parse File Identifier Descriptors in their own method

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a TODO to use the CRC from Hashing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:18:23 +01:00
Matt Nadareski
5c3184baa7 Add .NET 11 support 2026-11-11 12:02:16 +01:00
gmipf
9e39a0cf16 Close the cabinet files opened for a cabinet set (#105)
* Close the cabinet files opened for a cabinet set

MicrosoftCabinet.Extract opened the cabinet file again for the set,
together with the previous and next cabinets, and never closed any of
them, so they stayed open until their streams were finalized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the original comment on the previous cabinet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:19:16 -04:00
gmipf
36a67139d9 Add the UDF models (#108)
The ECMA-167 and UDF structures that the UDF reader fills in, split out
of #100. The only change is that the notes on Volume moved from the
summary into the remarks.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 13:31:46 -04:00
gmipf
33caf06bc1 Read ISO 9660 directories in volume order and add a path lookup (#99)
* Read directory extents in volume order

ParseDirectoryDescriptors now keeps one worklist of directory extents for
all volume descriptors, sorted by offset, and always reads the next
extent at or after the last one, wrapping around at the end. Each extent
is read once. The record loop moves to a shared helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a path lookup for ISO 9660 directory records

FindDirectoryRecords follows a path from the root directory of one
volume descriptor and reads only the directories on that path. Names
are compared case-insensitively, without the version suffix or a
trailing period. A file recorded in several extents returns all of its
records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 11:00:54 -04:00
Matt Nadareski
f639f7ac24 Strip out MS-CAB extraction test even further
This will make it so that exceptions are surfaced directly into the test log and cancel right away. It effectively does the same thing as capturing the error log and redirecting it, but with many less steps.
2026-10-26 09:52:32 -04:00
gmipf
11f9bf9fd9 Stop writing after the last file of a cabinet folder (#107)
* Stop writing after the last file of a cabinet folder

When the last data block of a folder held the end of two or more files,
WriteData still wrote the rest of the block after EndFile had closed the
last file, which threw an ObjectDisposedException. It now returns there,
as it already does when the first file in a block ends the folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Simplify the console check in the cabinet extraction test

Extract catches the ObjectDisposedException and only writes it to
Console.Error, so the test still reads the console, but without the
try/finally. It now fails with the whole exception as its message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:46:10 -04:00
gmipf
373cf2d752 Replace invalid filename characters in Wise dialog names (#106)
The file of a custom dialog is named after the dialog name from the
script, which is free text. On Windows, a name like "Install under NT?"
is not a valid filename, so writing the file threw an IOException and
stopped the extraction.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:32:34 -04:00
gmipf
dc18951d0b Close the Wise script and the multipart file after extraction (#104)
* Look for the Wise overlay header before opening the file again

ExtractWiseOverlay (and ExtractWise for NE) opened a second stream on
the executable for multi-part Wise installers before checking for a
Wise overlay header at all. For any other executable that stream was
never closed, so the file stayed open until the stream was finalized.
On Windows such a file cannot be deleted, so an extraction folder that
contains it is left behind in TEMP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the Wise script and the multipart file after extraction

ExtractWiseOverlay (and ExtractWise for NE) never closed WiseScript.bin,
which it opens from the output directory, nor the stream with the
installer and its parts, so these files stayed open until their streams
were finalized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the original block that opens the multipart file

Close the multipart stream with a separate using declaration after the
block instead of rewriting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:30:32 -04:00
gmipf
29b9d27c33 Look for the Wise overlay header before opening the file again (#103)
ExtractWiseOverlay (and ExtractWise for NE) opened a second stream on
the executable for multi-part Wise installers before checking for a
Wise overlay header at all. For any other executable that stream was
never closed, so the file stayed open until the stream was finalized.
On Windows such a file cannot be deleted, so an extraction folder that
contains it is left behind in TEMP.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:50:21 -04:00
gmipf
f6ade446c6 Locate the PKZIP file data instead of reading it into the model (#102)
* Locate the PKZIP file data instead of reading it into the model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep only the offset of the PKZIP file data

The length is the compressed size from the local file header or the ZIP64
extended field, which the printer already shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the file data property as a comment for documentation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:04:01 -04:00
gmipf
6238fcbb46 Format refresh and freq with the invariant culture (#101)
The DAT writers and the filter formatted Refresh and Freq with the
current culture, so a German or French system wrote "123,450000".
SabreTools.IO parses these values with the invariant culture.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:49:12 -04:00
gmipf
2f6698edac Parse the big-endian directory extent only when ambiguous (#98)
ParseDirectory parsed the big-endian extent when the extent location
was valid (both values equal) instead of when it was ambiguous, so
every directory was parsed again, once per directory level.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:16:14 -04:00
Matt Nadareski
04e8751f9b Update packages 2026-10-12 09:56:31 -04:00
Matt Nadareski
7262f74f7f Enable versions in publish script 2026-09-21 09:17:22 -04:00
Matt Nadareski
0b6af978ff Check for "null" literal string cases 2026-09-18 11:01:40 -04:00
Matt Nadareski
2cacaac9fd Make size an optional output for CMP and Logiqx 2026-09-18 10:56:30 -04:00
Matt Nadareski
d39d8f9b61 Allow sizeless ROMs if there's a SHA-1 or SHA-256 2026-09-18 10:54:32 -04:00
Matt Nadareski
09fd38e776 Allow sizeless ROMs if there's a SHA-1 or SHA-256 2026-09-18 10:47:36 -04:00
Matt Nadareski
db2a6f63ff Allow sizeless ROMs if there's a SHA-1 or SHA-256 2026-09-18 10:44:56 -04:00
Matt Nadareski
d9dd16fbb9 Fix OpenMSX rom attribute parsing 2026-09-18 10:34:40 -04:00
Matt Nadareski
3c0c31cf10 Use SHA-1 as placeholder name for OpenMSX 2026-09-18 10:27:46 -04:00
Matt Nadareski
d9bf16deef OpenMSX has changed a bit 2026-09-18 10:15:54 -04:00
Matt Nadareski
3798dbcb47 Bump version 2026-08-31 19:15:36 -04:00
Matt Nadareski
2a70a9a20e Update IO to 2.3.0 2026-08-28 13:11:31 -04:00
Matt Nadareski
6f92f85f5d Convert BFPK writing to use GrindCore zlib 2026-08-27 14:01:50 -04:00
Matt Nadareski
b7851601a4 Convert GCZ writing to use GrindCore zlib 2026-08-27 13:59:38 -04:00
Matt Nadareski
cb2a96c413 Convert GCZ to use GrindCore zlib 2026-08-27 13:57:49 -04:00
Matt Nadareski
69c0843a0d Convert GZip to use GrindCore zlib 2026-08-27 13:56:09 -04:00
Matt Nadareski
0171ea9da7 Convert IS-CAB to use GrindCore zlib 2026-08-27 13:53:23 -04:00
Matt Nadareski
e493af25e0 Convert CExe to use GrindCore zlib 2026-08-27 12:37:26 -04:00
Matt Nadareski
62427576e9 Convert SGA to use GrindCore zlib 2026-08-27 12:30:01 -04:00
Matt Nadareski
e80ea00872 Update packages 2026-08-27 09:36:42 -04:00
Matt Nadareski
ce1cd15978 Fix locking issues with item dictionary implementation 2026-08-11 22:04:32 -04:00
HeroponRikiBestest
2a604bd73a Ensure that the data being read is actually large enough to have a debug directory entry parsed from it (#96) 2026-07-30 20:44:49 -04:00
Matt Nadareski
bcc0a5bd1e Add set type RomVault game extension field 2026-07-23 17:03:14 -04:00
Matt Nadareski
32d9e8a09d Add dir RomVault header extension field 2026-07-23 16:45:12 -04:00
Matt Nadareski
d0f5ac322d Add subset RomVault header extension field 2026-07-23 16:15:27 -04:00
Matt Nadareski
05c86e4c24 Add support for BLAKE3 2026-07-23 11:36:54 -04:00
Matt Nadareski
1d76967a08 Ignore bad IS-CAB group names 2026-07-19 21:09:59 -04:00
Matt Nadareski
b9ffd61252 Bump version 2026-07-10 13:29:35 -04:00
Matt Nadareski
b4e1f6b729 Update packages, relax requirements 2026-07-10 11:23:17 -04:00
166 changed files with 7428 additions and 493 deletions

View File

@@ -20,6 +20,7 @@ jobs:
8.0.x 8.0.x
9.0.x 9.0.x
10.0.x 10.0.x
11.0.x
- name: Run tests - name: Run tests
run: dotnet test run: dotnet test

View File

@@ -15,6 +15,7 @@ jobs:
8.0.x 8.0.x
9.0.x 9.0.x
10.0.x 10.0.x
11.0.x
- name: Build - name: Build
run: dotnet build run: dotnet build

4
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/ExtractionTool/bin/Debug/net10.0/ExtractionTool.dll", "program": "${workspaceFolder}/ExtractionTool/bin/Debug/net11.0/ExtractionTool.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
@@ -24,7 +24,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/InfoPrint/bin/Debug/net10.0/InfoPrint.dll", "program": "${workspaceFolder}/InfoPrint/bin/Debug/net11.0/InfoPrint.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
@@ -10,7 +10,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
</PropertyGroup> </PropertyGroup>
<!-- Support All Frameworks --> <!-- Support All Frameworks -->
@@ -20,11 +20,11 @@
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))"> <PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`))"> <PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`)) OR $(TargetFramework.StartsWith(`net11`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))"> <PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<!-- These are needed for dealing with native Windows DLLs --> <!-- These are needed for dealing with native Windows DLLs -->
@@ -48,9 +48,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" /> <PackageReference Include="SabreTools.CommandLine" Version="1.6.0" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.8" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="11.0.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
@@ -10,7 +10,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
</PropertyGroup> </PropertyGroup>
<!-- Support All Frameworks --> <!-- Support All Frameworks -->
@@ -20,11 +20,11 @@
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))"> <PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`))"> <PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`)) OR $(TargetFramework.StartsWith(`net11`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))"> <PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -32,9 +32,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" /> <PackageReference Include="SabreTools.CommandLine" Version="1.6.0" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -361,6 +361,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -380,6 +381,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -406,6 +408,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -424,6 +427,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -449,6 +453,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
var other = new Rom var other = new Rom
{ {
@@ -467,6 +472,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -493,6 +499,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
var other = new Rom var other = new Rom
{ {
@@ -510,6 +517,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -536,6 +544,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -554,6 +563,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -580,6 +590,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -598,6 +609,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -624,6 +636,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -642,6 +655,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -668,6 +682,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -686,6 +701,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -712,6 +728,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -730,6 +747,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -756,6 +774,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -774,6 +793,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -800,6 +820,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -818,6 +839,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -844,6 +866,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -862,6 +885,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -888,6 +912,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -906,6 +931,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -932,6 +958,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -950,6 +977,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -976,6 +1004,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -994,6 +1023,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -1020,6 +1050,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -1038,6 +1069,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -1064,6 +1096,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = string.Empty,
}; };
var other = new Rom var other = new Rom
{ {
@@ -1082,6 +1115,53 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
};
bool actual = self.PartialEquals(other);
Assert.True(actual);
}
[Fact]
public void PartialEquals_Rom_PartialBLAKE3_True()
{
var self = new Rom
{
Name = "name1",
Size = 12345,
CRC16 = string.Empty,
CRC32 = string.Empty,
CRC64 = string.Empty,
MD2 = string.Empty,
MD4 = string.Empty,
MD5 = string.Empty,
RIPEMD128 = string.Empty,
RIPEMD160 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
SHA384 = string.Empty,
SHA512 = string.Empty,
SpamSum = string.Empty,
BLAKE3 = "blake3",
};
var other = new Rom
{
Name = "name2",
Size = 12345,
CRC16 = "crc16",
CRC32 = "crc32",
CRC64 = "crc64",
MD2 = "md2",
MD4 = "md4",
MD5 = "md5",
RIPEMD128 = "ripemd128",
RIPEMD160 = "ripemd160",
SHA1 = "sha1",
SHA256 = "sha256",
SHA384 = "sha384",
SHA512 = "sha512",
SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -1108,6 +1188,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
var other = new Rom var other = new Rom
{ {
@@ -1126,6 +1207,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.PartialEquals(other); bool actual = self.PartialEquals(other);
@@ -1392,6 +1474,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1408,6 +1491,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1432,6 +1516,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1448,6 +1533,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1472,6 +1558,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1488,6 +1575,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1512,6 +1600,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1528,6 +1617,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1552,6 +1642,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1568,6 +1659,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1592,6 +1684,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1608,6 +1701,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1632,6 +1726,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1648,6 +1743,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1672,6 +1768,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1688,6 +1785,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1712,6 +1810,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1728,6 +1827,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1752,6 +1852,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1768,6 +1869,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1792,6 +1894,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1808,6 +1911,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1832,6 +1936,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1848,6 +1953,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1872,6 +1978,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1888,6 +1995,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1912,6 +2020,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = string.Empty,
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1928,6 +2037,49 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
};
bool actual = self.HashMatch(other);
Assert.True(actual);
}
[Fact]
public void HashMatch_Rom_PartialBLAKE3_True()
{
Rom self = new Rom
{
CRC16 = string.Empty,
CRC32 = string.Empty,
CRC64 = string.Empty,
MD2 = string.Empty,
MD4 = string.Empty,
MD5 = string.Empty,
RIPEMD128 = string.Empty,
RIPEMD160 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
SHA384 = string.Empty,
SHA512 = string.Empty,
SpamSum = string.Empty,
BLAKE3 = "blake3",
};
Rom other = new Rom
{
CRC16 = "crc16",
CRC32 = "crc32",
CRC64 = "crc64",
MD2 = "md2",
MD4 = "md4",
MD5 = "md5",
RIPEMD128 = "ripemd128",
RIPEMD160 = "ripemd160",
SHA1 = "sha1",
SHA256 = "sha256",
SHA384 = "sha384",
SHA512 = "sha512",
SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -1952,6 +2104,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
Rom other = new Rom Rom other = new Rom
{ {
@@ -1968,6 +2121,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HashMatch(other); bool actual = self.HashMatch(other);
@@ -2162,6 +2316,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2186,6 +2341,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2210,6 +2366,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2234,6 +2391,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2258,6 +2416,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2282,6 +2441,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2306,6 +2466,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2330,6 +2491,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2354,6 +2516,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2378,6 +2541,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2402,6 +2566,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2426,6 +2591,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = HashType.SHA384.ZeroString, SHA384 = HashType.SHA384.ZeroString,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2450,6 +2616,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = HashType.SHA512.ZeroString, SHA512 = HashType.SHA512.ZeroString,
SpamSum = string.Empty, SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2474,6 +2641,32 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = HashType.SpamSum.ZeroString, SpamSum = HashType.SpamSum.ZeroString,
BLAKE3 = string.Empty,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_Rom_ZeroBLAKE3_True()
{
var self = new Rom
{
CRC16 = string.Empty,
CRC32 = string.Empty,
CRC64 = string.Empty,
MD2 = string.Empty,
MD4 = string.Empty,
MD5 = string.Empty,
RIPEMD128 = string.Empty,
RIPEMD160 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
SHA384 = string.Empty,
SHA512 = string.Empty,
SpamSum = string.Empty,
BLAKE3 = HashType.BLAKE3.ZeroString,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2498,6 +2691,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = HashType.SHA384.ZeroString, SHA384 = HashType.SHA384.ZeroString,
SHA512 = HashType.SHA512.ZeroString, SHA512 = HashType.SHA512.ZeroString,
SpamSum = HashType.SpamSum.ZeroString, SpamSum = HashType.SpamSum.ZeroString,
BLAKE3 = HashType.BLAKE3.ZeroString,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -2583,6 +2777,7 @@ namespace SabreTools.Data.Extensions.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
self.FillMissingHashes(other); self.FillMissingHashes(other);

View File

@@ -1,17 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,274 @@
using SabreTools.Data.Models.UDF;
using Xunit;
namespace SabreTools.Data.Extensions.Test
{
public class UDFExtensionsTests
{
#region DecodeDCharacters
[Fact]
public void DecodeDCharacters_Null_Empty()
{
byte[]? data = null;
Assert.Equal(string.Empty, data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_8Bit_Decoded()
{
byte[] data = [8, 0x41, 0x62, 0xE4];
Assert.Equal("Abä", data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_16Bit_Decoded()
{
byte[] data = [16, 0x00, 0x41, 0x30, 0x42];
Assert.Equal("Aあ", data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_UnknownCompression_Null()
{
byte[] data = [1, 0x41];
Assert.Null(data.DecodeDCharacters());
}
[Fact]
public void DecodeDString_UsesLengthByte()
{
byte[] data = [8, 0x41, 0x42, 0x43, 0x00, 3];
Assert.Equal("AB", data.DecodeDString());
}
[Fact]
public void DecodeDString_ZeroLength_Empty()
{
byte[] data = [8, 0x41, 0x00, 0];
Assert.Equal(string.Empty, data.DecodeDString());
}
#endregion
#region TryGetSector
[Fact]
public void TryGetSector_Type1_StartPlusBlock()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(0, 10), out long sector);
Assert.True(actual);
Assert.Equal(1010, sector);
}
[Fact]
public void TryGetSector_Type1_OutsidePartition_False()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(0, 100), out _);
Assert.False(actual);
}
[Fact]
public void TryGetSector_UnknownPartitionReference_False()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(1, 0), out _);
Assert.False(actual);
}
[Fact]
public void TryGetSector_Metadata_ThroughMetadataFile()
{
// Metadata File at block 5 of partition 0: blocks 0-1 of the metadata partition are blocks 20-21,
// blocks 2-3 are blocks 40-41
var volume = CreateVolume(
new Type1PartitionMap { PartitionNumber = 0 },
new MetadataPartitionMap { PartitionNumber = 0, MetadataFileLocation = 5, MetadataMirrorFileLocation = 6 });
volume.FileEntries[UDFExtensions.GetKey(0, 5)] = CreateShortAdEntry((4096, 20), (4096, 40));
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 1), out long first));
Assert.Equal(1021, first);
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 2), out long second));
Assert.Equal(1040, second);
Assert.False(volume.TryGetSector(UDFExtensions.GetKey(1, 4), out _));
}
[Fact]
public void TryGetSector_Metadata_FallsBackToMirror()
{
var volume = CreateVolume(
new Type1PartitionMap { PartitionNumber = 0 },
new MetadataPartitionMap { PartitionNumber = 0, MetadataFileLocation = 5, MetadataMirrorFileLocation = 6 });
volume.FileEntries[UDFExtensions.GetKey(0, 6)] = CreateShortAdEntry((2048, 30));
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 0), out long sector));
Assert.Equal(1030, sector);
}
#endregion
#region GetAllocationDescriptors
[Fact]
public void GetAllocationDescriptors_Embedded_Empty()
{
var entry = new FileEntry();
entry.ICBTag.Flags = (ushort)AllocationDescriptorType.EMBEDDED;
entry.AllocationDescriptors = [1, 2, 3];
var actual = new Volume().GetAllocationDescriptors(0, entry);
Assert.NotNull(actual);
Assert.Empty(actual);
}
[Fact]
public void GetAllocationDescriptors_ZeroLengthTerminates()
{
var entry = CreateShortAdEntry((2048, 20), (0, 99), (2048, 40));
var actual = new Volume().GetAllocationDescriptors(UDFExtensions.GetKey(2, 7), entry);
Assert.NotNull(actual);
var ad = Assert.Single(actual);
Assert.Equal(20u, ad.ExtentLocation.LogicalBlockNumber);
Assert.Equal((ushort)2, ad.ExtentLocation.PartitionReferenceNumber);
}
[Fact]
public void GetAllocationDescriptors_MissingAllocationExtent_NullWithKey()
{
var entry = CreateShortAdEntry((2048, 20), ((3u << 30) | 2048, 50));
var actual = new Volume().GetAllocationDescriptors(UDFExtensions.GetKey(0, 7), entry, out long missingKey);
Assert.Null(actual);
Assert.Equal(UDFExtensions.GetKey(0, 50), missingKey);
}
[Fact]
public void GetAllocationDescriptors_AllocationExtentLoop_Null()
{
var volume = new Volume();
var entry = CreateShortAdEntry(((3u << 30) | 2048, 50));
volume.AllocationExtentDescriptors[UDFExtensions.GetKey(0, 50)] = new AllocationExtentDescriptor
{
AllocationDescriptors = CreateShortAdEntry(((3u << 30) | 2048, 50)).AllocationDescriptors,
};
var actual = volume.GetAllocationDescriptors(UDFExtensions.GetKey(0, 7), entry);
Assert.Null(actual);
}
#endregion
#region IsValidDescriptor
[Theory]
[InlineData(2)]
[InlineData(3)]
public void IsValidDescriptor_Valid_True(byte version)
{
byte[] data = CreateDescriptor(version);
Assert.True(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_WrongChecksum_False()
{
byte[] data = CreateDescriptor(2);
data[4]++;
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_UnknownVersion_False()
{
byte[] data = CreateDescriptor(4);
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_WrongCrc_False()
{
byte[] data = CreateDescriptor(2);
data[16] ^= 0x01;
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_CrcPastAvailable_NotChecked()
{
byte[] data = CreateDescriptor(2);
data[16] ^= 0x01;
Assert.True(data.IsValidDescriptor(0, 16, 20));
}
[Fact]
public void IsValidDescriptor_WrongLocation_False()
{
byte[] data = CreateDescriptor(2);
Assert.False(data.IsValidDescriptor(0, data.Length, 21));
}
#endregion
/// <summary>
/// Descriptor with tag location 20 whose 3 bytes after the tag have the CRC #3299 (ECMA-167 3/7.2.6)
/// </summary>
private static byte[] CreateDescriptor(byte version)
{
byte[] data = new byte[19];
data[0] = 1;
data[2] = version;
data[8] = 0x99;
data[9] = 0x32;
data[10] = 3;
data[12] = 20;
data[16] = 0x70;
data[17] = 0x6A;
data[18] = 0x77;
for (int i = 0; i < 16; i++)
{
if (i != 4)
data[4] += data[i];
}
return data;
}
/// <summary>
/// Volume with partition 0 at sector 1000 (length 50) and the given partition maps
/// </summary>
private static Volume CreateVolume(params PartitionMap[] maps)
{
return new Volume
{
VolumeDescriptorSequence =
[
new PartitionDescriptor { PartitionNumber = 0, PartitionStartingLocation = 1000, PartitionLength = 50 },
new LogicalVolumeDescriptor { LogicalBlockSize = 2048, PartitionMaps = maps },
],
};
}
/// <summary>
/// File Entry with the given short allocation descriptors (extent length, position)
/// </summary>
private static FileEntry CreateShortAdEntry(params (uint Length, uint Position)[] extents)
{
var data = new byte[extents.Length * 8];
for (int i = 0; i < extents.Length; i++)
{
for (int b = 0; b < 4; b++)
{
data[(i * 8) + b] = (byte)(extents[i].Length >> (8 * b));
data[(i * 8) + 4 + b] = (byte)(extents[i].Position >> (8 * b));
}
}
var entry = new FileEntry { AllocationDescriptors = data };
entry.ICBTag.Flags = (ushort)AllocationDescriptorType.SHORT;
return entry;
}
}
}

View File

@@ -296,6 +296,10 @@ namespace SabreTools.Data.Extensions
string? otherSpamSum = other.SpamSum; string? otherSpamSum = other.SpamSum;
bool conditionalSpamSum = ConditionalHashEquals(selfSpamSum, otherSpamSum); bool conditionalSpamSum = ConditionalHashEquals(selfSpamSum, otherSpamSum);
string? selfBlake3 = self.BLAKE3;
string? otherBlake3 = other.BLAKE3;
bool conditionalBlake3 = ConditionalHashEquals(selfBlake3, otherBlake3);
return conditionalCrc16 return conditionalCrc16
&& conditionalCrc && conditionalCrc
&& conditionalCrc64 && conditionalCrc64
@@ -308,7 +312,8 @@ namespace SabreTools.Data.Extensions
&& conditionalSha256 && conditionalSha256
&& conditionalSha384 && conditionalSha384
&& conditionalSha512 && conditionalSha512
&& conditionalSpamSum; && conditionalSpamSum
&& conditionalBlake3;
} }
/// <summary> /// <summary>
@@ -357,6 +362,7 @@ namespace SabreTools.Data.Extensions
bool sha384Null = string.IsNullOrEmpty(rom.SHA384); bool sha384Null = string.IsNullOrEmpty(rom.SHA384);
bool sha512Null = string.IsNullOrEmpty(rom.SHA512); bool sha512Null = string.IsNullOrEmpty(rom.SHA512);
bool spamsumNull = string.IsNullOrEmpty(rom.SpamSum); bool spamsumNull = string.IsNullOrEmpty(rom.SpamSum);
bool blake3Null = string.IsNullOrEmpty(rom.BLAKE3);
return !crc16Null return !crc16Null
|| !crc32Null || !crc32Null
@@ -370,7 +376,8 @@ namespace SabreTools.Data.Extensions
|| !sha256Null || !sha256Null
|| !sha384Null || !sha384Null
|| !sha512Null || !sha512Null
|| !spamsumNull; || !spamsumNull
|| !blake3Null;
} }
/// <summary> /// <summary>
@@ -455,6 +462,9 @@ namespace SabreTools.Data.Extensions
string? spamsum = rom.SpamSum; string? spamsum = rom.SpamSum;
bool spamsumNull = string.IsNullOrEmpty(spamsum) || string.Equals(spamsum, HashType.SpamSum.ZeroString, StringComparison.OrdinalIgnoreCase); bool spamsumNull = string.IsNullOrEmpty(spamsum) || string.Equals(spamsum, HashType.SpamSum.ZeroString, StringComparison.OrdinalIgnoreCase);
string? blake3 = rom.BLAKE3;
bool blake3Null = string.IsNullOrEmpty(blake3) || string.Equals(blake3, HashType.BLAKE3.ZeroString, StringComparison.OrdinalIgnoreCase);
return crc16Null return crc16Null
&& crc32Null && crc32Null
&& crc64Null && crc64Null
@@ -467,7 +477,8 @@ namespace SabreTools.Data.Extensions
&& sha256Null && sha256Null
&& sha384Null && sha384Null
&& sha512Null && sha512Null
&& spamsumNull; && spamsumNull
&& blake3Null;
} }
/// <summary> /// <summary>
@@ -552,6 +563,9 @@ namespace SabreTools.Data.Extensions
bool spamsumNull = string.IsNullOrEmpty(self.SpamSum); bool spamsumNull = string.IsNullOrEmpty(self.SpamSum);
spamsumNull ^= string.IsNullOrEmpty(other.SpamSum); spamsumNull ^= string.IsNullOrEmpty(other.SpamSum);
bool blake3Null = string.IsNullOrEmpty(self.BLAKE3);
blake3Null ^= string.IsNullOrEmpty(other.BLAKE3);
return !crc16Null return !crc16Null
|| !crc32Null || !crc32Null
|| !crc64Null || !crc64Null
@@ -564,7 +578,8 @@ namespace SabreTools.Data.Extensions
|| !sha256Null || !sha256Null
|| !sha384Null || !sha384Null
|| !sha512Null || !sha512Null
|| !spamsumNull; || !spamsumNull
|| !blake3Null;
} }
#endregion #endregion
@@ -696,6 +711,11 @@ namespace SabreTools.Data.Extensions
string? otherSpamSum = other.SpamSum; string? otherSpamSum = other.SpamSum;
if (string.IsNullOrEmpty(selfSpamSum) && !string.IsNullOrEmpty(otherSpamSum)) if (string.IsNullOrEmpty(selfSpamSum) && !string.IsNullOrEmpty(otherSpamSum))
self.SpamSum = otherSpamSum; self.SpamSum = otherSpamSum;
string? selfBlake3 = self.BLAKE3;
string? otherBlake3 = other.BLAKE3;
if (string.IsNullOrEmpty(selfBlake3) && !string.IsNullOrEmpty(otherBlake3))
self.BLAKE3 = otherBlake3;
} }
#endregion #endregion

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -32,7 +32,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" /> <PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,417 @@
using System.Collections.Generic;
using System.Text;
using SabreTools.Data.Models.UDF;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Data.Extensions
{
public static class UDFExtensions
{
/// <summary>
/// Maximum number of Allocation Extent Descriptors followed for one File Entry
/// </summary>
private const int MaximumAllocationExtents = 1024;
/// <summary>
/// CRC-ITU-T (CCITT, polynomial 0x1021) table for descriptor CRCs
/// </summary>
private static readonly ushort[] CrcTable = CreateCrcTable();
/// <summary>
/// Get the dictionary key of a logical block address
/// </summary>
/// <param name="address">Logical block address</param>
/// <returns>((long)PartitionReferenceNumber &lt;&lt; 32) | LogicalBlockNumber</returns>
public static long GetKey(this LogicalBlockAddress address)
=> GetKey(address.PartitionReferenceNumber, address.LogicalBlockNumber);
/// <summary>
/// Get the dictionary key of a logical block address
/// </summary>
/// <param name="partitionReferenceNumber">Index of the partition map</param>
/// <param name="logicalBlockNumber">Logical block number within the partition</param>
/// <returns>((long)partitionReferenceNumber &lt;&lt; 32) | logicalBlockNumber</returns>
public static long GetKey(ushort partitionReferenceNumber, uint logicalBlockNumber)
=> ((long)partitionReferenceNumber << 32) | logicalBlockNumber;
/// <summary>
/// Get the extent type from an allocation descriptor's extent length
/// </summary>
public static ExtentType GetExtentType(uint extentLength)
=> (ExtentType)(extentLength >> Constants.ExtentTypeShift);
/// <summary>
/// Get the extent length in bytes from an allocation descriptor's extent length
/// </summary>
public static uint GetExtentLength(uint extentLength)
=> extentLength & Constants.ExtentLengthMask;
/// <summary>
/// Get the type of the allocation descriptors of an ICB
/// </summary>
public static AllocationDescriptorType GetAllocationDescriptorType(this ICBTag tag)
=> (AllocationDescriptorType)(tag.Flags & Constants.AllocationDescriptorTypeMask);
/// <summary>
/// Get the prevailing Logical Volume Descriptor (highest Volume Descriptor Sequence Number)
/// </summary>
/// <param name="volume">Volume to search</param>
/// <returns>Logical Volume Descriptor on success, null if none was recorded</returns>
public static LogicalVolumeDescriptor? GetLogicalVolumeDescriptor(this Volume volume)
{
LogicalVolumeDescriptor? prevailing = null;
foreach (var descriptor in volume.VolumeDescriptorSequence)
{
if (descriptor is LogicalVolumeDescriptor lvd
&& (prevailing is null || lvd.VolumeDescriptorSequenceNumber > prevailing.VolumeDescriptorSequenceNumber))
{
prevailing = lvd;
}
}
return prevailing;
}
/// <summary>
/// Get the prevailing Partition Descriptor of a partition (highest Volume Descriptor Sequence Number)
/// </summary>
/// <param name="volume">Volume to search</param>
/// <param name="partitionNumber">Partition number</param>
/// <returns>Partition Descriptor on success, null if none was recorded</returns>
public static PartitionDescriptor? GetPartitionDescriptor(this Volume volume, ushort partitionNumber)
{
PartitionDescriptor? prevailing = null;
foreach (var descriptor in volume.VolumeDescriptorSequence)
{
if (descriptor is PartitionDescriptor pd
&& pd.PartitionNumber == partitionNumber
&& (prevailing is null || pd.VolumeDescriptorSequenceNumber > prevailing.VolumeDescriptorSequenceNumber))
{
prevailing = pd;
}
}
return prevailing;
}
/// <summary>
/// Get the partition reference number of the Type 1 partition map for a partition number
/// </summary>
/// <param name="lvd">Logical Volume Descriptor holding the partition maps</param>
/// <param name="partitionNumber">Partition number</param>
/// <returns>Partition reference number on success, -1 if there is no such map</returns>
public static int GetType1PartitionReference(this LogicalVolumeDescriptor lvd, ushort partitionNumber)
{
for (int i = 0; i < lvd.PartitionMaps.Length; i++)
{
if (lvd.PartitionMaps[i] is Type1PartitionMap map && map.PartitionNumber == partitionNumber)
return i;
}
return -1;
}
/// <summary>
/// Map a logical block of a partition to a logical sector of the volume
/// Supports Type 1 partitions and Metadata Partitions (the Metadata File's File Entry must be in FileEntries)
/// </summary>
/// <param name="volume">Volume containing the Volume Descriptor Sequence</param>
/// <param name="key">Logical block address key, see GetKey</param>
/// <param name="sector">Logical sector on success</param>
/// <returns>True if the block could be mapped, false otherwise</returns>
public static bool TryGetSector(this Volume volume, long key, out long sector)
{
sector = -1;
var lvd = volume.GetLogicalVolumeDescriptor();
if (lvd is null)
return false;
int partitionReference = (int)(key >> 32);
uint block = (uint)key;
if (partitionReference < 0 || partitionReference >= lvd.PartitionMaps.Length)
return false;
var map = lvd.PartitionMaps[partitionReference];
if (map is Type1PartitionMap type1)
{
var pd = volume.GetPartitionDescriptor(type1.PartitionNumber);
if (pd is null || block >= pd.PartitionLength)
return false;
sector = (long)pd.PartitionStartingLocation + block;
return true;
}
else if (map is MetadataPartitionMap metadata)
{
// The Metadata File (or its mirror) is recorded in the Type 1 partition of the same partition number
int physical = lvd.GetType1PartitionReference(metadata.PartitionNumber);
if (physical < 0)
return false;
if (!TryGetMetadataFileBlock(volume, lvd, GetKey((ushort)physical, metadata.MetadataFileLocation), block, out long physicalKey)
&& !TryGetMetadataFileBlock(volume, lvd, GetKey((ushort)physical, metadata.MetadataMirrorFileLocation), block, out physicalKey))
{
return false;
}
return volume.TryGetSector(physicalKey, out sector);
}
// Virtual and Sparable partitions are not supported
return false;
}
/// <summary>
/// Map a logical block of a Metadata Partition through the Metadata File's allocation descriptors
/// </summary>
private static bool TryGetMetadataFileBlock(Volume volume, LogicalVolumeDescriptor lvd, long fileKey, uint block, out long physicalKey)
{
physicalKey = -1;
if (!volume.FileEntries.TryGetValue(fileKey, out var fileEntry))
return false;
var descriptors = volume.GetAllocationDescriptors(fileKey, fileEntry);
if (descriptors is null)
return false;
uint blockSize = lvd.LogicalBlockSize;
long first = 0;
foreach (var ad in descriptors)
{
long blocks = (GetExtentLength(ad.ExtentLength) + blockSize - 1) / blockSize;
if (block < first + blocks)
{
if (GetExtentType(ad.ExtentLength) != ExtentType.RECORDED_AND_ALLOCATED)
return false;
physicalKey = GetKey(ad.ExtentLocation.PartitionReferenceNumber, (uint)(ad.ExtentLocation.LogicalBlockNumber + (block - first)));
return true;
}
first += blocks;
}
return false;
}
/// <summary>
/// Get the allocation descriptors of a File Entry, including those continued in Allocation Extent Descriptors,
/// as long allocation descriptors (short allocation descriptors get the partition of the File Entry)
/// </summary>
/// <param name="volume">Volume containing the Allocation Extent Descriptors</param>
/// <param name="key">Logical block address key of the File Entry</param>
/// <param name="fileEntry">File Entry</param>
/// <returns>Allocation descriptors (empty for embedded data), null if they are not supported or incomplete</returns>
public static List<LongAllocationDescriptor>? GetAllocationDescriptors(this Volume volume, long key, FileEntry fileEntry)
=> volume.GetAllocationDescriptors(key, fileEntry, out _);
/// <summary>
/// Get the allocation descriptors of a File Entry, including those continued in Allocation Extent Descriptors,
/// as long allocation descriptors (short allocation descriptors get the partition of the File Entry)
/// </summary>
/// <param name="volume">Volume containing the Allocation Extent Descriptors</param>
/// <param name="key">Logical block address key of the File Entry</param>
/// <param name="fileEntry">File Entry</param>
/// <param name="missingKey">Key of the first Allocation Extent Descriptor that is not in the volume, -1 if none</param>
/// <returns>Allocation descriptors (empty for embedded data), null if they are not supported or incomplete</returns>
public static List<LongAllocationDescriptor>? GetAllocationDescriptors(this Volume volume, long key, FileEntry fileEntry, out long missingKey)
{
missingKey = -1;
var type = fileEntry.ICBTag.GetAllocationDescriptorType();
if (type == AllocationDescriptorType.EMBEDDED)
return [];
// Extended allocation descriptors are not allowed by UDF
if (type != AllocationDescriptorType.SHORT && type != AllocationDescriptorType.LONG)
return null;
List<LongAllocationDescriptor> result = [];
ushort partition = (ushort)(key >> 32);
byte[] descriptors = fileEntry.AllocationDescriptors;
int length = descriptors.Length;
for (int extents = 0; extents <= MaximumAllocationExtents; extents++)
{
long nextKey = -1;
int size = type == AllocationDescriptorType.SHORT ? 8 : 16;
for (int offset = 0; offset + size <= length; offset += size)
{
var ad = new LongAllocationDescriptor();
ad.ExtentLength = descriptors.ToUInt32LittleEndian(offset);
if (type == AllocationDescriptorType.SHORT)
{
ad.ExtentLocation.LogicalBlockNumber = descriptors.ToUInt32LittleEndian(offset + 4);
ad.ExtentLocation.PartitionReferenceNumber = partition;
}
else
{
ad.ExtentLocation.LogicalBlockNumber = descriptors.ToUInt32LittleEndian(offset + 4);
ad.ExtentLocation.PartitionReferenceNumber = descriptors.ToUInt16LittleEndian(offset + 8);
for (int i = 0; i < 6; i++)
{
ad.ImplementationUse[i] = descriptors[offset + 10 + i];
}
}
// A zero length terminates the allocation descriptors
if (GetExtentLength(ad.ExtentLength) == 0)
return result;
if (GetExtentType(ad.ExtentLength) == ExtentType.NEXT_EXTENT)
{
nextKey = ad.ExtentLocation.GetKey();
break;
}
result.Add(ad);
}
if (nextKey < 0)
return result;
if (!volume.AllocationExtentDescriptors.TryGetValue(nextKey, out var aed))
{
missingKey = nextKey;
return null;
}
partition = (ushort)(nextKey >> 32);
descriptors = aed.AllocationDescriptors;
length = descriptors.Length;
}
// Too many continuation extents, assume a loop
return null;
}
/// <summary>
/// Check a descriptor tag: checksum, version, CRC (when it fits into the data) and location
/// </summary>
/// <remarks>ECMA-167 3/7.2; receiving systems allow descriptor version 2 or 3 (3/7.2.2)</remarks>
/// <param name="data">Data holding the descriptor</param>
/// <param name="offset">Offset of the descriptor</param>
/// <param name="available">Number of bytes of the descriptor available from the offset</param>
/// <param name="location">Expected tag location (logical sector or logical block number)</param>
/// <returns>True if the tag is valid, false otherwise</returns>
public static bool IsValidDescriptor(this byte[] data, int offset, int available, uint location)
{
byte checksum = 0;
for (int i = 0; i < 16; i++)
{
if (i != 4)
checksum += data[offset + i];
}
if (checksum != data[offset + 4])
return false;
ushort version = data.ToUInt16LittleEndian(offset + 2);
if (version != 2 && version != 3)
return false;
ushort crcLength = data.ToUInt16LittleEndian(offset + 10);
if (16 + crcLength <= available)
{
if (ComputeCrc(data, offset + 16, crcLength) != data.ToUInt16LittleEndian(offset + 8))
return false;
}
return data.ToUInt32LittleEndian(offset + 12) == location;
}
/// <summary>
/// Decode OSTA Compressed Unicode d-characters (first byte = compression ID)
/// </summary>
/// <param name="data">Encoded characters</param>
/// <returns>Decoded string, empty for no characters, null for an unknown compression ID</returns>
public static string? DecodeDCharacters(this byte[]? data)
=> DecodeDCharacters(data, data?.Length ?? 0);
/// <summary>
/// Decode an OSTA Compressed Unicode dstring (last byte = number of used bytes)
/// </summary>
/// <param name="data">Encoded dstring field</param>
/// <returns>Decoded string, empty for no characters, null for an unknown compression ID</returns>
public static string? DecodeDString(this byte[]? data)
{
if (data is null || data.Length == 0)
return string.Empty;
int length = data[data.Length - 1];
if (length > data.Length - 1)
length = data.Length - 1;
return DecodeDCharacters(data, length);
}
/// <summary>
/// Decode the first length bytes of OSTA Compressed Unicode d-characters
/// </summary>
private static string? DecodeDCharacters(byte[]? data, int length)
{
if (data is null || length == 0)
return string.Empty;
// 254 and 255 are the same encodings, used by UDF for deleted entries
byte compressionId = data[0];
var builder = new StringBuilder(length);
if (compressionId == Constants.CompressionId8 || compressionId == 254)
{
for (int i = 1; i < length; i++)
{
builder.Append((char)data[i]);
}
}
else if (compressionId == Constants.CompressionId16 || compressionId == 255)
{
for (int i = 1; i + 1 < length; i += 2)
{
builder.Append((char)data.ToUInt16BigEndian(i));
}
}
else
{
return null;
}
return builder.ToString();
}
// TODO: Replace with CRC-16/XMODEM from Hashing once its CRC code is faster
/// <summary>
/// Compute the CRC-ITU-T of a range of bytes
/// </summary>
private static ushort ComputeCrc(byte[] data, int offset, int length)
{
ushort crc = 0;
for (int i = offset; i < offset + length; i++)
{
crc = (ushort)(CrcTable[((crc >> 8) ^ data[i]) & 0xFF] ^ (crc << 8));
}
return crc;
}
/// <summary>
/// Create the CRC-ITU-T table
/// </summary>
private static ushort[] CreateCrcTable()
{
var table = new ushort[256];
for (int i = 0; i < 256; i++)
{
ushort crc = (ushort)(i << 8);
for (int bit = 0; bit < 8; bit++)
{
crc = (crc & 0x8000) != 0 ? (ushort)((crc << 1) ^ 0x1021) : (ushort)(crc << 1);
}
table[i] = crc;
}
return table;
}
}
}

View File

@@ -68,6 +68,9 @@ namespace SabreTools.Data.Models.ClrMamePro
/// <remarks>spamsum; Appears after SHA512</remarks> /// <remarks>spamsum; Appears after SHA512</remarks>
public string? SpamSum { get; set; } public string? SpamSum { get; set; }
/// <remarks>blake3; Appears after SHA512</remarks>
public string? BLAKE3 { get; set; }
#endregion #endregion
#region DiscImgeCreator Extensions #region DiscImgeCreator Extensions

View File

@@ -0,0 +1,15 @@
namespace SabreTools.Data.Models.Hashfile
{
/// <summary>
/// BLAKE3 File
/// </summary>
public class BLAKE3
{
[Required]
public string? Hash { get; set; }
[Required]
public string? File { get; set; }
}
}

View File

@@ -24,5 +24,7 @@ namespace SabreTools.Data.Models.Hashfile
public SHA512[]? SHA512 { get; set; } public SHA512[]? SHA512 { get; set; }
public SpamSum[]? SpamSum { get; set; } public SpamSum[]? SpamSum { get; set; }
public BLAKE3[]? BLAKE3 { get; set; }
} }
} }

View File

@@ -55,6 +55,11 @@ namespace SabreTools.Data.Models.Logiqx
[XmlAttribute("runnable")] [XmlAttribute("runnable")]
public Runnable? Runnable { get; set; } public Runnable? Runnable { get; set; }
/// <remarks>(dir|zip|7z), RomVault extension</remarks>
/// TODO: Turn this into an enum like the other flags
[XmlAttribute("type")]
public string? SetType { get; set; }
[XmlElement("comment")] [XmlElement("comment")]
public string[]? Comment { get; set; } public string[]? Comment { get; set; }

View File

@@ -48,6 +48,10 @@ namespace SabreTools.Data.Models.Logiqx
[XmlElement("comment")] [XmlElement("comment")]
public string? Comment { get; set; } public string? Comment { get; set; }
/// <remarks>RomVault extension</remarks>
[XmlElement("subset")]
public string? Subset { get; set; }
/// <remarks>Trurip extension</remarks> /// <remarks>Trurip extension</remarks>
[XmlElement("type")] [XmlElement("type")]
public string? Type { get; set; } public string? Type { get; set; }

View File

@@ -65,6 +65,10 @@ namespace SabreTools.Data.Models.Logiqx
[XmlAttribute("spamsum")] [XmlAttribute("spamsum")]
public string? SpamSum { get; set; } public string? SpamSum { get; set; }
/// <remarks>Hash extension</remarks>
[XmlAttribute("blake3")]
public string? BLAKE3 { get; set; }
/// <remarks>DiscImgeCreator extension</remarks> /// <remarks>DiscImgeCreator extension</remarks>
[XmlAttribute("xxh3_64")] [XmlAttribute("xxh3_64")]
public string? xxHash364 { get; set; } public string? xxHash364 { get; set; }

View File

@@ -21,5 +21,10 @@ namespace SabreTools.Data.Models.Logiqx
/// <remarks>(zip|unzip) "zip"</remarks> /// <remarks>(zip|unzip) "zip"</remarks>
[XmlAttribute("forcepacking")] [XmlAttribute("forcepacking")]
public PackingFlag ForcePacking { get; set; } public PackingFlag ForcePacking { get; set; }
/// <remarks>(noautodir|nogame)</remarks>
/// TODO: Turn this into an enum like the other flags
[XmlAttribute("dir")]
public string? Dir { get; set; }
} }
} }

View File

@@ -33,6 +33,8 @@ namespace SabreTools.Data.Models.Metadata
public string? Description { get; set; } public string? Description { get; set; }
public string? DirHandling { get; set; }
public string? Email { get; set; } public string? Email { get; set; }
public string? EmulatorVersion { get; set; } public string? EmulatorVersion { get; set; }
@@ -108,6 +110,8 @@ namespace SabreTools.Data.Models.Metadata
/// TODO: This needs an internal model OR mapping to fields /// TODO: This needs an internal model OR mapping to fields
public OfflineList.Search? Search { get; set; } public OfflineList.Search? Search { get; set; }
public string? Subset { get; set; }
public string? System { get; set; } public string? System { get; set; }
public string? Timestamp { get; set; } public string? Timestamp { get; set; }
@@ -135,6 +139,7 @@ namespace SabreTools.Data.Models.Metadata
obj.DatVersion = DatVersion; obj.DatVersion = DatVersion;
obj.Debug = Debug; obj.Debug = Debug;
obj.Description = Description; obj.Description = Description;
obj.DirHandling = DirHandling;
obj.Email = Email; obj.Email = Email;
obj.EmulatorVersion = EmulatorVersion; obj.EmulatorVersion = EmulatorVersion;
obj.FileName = FileName; obj.FileName = FileName;
@@ -166,6 +171,7 @@ namespace SabreTools.Data.Models.Metadata
obj.ScreenshotsHeight = ScreenshotsHeight; obj.ScreenshotsHeight = ScreenshotsHeight;
obj.ScreenshotsWidth = ScreenshotsWidth; obj.ScreenshotsWidth = ScreenshotsWidth;
obj.Search = Search; obj.Search = Search;
obj.Subset = Subset;
obj.System = System; obj.System = System;
obj.Timestamp = Timestamp; obj.Timestamp = Timestamp;
obj.Type = Type; obj.Type = Type;
@@ -224,6 +230,11 @@ namespace SabreTools.Data.Models.Metadata
else if (Description is not null && !Description.Equals(other.Description, StringComparison.OrdinalIgnoreCase)) else if (Description is not null && !Description.Equals(other.Description, StringComparison.OrdinalIgnoreCase))
return false; return false;
if ((DirHandling is null) ^ (other.DirHandling is null))
return false;
else if (DirHandling is not null && !DirHandling.Equals(other.DirHandling, StringComparison.OrdinalIgnoreCase))
return false;
if ((Email is null) ^ (other.Email is null)) if ((Email is null) ^ (other.Email is null))
return false; return false;
else if (Email is not null && !Email.Equals(other.Email, StringComparison.OrdinalIgnoreCase)) else if (Email is not null && !Email.Equals(other.Email, StringComparison.OrdinalIgnoreCase))
@@ -336,6 +347,11 @@ namespace SabreTools.Data.Models.Metadata
else if (ScreenshotsWidth is not null && !ScreenshotsWidth.Equals(other.ScreenshotsWidth, StringComparison.OrdinalIgnoreCase)) else if (ScreenshotsWidth is not null && !ScreenshotsWidth.Equals(other.ScreenshotsWidth, StringComparison.OrdinalIgnoreCase))
return false; return false;
if ((Subset is null) ^ (other.Subset is null))
return false;
else if (Subset is not null && !Subset.Equals(other.Subset, StringComparison.OrdinalIgnoreCase))
return false;
if ((System is null) ^ (other.System is null)) if ((System is null) ^ (other.System is null))
return false; return false;
else if (System is not null && !System.Equals(other.System, StringComparison.OrdinalIgnoreCase)) else if (System is not null && !System.Equals(other.System, StringComparison.OrdinalIgnoreCase))

View File

@@ -159,6 +159,9 @@ namespace SabreTools.Data.Models.Metadata
public string? Serial { get; set; } public string? Serial { get; set; }
/// <remarks>(dir|zip|7z), RomVault extension</remarks>
public string? SetType { get; set; }
public SharedFeat[]? SharedFeat { get; set; } public SharedFeat[]? SharedFeat { get; set; }
public Slot[]? Slot { get; set; } public Slot[]? Slot { get; set; }
@@ -298,6 +301,7 @@ namespace SabreTools.Data.Models.Metadata
obj.SaveType = SaveType; obj.SaveType = SaveType;
obj.Score = Score; obj.Score = Score;
obj.Serial = Serial; obj.Serial = Serial;
obj.SetType = SetType;
if (SharedFeat is not null) if (SharedFeat is not null)
obj.SharedFeat = Array.ConvertAll(SharedFeat, i => (SharedFeat)i.Clone()); obj.SharedFeat = Array.ConvertAll(SharedFeat, i => (SharedFeat)i.Clone());
if (Slot is not null) if (Slot is not null)
@@ -569,6 +573,11 @@ namespace SabreTools.Data.Models.Metadata
else if (Serial is not null && !Serial.Equals(other.Serial, StringComparison.OrdinalIgnoreCase)) else if (Serial is not null && !Serial.Equals(other.Serial, StringComparison.OrdinalIgnoreCase))
return false; return false;
if ((SetType is null) ^ (other.SetType is null))
return false;
else if (SetType is not null && !SetType.Equals(other.SetType, StringComparison.OrdinalIgnoreCase))
return false;
if ((Source is null) ^ (other.Source is null)) if ((Source is null) ^ (other.Source is null))
return false; return false;
else if (Source is not null && !Source.Equals(other.Source, StringComparison.OrdinalIgnoreCase)) else if (Source is not null && !Source.Equals(other.Source, StringComparison.OrdinalIgnoreCase))

View File

@@ -39,6 +39,8 @@ namespace SabreTools.Data.Models.Metadata
/// <remarks>ArchiveDotOrg.File</remarks> /// <remarks>ArchiveDotOrg.File</remarks>
public string? BitTorrentMagnetHash { get; set; } public string? BitTorrentMagnetHash { get; set; }
public string? BLAKE3 { get; set; }
/// <remarks>ArchiveDotOrg.File</remarks> /// <remarks>ArchiveDotOrg.File</remarks>
public string? ClothCoverDetectionModuleVersion { get; set; } public string? ClothCoverDetectionModuleVersion { get; set; }
@@ -190,6 +192,9 @@ namespace SabreTools.Data.Models.Metadata
/// <remarks>(baddump|nodump|good|verified) "good"</remarks> /// <remarks>(baddump|nodump|good|verified) "good"</remarks>
public ItemStatus? Status { get; set; } public ItemStatus? Status { get; set; }
/// <remarks>Possibly enum; OpenMSX.RomBase</remarks>
public string? StatusMisc { get; set; }
/// <remarks>ArchiveDotOrg.File</remarks> /// <remarks>ArchiveDotOrg.File</remarks>
public string? Summation { get; set; } public string? Summation { get; set; }
@@ -293,6 +298,7 @@ namespace SabreTools.Data.Models.Metadata
obj.Bios = Bios; obj.Bios = Bios;
obj.Bitrate = Bitrate; obj.Bitrate = Bitrate;
obj.BitTorrentMagnetHash = BitTorrentMagnetHash; obj.BitTorrentMagnetHash = BitTorrentMagnetHash;
obj.BLAKE3 = BLAKE3;
obj.ClothCoverDetectionModuleVersion = ClothCoverDetectionModuleVersion; obj.ClothCoverDetectionModuleVersion = ClothCoverDetectionModuleVersion;
obj.CollectionCatalogNumber = CollectionCatalogNumber; obj.CollectionCatalogNumber = CollectionCatalogNumber;
obj.Comment = Comment; obj.Comment = Comment;
@@ -350,6 +356,7 @@ namespace SabreTools.Data.Models.Metadata
obj.SpamSum = SpamSum; obj.SpamSum = SpamSum;
obj.Start = Start; obj.Start = Start;
obj.Status = Status; obj.Status = Status;
obj.StatusMisc = StatusMisc;
obj.Summation = Summation; obj.Summation = Summation;
obj.TesseractOCR = TesseractOCR; obj.TesseractOCR = TesseractOCR;
obj.TesseractOCRConverted = TesseractOCRConverted; obj.TesseractOCRConverted = TesseractOCRConverted;

View File

@@ -8,17 +8,37 @@ namespace SabreTools.Data.Models.OpenMSX
/// </summary> /// </summary>
public abstract class RomBase public abstract class RomBase
{ {
[XmlElement("start")] /// <remarks>
public string? Start { get; set; } /// "hash" in older versions, "sha1" in newer versions
/// </remarks>
[XmlAttribute("sha1")]
[XmlElement("hash")]
public string? SHA1 { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[XmlAttribute("type")]
[XmlElement("type")] [XmlElement("type")]
public string? Type { get; set; } public string? Type { get; set; }
/// <remarks>SHA-1 hash</remarks> /// <remarks>
[XmlElement("hash")] /// Only present in newer versions
public string? Hash { get; set; } /// </remarks>
[XmlAttribute("status")]
public string? Status { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[XmlAttribute("remark")]
[XmlElement("remark")] [XmlElement("remark")]
public string? Remark { get; set; } public string? Remark { get; set; }
/// <remarks>
/// Only present in older versions
/// </remarks>
[XmlElement("start")]
public string? Start { get; set; }
} }
} }

View File

@@ -6,30 +6,63 @@ namespace SabreTools.Data.Models.OpenMSX
[XmlRoot("software")] [XmlRoot("software")]
public class Software public class Software
{ {
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[Required] [Required]
[XmlAttribute("title")]
[XmlElement("title")] [XmlElement("title")]
public string? Title { get; set; } public string? Title { get; set; }
[XmlElement("genmsxid")] /// <remarks>
public string? GenMSXID { get; set; } /// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[Required] [Required]
[XmlAttribute("system")]
[XmlElement("system")] [XmlElement("system")]
public string? System { get; set; } public string? System { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[Required] [Required]
[XmlAttribute("company")]
[XmlElement("company")] [XmlElement("company")]
public string? Company { get; set; } public string? Company { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[Required] [Required]
[XmlAttribute("year")]
[XmlElement("year")] [XmlElement("year")]
public string? Year { get; set; } public string? Year { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[Required] [Required]
[XmlAttribute("country")]
[XmlElement("country")] [XmlElement("country")]
public string? Country { get; set; } public string? Country { get; set; }
/// <remarks>
/// Element in older versions and DTD, attribute in newer versions
/// </remarks>
[XmlAttribute("genmsxid")]
[XmlElement("genmsxid")]
public string? GenMSXID { get; set; }
/// <remarks>
/// Present in older versions
/// </remarks>
[XmlElement("dump")] [XmlElement("dump")]
public Dump[]? Dump { get; set; } public Dump[]? Dump { get; set; }
/// <remarks>
/// Present in newer versions
/// </remarks>
[XmlElement("rom")]
public RomBase[]? Rom { get; set; }
} }
} }

View File

@@ -22,7 +22,13 @@ namespace SabreTools.Data.Models.PKZIP
/// File data, appears after the encryption header /// File data, appears after the encryption header
/// if it exists or after the local file header otherwise /// if it exists or after the local file header otherwise
/// </summary> /// </summary>
public byte[] FileData { get; set; } = []; /// Not read into the model, see <see cref="FileDataOffset"/>
// public byte[] FileData { get; set; } = [];
/// <summary>
/// Offset of the file data in the stream
/// </summary>
public long FileDataOffset { get; set; }
/// <summary> /// <summary>
/// Data descriptors, appears after the file data /// Data descriptors, appears after the file data

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -32,7 +32,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Allocation Extent Descriptor (4/14.5), continues the allocation descriptors of a File Entry
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class AllocationExtentDescriptor : Descriptor
{
/// <summary>
/// Logical block of the previous allocation extent
/// </summary>
public uint PreviousAllocationExtentLocation { get; set; }
/// <summary>
/// Length of the allocation descriptors in bytes
/// </summary>
public uint LengthOfAllocationDescriptors { get; set; }
/// <summary>
/// Allocation descriptors, of the type given in the owning File Entry's ICB tag flags
/// </summary>
public byte[] AllocationDescriptors { get; set; } = [];
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Anchor Volume Descriptor Pointer (3/10.2), recorded at sector 256 (and at the end of the volume)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class AnchorVolumeDescriptorPointer : Descriptor
{
/// <summary>
/// Extent of the Main Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor MainVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Extent of the Reserve Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor ReserveVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Reserved, 480 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[480];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Character Set Specification (charspec, 1/7.2.1)
/// UDF requires CS0 with the information "OSTA Compressed Unicode"
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class CharacterSetSpecification
{
/// <summary>
/// Character set type (0 = CS0)
/// </summary>
public byte CharacterSetType { get; set; }
/// <summary>
/// Character set information, padded with 0x00
/// </summary>
public byte[] CharacterSetInformation { get; set; } = new byte[63];
}
}

View File

@@ -0,0 +1,126 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF / ECMA-167 constant values and arrays
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public static class Constants
{
#region Volume Recognition Constants
/// <summary>
/// Size of a logical sector on optical media
/// </summary>
public const int SectorSize = 2048;
/// <summary>
/// Sector of the first Volume Structure Descriptor (after the 32KiB System Area)
/// </summary>
public const int VolumeRecognitionSequenceSector = 16;
/// <summary>
/// Sector of the first Anchor Volume Descriptor Pointer
/// </summary>
public const int AnchorVolumeDescriptorPointerSector = 256;
/// <summary>
/// Beginning Extended Area Descriptor identifier, "BEA01"
/// </summary>
public static readonly byte[] BeginningExtendedAreaDescriptor = [0x42, 0x45, 0x41, 0x30, 0x31];
/// <summary>
/// NSR Descriptor identifier for ECMA-167 2nd edition (UDF 1.02 to 1.50), "NSR02"
/// </summary>
public static readonly byte[] NSR02 = [0x4E, 0x53, 0x52, 0x30, 0x32];
/// <summary>
/// NSR Descriptor identifier for ECMA-167 3rd edition (UDF 2.00 and later), "NSR03"
/// </summary>
public static readonly byte[] NSR03 = [0x4E, 0x53, 0x52, 0x30, 0x33];
/// <summary>
/// Terminating Extended Area Descriptor identifier, "TEA01"
/// </summary>
public static readonly byte[] TerminatingExtendedAreaDescriptor = [0x54, 0x45, 0x41, 0x30, 0x31];
/// <summary>
/// ISO 9660 volume descriptor identifier, "CD001" (bridge discs)
/// </summary>
public static readonly byte[] CD001 = [0x43, 0x44, 0x30, 0x30, 0x31];
/// <summary>
/// Boot Descriptor identifier, "BOOT2"
/// </summary>
public static readonly byte[] BOOT2 = [0x42, 0x4F, 0x4F, 0x54, 0x32];
/// <summary>
/// ECMA-168 volume descriptor identifier, "CDW02"
/// </summary>
public static readonly byte[] CDW02 = [0x43, 0x44, 0x57, 0x30, 0x32];
#endregion
#region Partition Map Constants
/// <summary>
/// Type 1 Partition Map type
/// </summary>
public const byte Type1PartitionMap = 1;
/// <summary>
/// Type 2 Partition Map type
/// </summary>
public const byte Type2PartitionMap = 2;
/// <summary>
/// Partition type identifier of a UDF Metadata Partition (UDF 2.50 and later)
/// </summary>
public const string MetadataPartitionIdentifier = "*UDF Metadata Partition";
/// <summary>
/// Partition type identifier of a UDF Sparable Partition
/// </summary>
public const string SparablePartitionIdentifier = "*UDF Sparable Partition";
/// <summary>
/// Partition type identifier of a UDF Virtual Partition
/// </summary>
public const string VirtualPartitionIdentifier = "*UDF Virtual Partition";
#endregion
#region Character Set Constants
/// <summary>
/// OSTA Compressed Unicode, 8 bits per character
/// </summary>
public const byte CompressionId8 = 8;
/// <summary>
/// OSTA Compressed Unicode, 16 bits per character (big-endian)
/// </summary>
public const byte CompressionId16 = 16;
#endregion
#region Allocation Descriptor Constants
/// <summary>
/// Mask of the extent length in an allocation descriptor (the top two bits hold the extent type)
/// </summary>
public const uint ExtentLengthMask = 0x3FFFFFFF;
/// <summary>
/// Shift of the extent type in an allocation descriptor's extent length
/// </summary>
public const int ExtentTypeShift = 30;
/// <summary>
/// Mask of the allocation descriptor type in an ICB tag's flags
/// </summary>
public const ushort AllocationDescriptorTypeMask = 0x0007;
#endregion
}
}

View File

@@ -0,0 +1,14 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Abstract UDF tagged descriptor, all start with a Descriptor Tag
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public abstract class Descriptor
{
/// <summary>
/// Descriptor tag
/// </summary>
public DescriptorTag DescriptorTag { get; set; } = new();
}
}

View File

@@ -0,0 +1,50 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Descriptor Tag (3/7.2), the first 16 bytes of every tagged descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class DescriptorTag
{
/// <summary>
/// Type of the descriptor
/// </summary>
public TagIdentifier TagIdentifier { get; set; }
/// <summary>
/// Version of the descriptor, 2 (ECMA-167 2nd edition) or 3 (3rd edition)
/// </summary>
public ushort DescriptorVersion { get; set; }
/// <summary>
/// Sum modulo 256 of bytes 0-3 and 5-15 of the tag
/// </summary>
public byte TagChecksum { get; set; }
/// <summary>
/// Reserved, 0x00
/// </summary>
public byte Reserved { get; set; }
/// <summary>
/// Identification of a set of descriptors
/// </summary>
public ushort TagSerialNumber { get; set; }
/// <summary>
/// CRC-CCITT of the DescriptorCRCLength bytes following the tag
/// </summary>
public ushort DescriptorCRC { get; set; }
/// <summary>
/// Number of bytes after the tag covered by the DescriptorCRC
/// </summary>
public ushort DescriptorCRCLength { get; set; }
/// <summary>
/// Number of the logical sector (volume structures) or logical block (file structures)
/// in which the descriptor is recorded
/// </summary>
public uint TagLocation { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Entity Identifier (regid, 1/7.4)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class EntityIdentifier
{
/// <summary>
/// Flags (bit 0: dirty, bit 1: protected)
/// </summary>
public byte Flags { get; set; }
/// <summary>
/// Identifier, padded with 0x00
/// </summary>
public byte[] Identifier { get; set; } = new byte[23];
/// <summary>
/// Identifier suffix (UDF domain identifiers: revision as a little-endian ushort, then domain flags)
/// </summary>
public byte[] IdentifierSuffix { get; set; } = new byte[8];
}
}

View File

@@ -0,0 +1,306 @@
using System;
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Enum for DescriptorTag.TagIdentifier
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum TagIdentifier : ushort
{
/// <summary>
/// Primary Volume Descriptor (3/10.1)
/// </summary>
PRIMARY_VOLUME_DESCRIPTOR = 1,
/// <summary>
/// Anchor Volume Descriptor Pointer (3/10.2)
/// </summary>
ANCHOR_VOLUME_DESCRIPTOR_POINTER = 2,
/// <summary>
/// Volume Descriptor Pointer (3/10.3)
/// </summary>
VOLUME_DESCRIPTOR_POINTER = 3,
/// <summary>
/// Implementation Use Volume Descriptor (3/10.4)
/// </summary>
IMPLEMENTATION_USE_VOLUME_DESCRIPTOR = 4,
/// <summary>
/// Partition Descriptor (3/10.5)
/// </summary>
PARTITION_DESCRIPTOR = 5,
/// <summary>
/// Logical Volume Descriptor (3/10.6)
/// </summary>
LOGICAL_VOLUME_DESCRIPTOR = 6,
/// <summary>
/// Unallocated Space Descriptor (3/10.8)
/// </summary>
UNALLOCATED_SPACE_DESCRIPTOR = 7,
/// <summary>
/// Terminating Descriptor (3/10.9)
/// </summary>
TERMINATING_DESCRIPTOR = 8,
/// <summary>
/// Logical Volume Integrity Descriptor (3/10.10)
/// </summary>
LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR = 9,
/// <summary>
/// File Set Descriptor (4/14.1)
/// </summary>
FILE_SET_DESCRIPTOR = 256,
/// <summary>
/// File Identifier Descriptor (4/14.4)
/// </summary>
FILE_IDENTIFIER_DESCRIPTOR = 257,
/// <summary>
/// Allocation Extent Descriptor (4/14.5)
/// </summary>
ALLOCATION_EXTENT_DESCRIPTOR = 258,
/// <summary>
/// Indirect Entry (4/14.7)
/// </summary>
INDIRECT_ENTRY = 259,
/// <summary>
/// Terminal Entry (4/14.8)
/// </summary>
TERMINAL_ENTRY = 260,
/// <summary>
/// File Entry (4/14.9)
/// </summary>
FILE_ENTRY = 261,
/// <summary>
/// Extended Attribute Header Descriptor (4/14.10.1)
/// </summary>
EXTENDED_ATTRIBUTE_HEADER_DESCRIPTOR = 262,
/// <summary>
/// Unallocated Space Entry (4/14.11)
/// </summary>
UNALLOCATED_SPACE_ENTRY = 263,
/// <summary>
/// Space Bitmap Descriptor (4/14.12)
/// </summary>
SPACE_BITMAP_DESCRIPTOR = 264,
/// <summary>
/// Partition Integrity Entry (4/14.13)
/// </summary>
PARTITION_INTEGRITY_ENTRY = 265,
/// <summary>
/// Extended File Entry (4/14.17)
/// </summary>
EXTENDED_FILE_ENTRY = 266,
}
/// <summary>
/// Enum for FileIdentifierDescriptor.FileCharacteristics (4/14.4.3)
/// Bits 5-7: Reserved: 0
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
[Flags]
public enum FileCharacteristics : byte
{
/// <summary>
/// No characteristics set
/// </summary>
NONE = 0x00,
/// <summary>
/// The existence of the file shall not be made known to the user (hidden)
/// </summary>
EXISTENCE = 0x01,
/// <summary>
/// The file is a directory
/// </summary>
DIRECTORY = 0x02,
/// <summary>
/// The descriptor shall be ignored (deleted)
/// </summary>
DELETED = 0x04,
/// <summary>
/// The ICB identifies the parent directory
/// </summary>
PARENT = 0x08,
/// <summary>
/// The file contains implementation use data (UDF: stream directory metadata)
/// </summary>
METADATA = 0x10,
}
/// <summary>
/// Enum for ICBTag.FileType (4/14.6.6, UDF 2.3.5.4)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum FileType : byte
{
/// <summary>
/// Not specified
/// </summary>
UNSPECIFIED = 0,
/// <summary>
/// Unallocated Space Entry
/// </summary>
UNALLOCATED_SPACE_ENTRY = 1,
/// <summary>
/// Partition Integrity Entry
/// </summary>
PARTITION_INTEGRITY_ENTRY = 2,
/// <summary>
/// Indirect Entry
/// </summary>
INDIRECT_ENTRY = 3,
/// <summary>
/// Directory
/// </summary>
DIRECTORY = 4,
/// <summary>
/// Sequence of bytes (a regular file)
/// </summary>
FILE = 5,
/// <summary>
/// Block special device
/// </summary>
BLOCK_DEVICE = 6,
/// <summary>
/// Character special device
/// </summary>
CHARACTER_DEVICE = 7,
/// <summary>
/// Extended attributes
/// </summary>
EXTENDED_ATTRIBUTES = 8,
/// <summary>
/// FIFO file
/// </summary>
FIFO = 9,
/// <summary>
/// Socket
/// </summary>
SOCKET = 10,
/// <summary>
/// Terminal Entry
/// </summary>
TERMINAL_ENTRY = 11,
/// <summary>
/// Symbolic link
/// </summary>
SYMBOLIC_LINK = 12,
/// <summary>
/// Stream directory
/// </summary>
STREAM_DIRECTORY = 13,
/// <summary>
/// UDF Virtual Allocation Table
/// </summary>
VIRTUAL_ALLOCATION_TABLE = 248,
/// <summary>
/// UDF Real-Time file
/// </summary>
REAL_TIME_FILE = 249,
/// <summary>
/// UDF Metadata File
/// </summary>
METADATA_FILE = 250,
/// <summary>
/// UDF Metadata Mirror File
/// </summary>
METADATA_MIRROR_FILE = 251,
/// <summary>
/// UDF Metadata Bitmap File
/// </summary>
METADATA_BITMAP_FILE = 252,
}
/// <summary>
/// Enum for the allocation descriptor type in ICBTag.Flags (4/14.6.8)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum AllocationDescriptorType : byte
{
/// <summary>
/// Short Allocation Descriptors (4/14.14.1)
/// </summary>
SHORT = 0,
/// <summary>
/// Long Allocation Descriptors (4/14.14.2)
/// </summary>
LONG = 1,
/// <summary>
/// Extended Allocation Descriptors (4/14.14.3)
/// </summary>
EXTENDED = 2,
/// <summary>
/// The file data is embedded in the allocation descriptors field
/// </summary>
EMBEDDED = 3,
}
/// <summary>
/// Enum for the extent type in the top two bits of an allocation descriptor's extent length (4/14.14.1.1)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public enum ExtentType : byte
{
/// <summary>
/// Extent recorded and allocated
/// </summary>
RECORDED_AND_ALLOCATED = 0,
/// <summary>
/// Extent not recorded but allocated
/// </summary>
NOT_RECORDED_BUT_ALLOCATED = 1,
/// <summary>
/// Extent not recorded and not allocated
/// </summary>
NOT_RECORDED_NOT_ALLOCATED = 2,
/// <summary>
/// The extent is the next extent of allocation descriptors
/// </summary>
NEXT_EXTENT = 3,
}
}

View File

@@ -0,0 +1,30 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Extended File Entry (4/14.17, UDF 2.00 and later), a File Entry with object size,
/// creation time and a stream directory
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ExtendedFileEntry : FileEntry
{
/// <summary>
/// Size of the file and its streams in bytes
/// </summary>
public ulong ObjectSize { get; set; }
/// <summary>
/// Creation date and time
/// </summary>
public Timestamp CreationDateAndTime { get; set; } = new();
/// <summary>
/// Reserved, 0x00000000
/// </summary>
public uint Reserved { get; set; }
/// <summary>
/// ICB of the stream directory, length 0 if none
/// </summary>
public LongAllocationDescriptor StreamDirectoryICB { get; set; } = new();
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Extent Descriptor (extent_ad, 3/7.1), an extent of logical sectors
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ExtentDescriptor
{
/// <summary>
/// Length of the extent in bytes
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical sector number of the start of the extent
/// </summary>
public uint ExtentLocation { get; set; }
}
}

View File

@@ -0,0 +1,115 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Entry (4/14.9), the ICB describing a file or directory and where its data is recorded
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public class FileEntry : Descriptor
{
/// <summary>
/// ICB tag
/// </summary>
public ICBTag ICBTag { get; set; } = new();
/// <summary>
/// User ID
/// </summary>
public uint Uid { get; set; }
/// <summary>
/// Group ID
/// </summary>
public uint Gid { get; set; }
/// <summary>
/// Permissions
/// </summary>
public uint Permissions { get; set; }
/// <summary>
/// Number of File Identifier Descriptors identifying this ICB
/// </summary>
public ushort FileLinkCount { get; set; }
/// <summary>
/// Record format
/// </summary>
public byte RecordFormat { get; set; }
/// <summary>
/// Record display attributes
/// </summary>
public byte RecordDisplayAttributes { get; set; }
/// <summary>
/// Record length
/// </summary>
public uint RecordLength { get; set; }
/// <summary>
/// Size of the file in bytes
/// </summary>
public ulong InformationLength { get; set; }
/// <summary>
/// Number of recorded logical blocks
/// </summary>
public ulong LogicalBlocksRecorded { get; set; }
/// <summary>
/// Access date and time
/// </summary>
public Timestamp AccessDateAndTime { get; set; } = new();
/// <summary>
/// Modification date and time
/// </summary>
public Timestamp ModificationDateAndTime { get; set; } = new();
/// <summary>
/// Attribute date and time
/// </summary>
public Timestamp AttributeDateAndTime { get; set; } = new();
/// <summary>
/// Checkpoint
/// </summary>
public uint Checkpoint { get; set; }
/// <summary>
/// ICB of the extended attributes, length 0 if none
/// </summary>
public LongAllocationDescriptor ExtendedAttributeICB { get; set; } = new();
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Unique ID
/// </summary>
public ulong UniqueID { get; set; }
/// <summary>
/// Length of the extended attributes in bytes
/// </summary>
public uint LengthOfExtendedAttributes { get; set; }
/// <summary>
/// Length of the allocation descriptors in bytes
/// </summary>
public uint LengthOfAllocationDescriptors { get; set; }
/// <summary>
/// Extended attributes
/// </summary>
public byte[] ExtendedAttributes { get; set; } = [];
/// <summary>
/// Allocation descriptors of the type given in the ICB tag flags,
/// or the file data itself if the type is AllocationDescriptorType.EMBEDDED
/// </summary>
public byte[] AllocationDescriptors { get; set; } = [];
}
}

View File

@@ -0,0 +1,49 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Identifier Descriptor (4/14.4), a directory entry naming a file or directory and pointing to its ICB
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class FileIdentifierDescriptor : Descriptor
{
/// <summary>
/// File version number
/// </summary>
public ushort FileVersionNumber { get; set; }
/// <summary>
/// File characteristics
/// </summary>
public FileCharacteristics FileCharacteristics { get; set; }
/// <summary>
/// Length of the file identifier in bytes
/// </summary>
public byte LengthOfFileIdentifier { get; set; }
/// <summary>
/// ICB of the file or directory
/// </summary>
public LongAllocationDescriptor ICB { get; set; } = new();
/// <summary>
/// Length of the implementation use in bytes
/// </summary>
public ushort LengthOfImplementationUse { get; set; }
/// <summary>
/// Implementation use
/// </summary>
public byte[] ImplementationUse { get; set; } = [];
/// <summary>
/// File identifier, d-characters (first byte = compression ID), empty for the parent directory
/// </summary>
public byte[] FileIdentifier { get; set; } = [];
/// <summary>
/// Padding to a multiple of 4 bytes
/// </summary>
public byte[] Padding { get; set; } = [];
}
}

View File

@@ -0,0 +1,99 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF File Set Descriptor (4/14.1), the location of the root directory
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class FileSetDescriptor : Descriptor
{
/// <summary>
/// Recording date and time
/// </summary>
public Timestamp RecordingDateAndTime { get; set; } = new();
/// <summary>
/// Interchange level
/// </summary>
public ushort InterchangeLevel { get; set; }
/// <summary>
/// Maximum interchange level
/// </summary>
public ushort MaximumInterchangeLevel { get; set; }
/// <summary>
/// Character set list
/// </summary>
public uint CharacterSetList { get; set; }
/// <summary>
/// Maximum character set list
/// </summary>
public uint MaximumCharacterSetList { get; set; }
/// <summary>
/// File set number
/// </summary>
public uint FileSetNumber { get; set; }
/// <summary>
/// File Set Descriptor number
/// </summary>
public uint FileSetDescriptorNumber { get; set; }
/// <summary>
/// Logical volume identifier character set
/// </summary>
public CharacterSetSpecification LogicalVolumeIdentifierCharacterSet { get; set; } = new();
/// <summary>
/// Logical volume identifier, dstring (last byte = length)
/// </summary>
public byte[] LogicalVolumeIdentifier { get; set; } = new byte[128];
/// <summary>
/// File set character set
/// </summary>
public CharacterSetSpecification FileSetCharacterSet { get; set; } = new();
/// <summary>
/// File set identifier, dstring (last byte = length)
/// </summary>
public byte[] FileSetIdentifier { get; set; } = new byte[32];
/// <summary>
/// Copyright file identifier, dstring (last byte = length)
/// </summary>
public byte[] CopyrightFileIdentifier { get; set; } = new byte[32];
/// <summary>
/// Abstract file identifier, dstring (last byte = length)
/// </summary>
public byte[] AbstractFileIdentifier { get; set; } = new byte[32];
/// <summary>
/// ICB of the root directory
/// </summary>
public LongAllocationDescriptor RootDirectoryICB { get; set; } = new();
/// <summary>
/// Domain identifier
/// </summary>
public EntityIdentifier DomainIdentifier { get; set; } = new();
/// <summary>
/// Next extent of File Set Descriptors, length 0 if none
/// </summary>
public LongAllocationDescriptor NextExtent { get; set; } = new();
/// <summary>
/// ICB of the system stream directory (ECMA-167 3rd edition), length 0 if none
/// </summary>
public LongAllocationDescriptor SystemStreamDirectoryICB { get; set; } = new();
/// <summary>
/// Reserved, 32 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[32];
}
}

View File

@@ -0,0 +1,15 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF tagged descriptor of a type that is not parsed further
/// (e.g. Implementation Use Volume Descriptor, Unallocated Space Descriptor, Terminating Descriptor)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class GenericDescriptor : Descriptor
{
/// <summary>
/// Contents of the descriptor after the tag, up to the end of the logical sector
/// </summary>
public byte[] Data { get; set; } = [];
}
}

View File

@@ -0,0 +1,49 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Information Control Block Tag (icbtag, 4/14.6)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ICBTag
{
/// <summary>
/// Prior recorded number of direct entries
/// </summary>
public uint PriorRecordedNumberOfDirectEntries { get; set; }
/// <summary>
/// Strategy type (UDF: 4, or 4096 for write-once media)
/// </summary>
public ushort StrategyType { get; set; }
/// <summary>
/// Strategy parameter, 2 bytes
/// </summary>
public byte[] StrategyParameter { get; set; } = new byte[2];
/// <summary>
/// Maximum number of entries
/// </summary>
public ushort MaximumNumberOfEntries { get; set; }
/// <summary>
/// Reserved, 0x00
/// </summary>
public byte Reserved { get; set; }
/// <summary>
/// File type
/// </summary>
public FileType FileType { get; set; }
/// <summary>
/// Parent ICB location
/// </summary>
public LogicalBlockAddress ParentICBLocation { get; set; } = new();
/// <summary>
/// Flags, the lower 3 bits hold the AllocationDescriptorType
/// </summary>
public ushort Flags { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Recorded Address (lb_addr, 4/7.1), a logical block within a partition
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LogicalBlockAddress
{
/// <summary>
/// Logical block number, relative to the start of the partition
/// </summary>
public uint LogicalBlockNumber { get; set; }
/// <summary>
/// Index of the partition map in the Logical Volume Descriptor
/// </summary>
public ushort PartitionReferenceNumber { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Logical Volume Descriptor (3/10.6), block size, partition maps and the location of the File Set Descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LogicalVolumeDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Descriptor character set
/// </summary>
public CharacterSetSpecification DescriptorCharacterSet { get; set; } = new();
/// <summary>
/// Logical volume identifier, dstring (last byte = length)
/// </summary>
public byte[] LogicalVolumeIdentifier { get; set; } = new byte[128];
/// <summary>
/// Size of a logical block in bytes
/// </summary>
public uint LogicalBlockSize { get; set; }
/// <summary>
/// Domain identifier ("*OSTA UDF Compliant", suffix holds the UDF revision)
/// </summary>
public EntityIdentifier DomainIdentifier { get; set; } = new();
/// <summary>
/// Logical volume contents use (UDF: extent of the File Set Descriptor)
/// </summary>
public LongAllocationDescriptor LogicalVolumeContentsUse { get; set; } = new();
/// <summary>
/// Length of the partition maps in bytes
/// </summary>
public uint MapTableLength { get; set; }
/// <summary>
/// Number of partition maps
/// </summary>
public uint NumberOfPartitionMaps { get; set; }
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 128 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[128];
/// <summary>
/// Extent of the Logical Volume Integrity Sequence
/// </summary>
public ExtentDescriptor IntegritySequenceExtent { get; set; } = new();
/// <summary>
/// Partition maps, a partition reference number is the index into this array
/// </summary>
public PartitionMap[] PartitionMaps { get; set; } = [];
}
}

View File

@@ -0,0 +1,25 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Long Allocation Descriptor (long_ad, 4/14.14.2), an extent within any partition of the logical volume
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class LongAllocationDescriptor
{
/// <summary>
/// Length of the extent in bytes (lower 30 bits) and the ExtentType (upper 2 bits)
/// A length of 0 terminates a sequence of allocation descriptors
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical block address of the start of the extent
/// </summary>
public LogicalBlockAddress ExtentLocation { get; set; } = new();
/// <summary>
/// Implementation use (UDF: flags and unique ID bits)
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[6];
}
}

View File

@@ -0,0 +1,45 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Metadata Partition Map (UDF 2.2.10, UDF 2.50 and later), a partition whose logical blocks
/// are the contents of the Metadata File recorded in the underlying partition
/// </summary>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public sealed class MetadataPartitionMap : Type2PartitionMap
{
/// <summary>
/// Logical block of the Metadata File's File Entry in the underlying partition
/// </summary>
public uint MetadataFileLocation { get; set; }
/// <summary>
/// Logical block of the Metadata Mirror File's File Entry in the underlying partition
/// </summary>
public uint MetadataMirrorFileLocation { get; set; }
/// <summary>
/// Logical block of the Metadata Bitmap File's File Entry, 0xFFFFFFFF if not recorded
/// </summary>
public uint MetadataBitmapFileLocation { get; set; }
/// <summary>
/// Allocation unit size in logical blocks
/// </summary>
public uint AllocationUnitSize { get; set; }
/// <summary>
/// Alignment unit size in logical blocks
/// </summary>
public ushort AlignmentUnitSize { get; set; }
/// <summary>
/// Flags (bit 0: duplicate metadata flag)
/// </summary>
public byte Flags { get; set; }
/// <summary>
/// Reserved, 5 bytes of 0x00
/// </summary>
public byte[] Reserved2 { get; set; } = new byte[5];
}
}

View File

@@ -0,0 +1,64 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Partition Descriptor (3/10.5), the physical location of a partition
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class PartitionDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Partition flags (bit 0: space is allocated)
/// </summary>
public ushort PartitionFlags { get; set; }
/// <summary>
/// Partition number, referenced by the partition maps
/// </summary>
public ushort PartitionNumber { get; set; }
/// <summary>
/// Partition contents ("+NSR02" or "+NSR03" for UDF)
/// </summary>
public EntityIdentifier PartitionContents { get; set; } = new();
/// <summary>
/// Partition contents use (Partition Header Descriptor for NSR), 128 bytes
/// </summary>
public byte[] PartitionContentsUse { get; set; } = new byte[128];
/// <summary>
/// Access type (1 = read-only, 2 = write-once, 3 = rewritable, 4 = overwritable)
/// </summary>
public uint AccessType { get; set; }
/// <summary>
/// Logical sector number of the first sector of the partition
/// </summary>
public uint PartitionStartingLocation { get; set; }
/// <summary>
/// Length of the partition in logical sectors
/// </summary>
public uint PartitionLength { get; set; }
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 128 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[128];
/// <summary>
/// Reserved, 156 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[156];
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// Abstract UDF Partition Map (3/10.7)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public abstract class PartitionMap
{
/// <summary>
/// Partition map type (1 or 2)
/// </summary>
public byte PartitionMapType { get; set; }
/// <summary>
/// Length of the partition map in bytes
/// </summary>
public byte PartitionMapLength { get; set; }
}
}

View File

@@ -0,0 +1,114 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Primary Volume Descriptor (3/10.1)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class PrimaryVolumeDescriptor : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number, the highest number prevails
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Primary Volume Descriptor Number
/// </summary>
public uint PrimaryVolumeDescriptorNumber { get; set; }
/// <summary>
/// Volume identifier, dstring (last byte = length)
/// </summary>
public byte[] VolumeIdentifier { get; set; } = new byte[32];
/// <summary>
/// Volume sequence number
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Maximum volume sequence number
/// </summary>
public ushort MaximumVolumeSequenceNumber { get; set; }
/// <summary>
/// Interchange level
/// </summary>
public ushort InterchangeLevel { get; set; }
/// <summary>
/// Maximum interchange level
/// </summary>
public ushort MaximumInterchangeLevel { get; set; }
/// <summary>
/// Character set list
/// </summary>
public uint CharacterSetList { get; set; }
/// <summary>
/// Maximum character set list
/// </summary>
public uint MaximumCharacterSetList { get; set; }
/// <summary>
/// Volume set identifier, dstring (last byte = length)
/// </summary>
public byte[] VolumeSetIdentifier { get; set; } = new byte[128];
/// <summary>
/// Descriptor character set
/// </summary>
public CharacterSetSpecification DescriptorCharacterSet { get; set; } = new();
/// <summary>
/// Explanatory character set
/// </summary>
public CharacterSetSpecification ExplanatoryCharacterSet { get; set; } = new();
/// <summary>
/// Extent of the volume abstract
/// </summary>
public ExtentDescriptor VolumeAbstract { get; set; } = new();
/// <summary>
/// Extent of the volume copyright notice
/// </summary>
public ExtentDescriptor VolumeCopyrightNotice { get; set; } = new();
/// <summary>
/// Application identifier
/// </summary>
public EntityIdentifier ApplicationIdentifier { get; set; } = new();
/// <summary>
/// Recording date and time
/// </summary>
public Timestamp RecordingDateAndTime { get; set; } = new();
/// <summary>
/// Implementation identifier
/// </summary>
public EntityIdentifier ImplementationIdentifier { get; set; } = new();
/// <summary>
/// Implementation use, 64 bytes
/// </summary>
public byte[] ImplementationUse { get; set; } = new byte[64];
/// <summary>
/// Predecessor Volume Descriptor Sequence location
/// </summary>
public uint PredecessorVolumeDescriptorSequenceLocation { get; set; }
/// <summary>
/// Flags (bit 0: volume set identification is common to all volumes of the set)
/// </summary>
public ushort Flags { get; set; }
/// <summary>
/// Reserved, 22 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[22];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Short Allocation Descriptor (short_ad, 4/14.14.1), an extent within the partition of the descriptor
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class ShortAllocationDescriptor
{
/// <summary>
/// Length of the extent in bytes (lower 30 bits) and the ExtentType (upper 2 bits)
/// A length of 0 terminates a sequence of allocation descriptors
/// </summary>
public uint ExtentLength { get; set; }
/// <summary>
/// Logical block number of the start of the extent
/// </summary>
public uint ExtentPosition { get; set; }
}
}

View File

@@ -0,0 +1,60 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Timestamp (1/7.3)
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class Timestamp
{
/// <summary>
/// Type (upper 4 bits, 1 = local time) and time zone offset from UTC in minutes
/// (lower 12 bits, signed, -2047 = not specified)
/// </summary>
public ushort TypeAndTimezone { get; set; }
/// <summary>
/// Year, 1 to 9999
/// </summary>
public short Year { get; set; }
/// <summary>
/// Month, 1 to 12
/// </summary>
public byte Month { get; set; }
/// <summary>
/// Day, 1 to 31
/// </summary>
public byte Day { get; set; }
/// <summary>
/// Hour, 0 to 23
/// </summary>
public byte Hour { get; set; }
/// <summary>
/// Minute, 0 to 59
/// </summary>
public byte Minute { get; set; }
/// <summary>
/// Second, 0 to 59
/// </summary>
public byte Second { get; set; }
/// <summary>
/// Centiseconds, 0 to 99
/// </summary>
public byte Centiseconds { get; set; }
/// <summary>
/// Hundreds of microseconds, 0 to 99
/// </summary>
public byte HundredsOfMicroseconds { get; set; }
/// <summary>
/// Microseconds, 0 to 99
/// </summary>
public byte Microseconds { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Type 1 Partition Map (3/10.7.2), logical blocks map 1:1 onto a Partition Descriptor's sectors
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class Type1PartitionMap : PartitionMap
{
/// <summary>
/// Volume sequence number of the volume containing the partition
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Partition number of the Partition Descriptor
/// </summary>
public ushort PartitionNumber { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Type 2 Partition Map (3/10.7.3, UDF 2.2.8-2.2.10), a partition identified by its partition type identifier
/// (e.g. "*UDF Virtual Partition", "*UDF Sparable Partition", "*UDF Metadata Partition")
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public class Type2PartitionMap : PartitionMap
{
/// <summary>
/// Reserved, 2 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[2];
/// <summary>
/// Partition type identifier
/// </summary>
public EntityIdentifier PartitionTypeIdentifier { get; set; } = new();
/// <summary>
/// Volume sequence number of the volume containing the partition
/// </summary>
public ushort VolumeSequenceNumber { get; set; }
/// <summary>
/// Partition number of the underlying Partition Descriptor
/// </summary>
public ushort PartitionNumber { get; set; }
/// <summary>
/// Partition type specific data after the partition number, 24 bytes
/// Empty for partition types that are parsed further (MetadataPartitionMap)
/// </summary>
public byte[] PartitionTypeData { get; set; } = [];
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// A volume (or disc image) with a UDF / ECMA-167 file system
/// </summary>
/// <remarks>
/// Logical sectors and logical blocks are 2048 bytes on optical media
/// File system structures are addressed by logical block address (partition reference number + logical block);
/// dictionaries are keyed by ((long)PartitionReferenceNumber &lt;&lt; 32) | LogicalBlockNumber
/// </remarks>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
/// <see href="http://www.osta.org/specs/pdf/udf260.pdf"/>
public sealed class Volume
{
/// <summary>
/// Volume Recognition Sequence starting at sector 16
/// </summary>
public VolumeStructureDescriptor[] VolumeRecognitionSequence { get; set; } = [];
/// <summary>
/// Anchor Volume Descriptor Pointer
/// </summary>
public AnchorVolumeDescriptorPointer? AnchorVolumeDescriptorPointer { get; set; }
/// <summary>
/// Volume Descriptor Sequence (Primary, Partition, Logical Volume, ... up to the Terminating Descriptor)
/// </summary>
public Descriptor[] VolumeDescriptorSequence { get; set; } = [];
/// <summary>
/// File Set Descriptor
/// </summary>
public FileSetDescriptor? FileSetDescriptor { get; set; }
/// <summary>
/// File Entries (and Extended File Entries) by logical block address
/// </summary>
public Dictionary<long, FileEntry> FileEntries { get; set; } = [];
/// <summary>
/// Allocation Extent Descriptors by logical block address
/// </summary>
public Dictionary<long, AllocationExtentDescriptor> AllocationExtentDescriptors { get; set; } = [];
/// <summary>
/// File Identifier Descriptors of each directory, by logical block address of the directory's File Entry
/// </summary>
public Dictionary<long, FileIdentifierDescriptor[]> Directories { get; set; } = [];
}
}

View File

@@ -0,0 +1,24 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Volume Descriptor Pointer (3/10.3), continues the Volume Descriptor Sequence in another extent
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class VolumeDescriptorPointer : Descriptor
{
/// <summary>
/// Volume Descriptor Sequence Number
/// </summary>
public uint VolumeDescriptorSequenceNumber { get; set; }
/// <summary>
/// Extent of the next part of the Volume Descriptor Sequence
/// </summary>
public ExtentDescriptor NextVolumeDescriptorSequenceExtent { get; set; } = new();
/// <summary>
/// Reserved, 484 bytes of 0x00
/// </summary>
public byte[] Reserved { get; set; } = new byte[484];
}
}

View File

@@ -0,0 +1,30 @@
namespace SabreTools.Data.Models.UDF
{
/// <summary>
/// UDF Volume Structure Descriptor (2/9.1), one logical sector of the Volume Recognition Sequence
/// starting at sector 16, e.g. "BEA01", "NSR02"/"NSR03", "TEA01", or "CD001" on bridge discs
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf"/>
public sealed class VolumeStructureDescriptor
{
/// <summary>
/// Structure type
/// </summary>
public byte StructureType { get; set; }
/// <summary>
/// Standard identifier, 5 bytes
/// </summary>
public byte[] StandardIdentifier { get; set; } = new byte[5];
/// <summary>
/// Structure version
/// </summary>
public byte StructureVersion { get; set; }
/// <summary>
/// Structure data, the rest of the logical sector
/// </summary>
public byte[] StructureData { get; set; } = [];
}
}

View File

@@ -212,6 +212,7 @@ namespace SabreTools.Metadata.DatFiles.Test
DatVersion = "datversion", DatVersion = "datversion",
Debug = true, Debug = true,
Description = "description", Description = "description",
DirHandling = "noautodir",
Email = "email", Email = "email",
EmulatorVersion = "emulatorversion", EmulatorVersion = "emulatorversion",
FileName = "filename", FileName = "filename",
@@ -243,6 +244,7 @@ namespace SabreTools.Metadata.DatFiles.Test
ScreenshotsHeight = "screenshotsheight", ScreenshotsHeight = "screenshotsheight",
ScreenshotsWidth = "screenshotsWidth", ScreenshotsWidth = "screenshotsWidth",
Search = search, Search = search,
Subset = "subset",
System = "system", System = "system",
Timestamp = "timestamp", Timestamp = "timestamp",
Type = "type", Type = "type",
@@ -327,6 +329,7 @@ namespace SabreTools.Metadata.DatFiles.Test
SaveType = "savetype", SaveType = "savetype",
Score = "score", Score = "score",
Serial = "serial", Serial = "serial",
SetType = "dir",
SharedFeat = [CreateMetadataSharedFeat()], SharedFeat = [CreateMetadataSharedFeat()],
Slot = [CreateMetadataSlot()], Slot = [CreateMetadataSlot()],
SoftwareList = [CreateMetadataSoftwareList()], SoftwareList = [CreateMetadataSoftwareList()],
@@ -778,6 +781,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Bios = "bios", Bios = "bios",
Bitrate = "bitrate", Bitrate = "bitrate",
BitTorrentMagnetHash = "btih", BitTorrentMagnetHash = "btih",
BLAKE3 = HashType.BLAKE3.ZeroString,
ClothCoverDetectionModuleVersion = "cloth_cover_detection_module_version", ClothCoverDetectionModuleVersion = "cloth_cover_detection_module_version",
CollectionCatalogNumber = "collection-catalog-number", CollectionCatalogNumber = "collection-catalog-number",
Comment = "comment", Comment = "comment",
@@ -835,6 +839,7 @@ namespace SabreTools.Metadata.DatFiles.Test
SpamSum = HashType.SpamSum.ZeroString, SpamSum = HashType.SpamSum.ZeroString,
Start = "start", Start = "start",
Status = Data.Models.Metadata.ItemStatus.Good, Status = Data.Models.Metadata.ItemStatus.Good,
StatusMisc = "status",
Summation = "summation", Summation = "summation",
TesseractOCR = "ocr", TesseractOCR = "ocr",
TesseractOCRConverted = "ocr_converted", TesseractOCRConverted = "ocr_converted",
@@ -1004,6 +1009,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("datversion", datHeader.DatVersion); Assert.Equal("datversion", datHeader.DatVersion);
Assert.True(datHeader.Debug); Assert.True(datHeader.Debug);
Assert.Equal("description", datHeader.Description); Assert.Equal("description", datHeader.Description);
Assert.Equal("noautodir", datHeader.DirHandling);
Assert.Equal("email", datHeader.Email); Assert.Equal("email", datHeader.Email);
Assert.Equal("emulatorversion", datHeader.EmulatorVersion); Assert.Equal("emulatorversion", datHeader.EmulatorVersion);
Assert.Equal(Data.Models.Metadata.MergingFlag.Merged, datHeader.ForceMerging); Assert.Equal(Data.Models.Metadata.MergingFlag.Merged, datHeader.ForceMerging);
@@ -1033,6 +1039,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("screenshotsheight", datHeader.ScreenshotsHeight); Assert.Equal("screenshotsheight", datHeader.ScreenshotsHeight);
Assert.Equal("screenshotsWidth", datHeader.ScreenshotsWidth); Assert.Equal("screenshotsWidth", datHeader.ScreenshotsWidth);
Assert.NotNull(datHeader.Search); Assert.NotNull(datHeader.Search);
Assert.Equal("subset", datHeader.Subset);
Assert.Equal("system", datHeader.System); Assert.Equal("system", datHeader.System);
Assert.Equal("timestamp", datHeader.Timestamp); Assert.Equal("timestamp", datHeader.Timestamp);
Assert.Equal("type", datHeader.Type); Assert.Equal("type", datHeader.Type);
@@ -1090,6 +1097,8 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("sampleof", machine.SampleOf); Assert.Equal("sampleof", machine.SampleOf);
Assert.Equal("savetype", machine.SaveType); Assert.Equal("savetype", machine.SaveType);
Assert.Equal("score", machine.Score); Assert.Equal("score", machine.Score);
Assert.Equal("serial", machine.Serial);
Assert.Equal("dir", machine.SetType);
Assert.Equal("source", machine.Source); Assert.Equal("source", machine.Source);
Assert.Equal("sourcefile", machine.SourceFile); Assert.Equal("sourcefile", machine.SourceFile);
Assert.Equal("sourcerom", machine.SourceRom); Assert.Equal("sourcerom", machine.SourceRom);
@@ -1450,6 +1459,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("bios", rom.Bios); Assert.Equal("bios", rom.Bios);
Assert.Equal("bitrate", rom.Bitrate); Assert.Equal("bitrate", rom.Bitrate);
Assert.Equal("btih", rom.BitTorrentMagnetHash); Assert.Equal("btih", rom.BitTorrentMagnetHash);
Assert.Equal(HashType.BLAKE3.ZeroString, rom.BLAKE3);
Assert.Equal("cloth_cover_detection_module_version", rom.ClothCoverDetectionModuleVersion); Assert.Equal("cloth_cover_detection_module_version", rom.ClothCoverDetectionModuleVersion);
Assert.Equal("collection-catalog-number", rom.CollectionCatalogNumber); Assert.Equal("collection-catalog-number", rom.CollectionCatalogNumber);
Assert.Equal("comment", rom.Comment); Assert.Equal("comment", rom.Comment);
@@ -1507,6 +1517,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum); Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum);
Assert.Equal("start", rom.Start); Assert.Equal("start", rom.Start);
Assert.Equal(Data.Models.Metadata.ItemStatus.Good, rom.Status); Assert.Equal(Data.Models.Metadata.ItemStatus.Good, rom.Status);
Assert.Equal("status", rom.StatusMisc);
Assert.Equal("summation", rom.Summation); Assert.Equal("summation", rom.Summation);
Assert.Equal("ocr", rom.TesseractOCR); Assert.Equal("ocr", rom.TesseractOCR);
Assert.Equal("ocr_converted", rom.TesseractOCRConverted); Assert.Equal("ocr_converted", rom.TesseractOCRConverted);

View File

@@ -344,6 +344,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("datversion", header.DatVersion); Assert.Equal("datversion", header.DatVersion);
Assert.True(header.Debug); Assert.True(header.Debug);
Assert.Equal("description", header.Description); Assert.Equal("description", header.Description);
Assert.Equal("noautodir", header.DirHandling);
Assert.Equal("email", header.Email); Assert.Equal("email", header.Email);
Assert.Equal("emulatorversion", header.EmulatorVersion); Assert.Equal("emulatorversion", header.EmulatorVersion);
Assert.Equal("filename", header.FileName); Assert.Equal("filename", header.FileName);
@@ -374,6 +375,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("screenshotsheight", header.ScreenshotsHeight); Assert.Equal("screenshotsheight", header.ScreenshotsHeight);
Assert.Equal("screenshotsWidth", header.ScreenshotsWidth); Assert.Equal("screenshotsWidth", header.ScreenshotsWidth);
Assert.NotNull(header.Search); Assert.NotNull(header.Search);
Assert.Equal("subset", header.Subset);
Assert.Equal("system", header.System); Assert.Equal("system", header.System);
Assert.Equal("timestamp", header.Timestamp); Assert.Equal("timestamp", header.Timestamp);
Assert.Equal("type", header.Type); Assert.Equal("type", header.Type);
@@ -436,6 +438,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("savetype", machine.SaveType); Assert.Equal("savetype", machine.SaveType);
Assert.Equal("score", machine.Score); Assert.Equal("score", machine.Score);
Assert.Equal("serial", machine.Serial); Assert.Equal("serial", machine.Serial);
Assert.Equal("dir", machine.SetType);
Assert.Equal("source", machine.Source); Assert.Equal("source", machine.Source);
Assert.Equal("sourcefile", machine.SourceFile); Assert.Equal("sourcefile", machine.SourceFile);
Assert.Equal("sourcerom", machine.SourceRom); Assert.Equal("sourcerom", machine.SourceRom);
@@ -1005,6 +1008,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("bios", rom.Bios); Assert.Equal("bios", rom.Bios);
Assert.Equal("bitrate", rom.Bitrate); Assert.Equal("bitrate", rom.Bitrate);
Assert.Equal("btih", rom.BitTorrentMagnetHash); Assert.Equal("btih", rom.BitTorrentMagnetHash);
Assert.Equal(HashType.BLAKE3.ZeroString, rom.BLAKE3);
Assert.Equal("cloth_cover_detection_module_version", rom.ClothCoverDetectionModuleVersion); Assert.Equal("cloth_cover_detection_module_version", rom.ClothCoverDetectionModuleVersion);
Assert.Equal("collection-catalog-number", rom.CollectionCatalogNumber); Assert.Equal("collection-catalog-number", rom.CollectionCatalogNumber);
Assert.Equal("comment", rom.Comment); Assert.Equal("comment", rom.Comment);
@@ -1062,6 +1066,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum); Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum);
Assert.Equal("start", rom.Start); Assert.Equal("start", rom.Start);
Assert.Equal(Data.Models.Metadata.ItemStatus.Good, rom.Status); Assert.Equal(Data.Models.Metadata.ItemStatus.Good, rom.Status);
Assert.Equal("status", rom.StatusMisc);
Assert.Equal("summation", rom.Summation); Assert.Equal("summation", rom.Summation);
Assert.Equal("ocr", rom.TesseractOCR); Assert.Equal("ocr", rom.TesseractOCR);
Assert.Equal("ocr_converted", rom.TesseractOCRConverted); Assert.Equal("ocr_converted", rom.TesseractOCRConverted);

View File

@@ -1396,7 +1396,8 @@ namespace SabreTools.Metadata.DatFiles.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
string actual = DatFile.FormatPrefixPostfix(item, machine, fix); string actual = DatFile.FormatPrefixPostfix(item, machine, fix);
@@ -1514,7 +1515,8 @@ namespace SabreTools.Metadata.DatFiles.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
string actual = DatFile.FormatPrefixPostfix(item, machine, fix); string actual = DatFile.FormatPrefixPostfix(item, machine, fix);
@@ -1550,6 +1552,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Null(rom.SHA384); Assert.Null(rom.SHA384);
Assert.Null(rom.SHA512); Assert.Null(rom.SHA512);
Assert.Null(rom.SpamSum); Assert.Null(rom.SpamSum);
Assert.Null(rom.BLAKE3);
} }
[Fact] [Fact]
@@ -1575,6 +1578,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Null(rom.SHA384); Assert.Null(rom.SHA384);
Assert.Null(rom.SHA512); Assert.Null(rom.SHA512);
Assert.Null(rom.SpamSum); Assert.Null(rom.SpamSum);
Assert.Null(rom.BLAKE3);
} }
[Fact] [Fact]
@@ -1594,7 +1598,8 @@ namespace SabreTools.Metadata.DatFiles.Test
SHA256 = "null", SHA256 = "null",
SHA384 = "null", SHA384 = "null",
SHA512 = "null", SHA512 = "null",
SpamSum = "null" SpamSum = "null",
BLAKE3 = "null",
}; };
DatItem actual = DatFile.ProcessNullifiedItem(item); DatItem actual = DatFile.ProcessNullifiedItem(item);
@@ -1614,6 +1619,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(HashType.SHA384.ZeroString, rom.SHA384); Assert.Equal(HashType.SHA384.ZeroString, rom.SHA384);
Assert.Equal(HashType.SHA512.ZeroString, rom.SHA512); Assert.Equal(HashType.SHA512.ZeroString, rom.SHA512);
Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum); Assert.Equal(HashType.SpamSum.ZeroString, rom.SpamSum);
Assert.Equal(HashType.BLAKE3.ZeroString, rom.BLAKE3);
} }
#endregion #endregion
@@ -1955,6 +1961,19 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal($"_{hash}", actual); Assert.Equal($"_{hash}", actual);
} }
[Fact]
public void GetDuplicateSuffix_Rom_BLAKE3()
{
string hash = "hash";
DatItem datItem = new Rom
{
BLAKE3 = hash
};
string actual = DatFile.GetDuplicateSuffix(datItem);
Assert.Equal($"_{hash}", actual);
}
[Fact] [Fact]
public void GetDuplicateSuffix_Other_Generic() public void GetDuplicateSuffix_Other_Generic()
{ {
@@ -2434,7 +2453,8 @@ namespace SabreTools.Metadata.DatFiles.Test
SHA256 = "crc", SHA256 = "crc",
SHA384 = "crc", SHA384 = "crc",
SHA512 = "crc", SHA512 = "crc",
SpamSum = "crc" SpamSum = "crc",
BLAKE3 = "crc",
}; };
DatFile datFile = new Logiqx(null, useGame: false); DatFile datFile = new Logiqx(null, useGame: false);

View File

@@ -60,6 +60,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(0, stats.GetHashCount(HashType.SHA384)); Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512)); Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -88,6 +89,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(1, stats.GetHashCount(HashType.SHA384)); Assert.Equal(1, stats.GetHashCount(HashType.SHA384));
Assert.Equal(1, stats.GetHashCount(HashType.SHA512)); Assert.Equal(1, stats.GetHashCount(HashType.SHA512));
Assert.Equal(2, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(2, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -116,6 +118,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(0, stats.GetHashCount(HashType.SHA384)); Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512)); Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -151,6 +154,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(0, stats.GetHashCount(HashType.SHA384)); Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512)); Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -179,6 +183,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(1, stats.GetHashCount(HashType.SHA384)); Assert.Equal(1, stats.GetHashCount(HashType.SHA384));
Assert.Equal(1, stats.GetHashCount(HashType.SHA512)); Assert.Equal(1, stats.GetHashCount(HashType.SHA512));
Assert.Equal(2, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(2, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(1, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -203,6 +208,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(0, stats.GetHashCount(HashType.SHA384)); Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512)); Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum)); Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetHashCount(HashType.BLAKE3));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.File));
Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media)); Assert.Equal(0, stats.GetItemCount(Data.Models.Metadata.ItemType.Media));
@@ -238,6 +244,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal(1, newStats.GetHashCount(HashType.SHA384)); Assert.Equal(1, newStats.GetHashCount(HashType.SHA384));
Assert.Equal(1, newStats.GetHashCount(HashType.SHA512)); Assert.Equal(1, newStats.GetHashCount(HashType.SHA512));
Assert.Equal(1, newStats.GetHashCount(HashType.SpamSum)); Assert.Equal(1, newStats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, newStats.GetHashCount(HashType.BLAKE3));
Assert.Equal(1, newStats.GetItemCount(Data.Models.Metadata.ItemType.Rom)); Assert.Equal(1, newStats.GetItemCount(Data.Models.Metadata.ItemType.Rom));
Assert.Equal(1, newStats.GetStatusCount(ItemStatus.Good)); Assert.Equal(1, newStats.GetStatusCount(ItemStatus.Good));
} }
@@ -317,6 +324,7 @@ namespace SabreTools.Metadata.DatFiles.Test
rom.SHA384 = HashType.SHA384.ZeroString; rom.SHA384 = HashType.SHA384.ZeroString;
rom.SHA512 = HashType.SHA512.ZeroString; rom.SHA512 = HashType.SHA512.ZeroString;
rom.SpamSum = HashType.SpamSum.ZeroString; rom.SpamSum = HashType.SpamSum.ZeroString;
rom.BLAKE3 = HashType.BLAKE3.ZeroString;
return rom; return rom;
} }

View File

@@ -700,6 +700,31 @@ namespace SabreTools.Metadata.DatFiles.Test
])); ]));
} }
[Fact]
public void Blake3File_SupportedTypes()
{
var datFile = new Blake3File(null);
var actual = datFile.SupportedTypes;
Assert.True(actual.SequenceEqual([
Data.Models.Metadata.ItemType.Rom,
]));
}
[Fact]
public void Blake3File_GetMissingRequiredFields_Rom()
{
var datItem = new Rom();
var datFile = new Blake3File(null);
var actual = datFile.GetMissingRequiredFields(datItem);
Assert.NotNull(actual);
Assert.True(actual.SequenceEqual([
nameof(Data.Models.Metadata.Rom.Name),
nameof(Data.Models.Metadata.Rom.BLAKE3),
]));
}
#endregion #endregion
#region Listrom #region Listrom

View File

@@ -1,18 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -104,6 +104,8 @@ namespace SabreTools.Metadata.DatFiles
Header.Debug = header.Debug; Header.Debug = header.Debug;
if (Header.Description is null) if (Header.Description is null)
Header.Description = header.Description; Header.Description = header.Description;
if (Header.DirHandling is null)
Header.DirHandling = header.DirHandling;
if (Header.Email is null) if (Header.Email is null)
Header.Email = header.Email; Header.Email = header.Email;
if (Header.EmulatorVersion is null) if (Header.EmulatorVersion is null)
@@ -156,6 +158,8 @@ namespace SabreTools.Metadata.DatFiles
Header.ScreenshotsWidth = header.ScreenshotsWidth; Header.ScreenshotsWidth = header.ScreenshotsWidth;
if (Header.System is null) if (Header.System is null)
Header.System = header.System; Header.System = header.System;
if (Header.Subset is null)
Header.Subset = header.Subset;
if (Header.Timestamp is null) if (Header.Timestamp is null)
Header.Timestamp = header.Timestamp; Header.Timestamp = header.Timestamp;
if (Header.Type is null) if (Header.Type is null)

View File

@@ -593,7 +593,8 @@ namespace SabreTools.Metadata.DatFiles
sha384 = string.Empty, sha384 = string.Empty,
sha512 = string.Empty, sha512 = string.Empty,
size = string.Empty, size = string.Empty,
spamsum = string.Empty; spamsum = string.Empty,
blake3 = string.Empty;
// Ensure we have the proper values for replacement // Ensure we have the proper values for replacement
if (item is Disk disk) if (item is Disk disk)
@@ -633,6 +634,7 @@ namespace SabreTools.Metadata.DatFiles
sha512 = rom.SHA512 ?? string.Empty; sha512 = rom.SHA512 ?? string.Empty;
size = rom.Size.ToString() ?? string.Empty; size = rom.Size.ToString() ?? string.Empty;
spamsum = rom.SpamSum ?? string.Empty; spamsum = rom.SpamSum ?? string.Empty;
blake3 = rom.BLAKE3 ?? string.Empty;
} }
// Now do bulk replacement where possible // Now do bulk replacement where possible
@@ -657,7 +659,8 @@ namespace SabreTools.Metadata.DatFiles
.Replace("%sha384%", sha384) .Replace("%sha384%", sha384)
.Replace("%sha512%", sha512) .Replace("%sha512%", sha512)
.Replace("%size%", size) .Replace("%size%", size)
.Replace("%spamsum%", spamsum); .Replace("%spamsum%", spamsum)
.Replace("%blake3%", blake3);
return fix; return fix;
} }
@@ -681,6 +684,14 @@ namespace SabreTools.Metadata.DatFiles
if (rom.CRC32 != "null") if (rom.CRC32 != "null")
return rom; return rom;
// If the item has a SHA-1 but no size
if (rom.Size is null && rom.SHA1 is not null && rom.SHA1 != "null")
return rom;
// If the item has a SHA-256 but no size
if (rom.Size is null && rom.SHA256 is not null && rom.SHA256 != "null")
return rom;
// If the Rom has "null" characteristics, ensure all fields // If the Rom has "null" characteristics, ensure all fields
rom.Name = rom.Name == "null" ? "-" : rom.Name; rom.Name = rom.Name == "null" ? "-" : rom.Name;
rom.Size = 0; rom.Size = 0;
@@ -697,6 +708,7 @@ namespace SabreTools.Metadata.DatFiles
rom.SHA384 = rom.SHA384 == "null" ? HashType.SHA384.ZeroString : null; rom.SHA384 = rom.SHA384 == "null" ? HashType.SHA384.ZeroString : null;
rom.SHA512 = rom.SHA512 == "null" ? HashType.SHA512.ZeroString : null; rom.SHA512 = rom.SHA512 == "null" ? HashType.SHA512.ZeroString : null;
rom.SpamSum = rom.SpamSum == "null" ? HashType.SpamSum.ZeroString : null; rom.SpamSum = rom.SpamSum == "null" ? HashType.SpamSum.ZeroString : null;
rom.BLAKE3 = rom.BLAKE3 == "null" ? HashType.BLAKE3.ZeroString : null;
return rom; return rom;
} }
@@ -979,7 +991,7 @@ namespace SabreTools.Metadata.DatFiles
{ {
// If we have a 0-size or blank rom, then we ignore // If we have a 0-size or blank rom, then we ignore
long? size = rom.Size; long? size = rom.Size;
if (size == 0 || size is null) if ((size == 0 || size is null) && rom.SHA1 is null && rom.SHA256 is null)
{ {
string itemString = JsonConvert.SerializeObject(datItem, Formatting.None); string itemString = JsonConvert.SerializeObject(datItem, Formatting.None);
_logger.Verbose($"Item '{itemString}' was skipped because it had an invalid size"); _logger.Verbose($"Item '{itemString}' was skipped because it had an invalid size");
@@ -1127,6 +1139,10 @@ namespace SabreTools.Metadata.DatFiles
if (!string.IsNullOrEmpty(spamSum)) if (!string.IsNullOrEmpty(spamSum))
return $"_{spamSum}"; return $"_{spamSum}";
string? blake3 = datItem.BLAKE3;
if (!string.IsNullOrEmpty(blake3))
return $"_{blake3}";
return "_1"; return "_1";
} }

View File

@@ -84,6 +84,12 @@ namespace SabreTools.Metadata.DatFiles
set => _internal.Description = value; set => _internal.Description = value;
} }
public string? DirHandling
{
get => _internal.DirHandling;
set => _internal.DirHandling = value;
}
public string? Email public string? Email
{ {
get => _internal.Email; get => _internal.Email;
@@ -282,6 +288,12 @@ namespace SabreTools.Metadata.DatFiles
[JsonIgnore] [JsonIgnore]
public bool SearchSpecified => Search is not null; public bool SearchSpecified => Search is not null;
public string? Subset
{
get => _internal.Subset;
set => _internal.Subset = value;
}
public string? System public string? System
{ {
get => _internal.System; get => _internal.System;

View File

@@ -353,6 +353,12 @@ namespace SabreTools.Metadata.DatFiles
ModifyHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.SHA384) ? 0 : 1); ModifyHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.SHA384) ? 0 : 1);
ModifyHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.SHA512) ? 0 : 1); ModifyHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.SHA512) ? 0 : 1);
ModifyHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.SpamSum) ? 0 : 1); ModifyHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.SpamSum) ? 0 : 1);
#if NET7_0_OR_GREATER
ModifyHashCount(HashType.BLAKE3, string.IsNullOrEmpty(rom.BLAKE3) ? 0 : 1);
#else
// HACK because BLAKE3 is not supported below .NET 7
ModifyHashCount(HashType.CRC1_ZERO, string.IsNullOrEmpty(rom.BLAKE3) ? 0 : 1);
#endif
} }
ModifyStatusCount(ItemStatus.BadDump, status == ItemStatus.BadDump ? 1 : 0); ModifyStatusCount(ItemStatus.BadDump, status == ItemStatus.BadDump ? 1 : 0);
@@ -430,6 +436,12 @@ namespace SabreTools.Metadata.DatFiles
ModifyHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.SHA384) ? 0 : -1); ModifyHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.SHA384) ? 0 : -1);
ModifyHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.SHA512) ? 0 : -1); ModifyHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.SHA512) ? 0 : -1);
ModifyHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.SpamSum) ? 0 : -1); ModifyHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.SpamSum) ? 0 : -1);
#if NET7_0_OR_GREATER
ModifyHashCount(HashType.BLAKE3, string.IsNullOrEmpty(rom.BLAKE3) ? 0 : -1);
#else
// HACK because BLAKE3 is not supported below .NET 7
ModifyHashCount(HashType.CRC1_ZERO, string.IsNullOrEmpty(rom.BLAKE3) ? 0 : -1);
#endif
} }
ModifyStatusCount(ItemStatus.BadDump, status == ItemStatus.BadDump ? -1 : 0); ModifyStatusCount(ItemStatus.BadDump, status == ItemStatus.BadDump ? -1 : 0);

View File

@@ -169,6 +169,11 @@ namespace SabreTools.Metadata.DatFiles
/// </summary> /// </summary>
RedumpSpamSum, RedumpSpamSum,
/// <summary>
/// BLAKE3 hash list
/// </summary>
RedumpBLAKE3,
#endregion #endregion
} }
} }

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using SabreTools.Hashing;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents an BLAKE3 hashfile
/// </summary>
public sealed class Blake3File : Hashfile
{
/// <inheritdoc/>
public override Data.Models.Metadata.ItemType[] SupportedTypes
=> [
Data.Models.Metadata.ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Blake3File(DatFile? datFile) : base(datFile)
{
#if NET7_0_OR_GREATER
_hash = HashType.BLAKE3;
#else
// HACK because BLAKE3 is not supported below .NET 7
_hash = HashType.CRC1_ZERO;
#endif
Header.DatFormat = DatFormat.RedumpBLAKE3;
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.Name))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Name));
if (string.IsNullOrEmpty(rom.BLAKE3))
missingFields.Add(nameof(Data.Models.Metadata.Rom.BLAKE3));
break;
default:
// Item type is not supported
break;
}
return missingFields;
}
}
}

View File

@@ -88,9 +88,12 @@ namespace SabreTools.Metadata.DatFiles.Formats
case Rom rom: case Rom rom:
if (string.IsNullOrEmpty(rom.Name)) if (string.IsNullOrEmpty(rom.Name))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Name)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Name));
if (rom.Size is null || rom.Size < 0) if ((rom.Size is null || rom.Size < 0)
&& (rom.SHA1 is null || rom.SHA1 == "null")
&& (rom.SHA256 is null || rom.SHA1 == "null"))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Size)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Size));
if (string.IsNullOrEmpty(rom.CRC16) if (string.IsNullOrEmpty(rom.BLAKE3)
&& string.IsNullOrEmpty(rom.CRC16)
&& string.IsNullOrEmpty(rom.CRC32) && string.IsNullOrEmpty(rom.CRC32)
&& string.IsNullOrEmpty(rom.CRC64) && string.IsNullOrEmpty(rom.CRC64)
&& string.IsNullOrEmpty(rom.MD2) && string.IsNullOrEmpty(rom.MD2)

View File

@@ -282,9 +282,12 @@ namespace SabreTools.Metadata.DatFiles.Formats
case Rom rom: case Rom rom:
if (string.IsNullOrEmpty(rom.Name)) if (string.IsNullOrEmpty(rom.Name))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Name)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Name));
if (rom.Size is null || rom.Size < 0) if ((rom.Size is null || rom.Size < 0)
&& (rom.SHA1 is null || rom.SHA1 == "null")
&& (rom.SHA256 is null || rom.SHA1 == "null"))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Size)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Size));
if (string.IsNullOrEmpty(rom.CRC16) if (string.IsNullOrEmpty(rom.BLAKE3)
&& string.IsNullOrEmpty(rom.CRC16)
&& string.IsNullOrEmpty(rom.CRC32) && string.IsNullOrEmpty(rom.CRC32)
&& string.IsNullOrEmpty(rom.CRC64) && string.IsNullOrEmpty(rom.CRC64)
&& string.IsNullOrEmpty(rom.MD2) && string.IsNullOrEmpty(rom.MD2)

View File

@@ -29,17 +29,22 @@ namespace SabreTools.Metadata.DatFiles.Formats
<![CDATA[ <![CDATA[
The softwaredb.xml file contains information about rom mapper types The softwaredb.xml file contains information about rom mapper types
- Copyright 2003 Nicolas Beyaert (Initial Database) Copyright 2003 Nicolas Beyaert (Initial Database)
- Copyright 2004-2013 BlueMSX Team Copyright 2004-2013 BlueMSX Team
- Copyright 2005-2023 openMSX Team Copyright 2005-2026 openMSX Team
- Generation MSXIDs by www.generation-msx.nl
- Thanks go out to: Generation MSXIDs by www.generation-msx.nl
- - Generation MSX/Sylvester for the incredible source of information
- p_gimeno and diedel for their help adding and valdiating ROM additions Thanks go out to:
- Generation MSX / Sylvester for the incredible source of information
- p_gimeno and diedel for their help adding and validating ROM additions
- GDX for additional ROM info and validations and corrections - GDX for additional ROM info and validations and corrections
- TFH / Filehunter for his sharing of information
- MP83 for deep dives into software and corrections
- bsittler for preservation and sharing of information
Important: This file is auto generated using a database.
The Database is located at https://romdb.vampier.net/
]]>"; ]]>";
#endregion #endregion

View File

@@ -93,7 +93,9 @@ namespace SabreTools.Metadata.DatFiles.Formats
case Rom rom: case Rom rom:
if (string.IsNullOrEmpty(rom.Name)) if (string.IsNullOrEmpty(rom.Name))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Name)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Name));
if (rom.Size is null || rom.Size < 0) if ((rom.Size is null || rom.Size < 0)
&& (rom.SHA1 is null || rom.SHA1 == "null")
&& (rom.SHA256 is null || rom.SHA1 == "null"))
missingFields.Add(nameof(Data.Models.Metadata.Rom.Size)); missingFields.Add(nameof(Data.Models.Metadata.Rom.Size));
if (string.IsNullOrEmpty(rom.CRC32) if (string.IsNullOrEmpty(rom.CRC32)
&& string.IsNullOrEmpty(rom.MD5) && string.IsNullOrEmpty(rom.MD5)

View File

@@ -482,6 +482,7 @@ namespace SabreTools.Metadata.DatFiles
rom.SHA384 = null; // HashType.SHA384.ZeroString; rom.SHA384 = null; // HashType.SHA384.ZeroString;
rom.SHA512 = null; // HashType.SHA512.ZeroString; rom.SHA512 = null; // HashType.SHA512.ZeroString;
rom.SpamSum = null; // HashType.SpamSum.ZeroString; rom.SpamSum = null; // HashType.SpamSum.ZeroString;
rom.BLAKE3 = null; // HashType.BLAKE3.ZeroString;
} }
// If the file has no size and it's not the above case, skip and log // If the file has no size and it's not the above case, skip and log

View File

@@ -40,6 +40,15 @@ namespace SabreTools.Metadata.DatFiles
private readonly Dictionary<string, List<DatItem>?> _items = []; private readonly Dictionary<string, List<DatItem>?> _items = [];
#endif #endif
/// <summary>
/// Internal dictionary for the class
/// </summary>
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
private readonly ConcurrentDictionary<string, object> _itemsLocks = [];
#else
private readonly Dictionary<string, object> _itemsLocks = [];
#endif
/// <summary> /// <summary>
/// Logging object /// Logging object
/// </summary> /// </summary>
@@ -163,6 +172,7 @@ namespace SabreTools.Metadata.DatFiles
rom.SHA384 = null; // HashType.SHA384.ZeroString; rom.SHA384 = null; // HashType.SHA384.ZeroString;
rom.SHA512 = null; // HashType.SHA512.ZeroString; rom.SHA512 = null; // HashType.SHA512.ZeroString;
rom.SpamSum = null; // HashType.SpamSum.ZeroString; rom.SpamSum = null; // HashType.SpamSum.ZeroString;
rom.BLAKE3 = null; // HashType.BLAKE3.ZeroString;
} }
// If the file has no size and it's not the above case, skip and log // If the file has no size and it's not the above case, skip and log
@@ -190,7 +200,14 @@ namespace SabreTools.Metadata.DatFiles
// If only adding statistics, we add an empty key for games and then just item stats // If only adding statistics, we add an empty key for games and then just item stats
if (statsOnly) if (statsOnly)
{ {
EnsureBucketingKey(key); // Add an empty key
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
_items.GetOrAdd(key, []);
#else
if (!_items.ContainsKey(key))
_items[key] = [];
#endif
DatStatistics.AddItemStatistics(item); DatStatistics.AddItemStatistics(item);
} }
else else
@@ -293,7 +310,16 @@ namespace SabreTools.Metadata.DatFiles
public bool RemoveItem(string key, DatItem value, int index) public bool RemoveItem(string key, DatItem value, int index)
{ {
// Explicit lock for some weird corner cases // Explicit lock for some weird corner cases
lock (key) #if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
object lockObj = _itemsLocks.GetOrAdd(key, _ => new object());
#else
if (!_itemsLocks.ContainsKey(key))
_itemsLocks[key] = new object();
object lockObj = _itemsLocks[key];
#endif
lock (lockObj)
{ {
// If the key doesn't exist, return // If the key doesn't exist, return
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER #if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
@@ -337,10 +363,24 @@ namespace SabreTools.Metadata.DatFiles
internal void AddItem(string key, DatItem value) internal void AddItem(string key, DatItem value)
{ {
// Explicit lock for some weird corner cases // Explicit lock for some weird corner cases
lock (key) #if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
object lockObj = _itemsLocks.GetOrAdd(key, _ => new object());
#else
if (!_itemsLocks.ContainsKey(key))
_itemsLocks[key] = new object();
object lockObj = _itemsLocks[key];
#endif
lock (lockObj)
{ {
// Ensure the key exists // Ensure the key exists
EnsureBucketingKey(key); #if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
_items.GetOrAdd(key, []);
#else
if (!_items.ContainsKey(key))
_items[key] = [];
#endif
// If item is null, don't add it // If item is null, don't add it
if (value is null) if (value is null)
@@ -639,21 +679,6 @@ namespace SabreTools.Metadata.DatFiles
return roms.FindIndex(datItem.Equals) > -1; return roms.FindIndex(datItem.Equals) > -1;
} }
/// <summary>
/// Ensure the key exists in the items dictionary
/// </summary>
/// <param name="key">Key to ensure</param>
private void EnsureBucketingKey(string key)
{
// If the key is missing from the dictionary, add it
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
_items.GetOrAdd(key, []);
#else
if (!_items.ContainsKey(key))
_items[key] = [];
#endif
}
/// <summary> /// <summary>
/// Get the highest-order Field value that represents the statistics /// Get the highest-order Field value that represents the statistics
/// </summary> /// </summary>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -46,8 +46,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MinValueTupleBridge" Version="0.2.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" /> <PackageReference Include="MinValueTupleBridge" Version="0.2.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="$(TargetFramework.StartsWith(`net40`)) OR $(TargetFramework.StartsWith(`net45`))" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="$(TargetFramework.StartsWith(`net40`)) OR $(TargetFramework.StartsWith(`net45`))" />
<PackageReference Include="System.ValueTuple" Version="4.6.2" Condition="$(TargetFramework.StartsWith(`net46`)) OR $(TargetFramework.StartsWith(`net47`)) OR $(TargetFramework.StartsWith(`net48`))" /> <PackageReference Include="System.ValueTuple" Version="4.6.2" Condition="$(TargetFramework.StartsWith(`net46`)) OR $(TargetFramework.StartsWith(`net47`)) OR $(TargetFramework.StartsWith(`net48`))" />
</ItemGroup> </ItemGroup>

View File

@@ -290,6 +290,10 @@ namespace SabreTools.Metadata.DatItems.Test
[InlineData(ItemKey.SpamSum, false, true, "3::")] [InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")] [InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")] [InlineData(ItemKey.SpamSum, true, true, "3::")]
[InlineData(ItemKey.BLAKE3, false, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, false, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
public void GetKeyDB_DefaultImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDB_DefaultImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);
@@ -363,6 +367,10 @@ namespace SabreTools.Metadata.DatItems.Test
[InlineData(ItemKey.SpamSum, false, true, "BASE64")] [InlineData(ItemKey.SpamSum, false, true, "BASE64")]
[InlineData(ItemKey.SpamSum, true, false, "base64")] [InlineData(ItemKey.SpamSum, true, false, "base64")]
[InlineData(ItemKey.SpamSum, true, true, "base64")] [InlineData(ItemKey.SpamSum, true, true, "base64")]
[InlineData(ItemKey.BLAKE3, false, false, "DEADBEEF")]
[InlineData(ItemKey.BLAKE3, false, true, "DEADBEEF")]
[InlineData(ItemKey.BLAKE3, true, false, "deadbeef")]
[InlineData(ItemKey.BLAKE3, true, true, "deadbeef")]
public void GetKeyDB_CustomImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDB_CustomImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);
@@ -383,7 +391,8 @@ namespace SabreTools.Metadata.DatItems.Test
SHA256 = "DEADBEEF", SHA256 = "DEADBEEF",
SHA384 = "DEADBEEF", SHA384 = "DEADBEEF",
SHA512 = "DEADBEEF", SHA512 = "DEADBEEF",
SpamSum = "BASE64" SpamSum = "BASE64",
BLAKE3 = "DEADBEEF",
}; };
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename); string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);

View File

@@ -260,6 +260,10 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
[InlineData(ItemKey.SpamSum, false, true, "3::")] [InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")] [InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")] [InlineData(ItemKey.SpamSum, true, true, "3::")]
[InlineData(ItemKey.BLAKE3, false, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, false, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);

View File

@@ -337,6 +337,10 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
[InlineData(ItemKey.SpamSum, false, true, "3::")] [InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")] [InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")] [InlineData(ItemKey.SpamSum, true, true, "3::")]
[InlineData(ItemKey.BLAKE3, false, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, false, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);

View File

@@ -330,6 +330,10 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
[InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")] [InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, true, false, "deadbeef")] [InlineData(ItemKey.SpamSum, true, false, "deadbeef")]
[InlineData(ItemKey.SpamSum, true, true, "deadbeef")] [InlineData(ItemKey.SpamSum, true, true, "deadbeef")]
[InlineData(ItemKey.BLAKE3, false, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, false, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, false, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData(ItemKey.BLAKE3, true, true, "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);

View File

@@ -28,6 +28,7 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
Assert.Null(self.SHA384); Assert.Null(self.SHA384);
Assert.Null(self.SHA512); Assert.Null(self.SHA512);
Assert.Null(self.SpamSum); Assert.Null(self.SpamSum);
Assert.Null(self.BLAKE3);
} }
[Fact] [Fact]
@@ -49,7 +50,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
self.FillMissingInformation(other); self.FillMissingInformation(other);
@@ -67,6 +69,7 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
Assert.Equal("sha384", self.SHA384); Assert.Equal("sha384", self.SHA384);
Assert.Equal("sha512", self.SHA512); Assert.Equal("sha512", self.SHA512);
Assert.Equal("spamsum", self.SpamSum); Assert.Equal("spamsum", self.SpamSum);
Assert.Equal("blake3", self.BLAKE3);
} }
#endregion #endregion
@@ -98,7 +101,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -122,7 +126,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -146,7 +151,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -170,7 +176,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -194,7 +201,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -218,7 +226,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -242,7 +251,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -266,7 +276,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -290,7 +301,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -314,7 +326,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -338,7 +351,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -362,7 +376,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -386,7 +401,33 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = string.Empty,
};
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_BLAKE3_True()
{
Rom self = new Rom
{
CRC16 = string.Empty,
CRC32 = string.Empty,
CRC64 = string.Empty,
MD2 = string.Empty,
MD4 = string.Empty,
MD5 = string.Empty,
RIPEMD128 = string.Empty,
RIPEMD160 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
SHA384 = string.Empty,
SHA512 = string.Empty,
SpamSum = string.Empty,
BLAKE3 = "blake3",
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -410,7 +451,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HasHashes(); bool actual = self.HasHashes();
@@ -446,7 +488,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = "sha256", SHA256 = "sha256",
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum" SpamSum = "spamsum",
BLAKE3 = "blake3",
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -470,7 +513,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -494,7 +538,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -518,7 +563,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -542,7 +588,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -566,7 +613,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -590,7 +638,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -614,7 +663,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -638,7 +688,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -662,7 +713,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -686,7 +738,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = HashType.SHA256.ZeroString, SHA256 = HashType.SHA256.ZeroString,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -710,7 +763,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = HashType.SHA384.ZeroString, SHA384 = HashType.SHA384.ZeroString,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -734,7 +788,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = HashType.SHA512.ZeroString, SHA512 = HashType.SHA512.ZeroString,
SpamSum = string.Empty SpamSum = string.Empty,
BLAKE3 = string.Empty,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -758,7 +813,33 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = string.Empty, SHA256 = string.Empty,
SHA384 = string.Empty, SHA384 = string.Empty,
SHA512 = string.Empty, SHA512 = string.Empty,
SpamSum = HashType.SpamSum.ZeroString SpamSum = HashType.SpamSum.ZeroString,
BLAKE3 = string.Empty,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroBLAKE3_True()
{
Rom self = new Rom
{
CRC16 = string.Empty,
CRC32 = string.Empty,
CRC64 = string.Empty,
MD2 = string.Empty,
MD4 = string.Empty,
MD5 = string.Empty,
RIPEMD128 = string.Empty,
RIPEMD160 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
SHA384 = string.Empty,
SHA512 = string.Empty,
SpamSum = string.Empty,
BLAKE3 = HashType.BLAKE3.ZeroString,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -782,7 +863,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = HashType.SHA256.ZeroString, SHA256 = HashType.SHA256.ZeroString,
SHA384 = HashType.SHA384.ZeroString, SHA384 = HashType.SHA384.ZeroString,
SHA512 = HashType.SHA512.ZeroString, SHA512 = HashType.SHA512.ZeroString,
SpamSum = HashType.SpamSum.ZeroString SpamSum = HashType.SpamSum.ZeroString,
BLAKE3 = HashType.BLAKE3.ZeroString,
}; };
bool actual = self.HasZeroHash(); bool actual = self.HasZeroHash();
@@ -855,6 +937,10 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
[InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")] [InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, true, false, "deadbeef")] [InlineData(ItemKey.SpamSum, true, false, "deadbeef")]
[InlineData(ItemKey.SpamSum, true, true, "deadbeef")] [InlineData(ItemKey.SpamSum, true, true, "deadbeef")]
[InlineData(ItemKey.BLAKE3, false, false, "DEADBEEF")]
[InlineData(ItemKey.BLAKE3, false, true, "DEADBEEF")]
[InlineData(ItemKey.BLAKE3, true, false, "deadbeef")]
[InlineData(ItemKey.BLAKE3, true, true, "deadbeef")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected) public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{ {
Source source = new Source(0); Source source = new Source(0);
@@ -875,7 +961,8 @@ namespace SabreTools.Metadata.DatItems.Formats.Test
SHA256 = "DEADBEEF", SHA256 = "DEADBEEF",
SHA384 = "DEADBEEF", SHA384 = "DEADBEEF",
SHA512 = "DEADBEEF", SHA512 = "DEADBEEF",
SpamSum = "DEADBEEF" SpamSum = "DEADBEEF",
BLAKE3 = "DEADBEEF",
}; };
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename); string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);

View File

@@ -1,18 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -191,6 +191,10 @@ namespace SabreTools.Metadata.DatItems
// Now determine what the key should be based on the bucketedBy value // Now determine what the key should be based on the bucketedBy value
switch (bucketedBy) switch (bucketedBy)
{ {
case ItemKey.BLAKE3:
key = HashType.BLAKE3.ZeroString;
break;
case ItemKey.CRC16: case ItemKey.CRC16:
key = HashType.CRC16.ZeroString; key = HashType.CRC16.ZeroString;
break; break;

View File

@@ -26,6 +26,7 @@ namespace SabreTools.Metadata.DatItems
Machine, Machine,
BLAKE3,
CRC16, CRC16,
CRC32, CRC32,
CRC64, CRC64,

View File

@@ -74,6 +74,12 @@ namespace SabreTools.Metadata.DatItems.Formats
set => _internal.BitTorrentMagnetHash = value; set => _internal.BitTorrentMagnetHash = value;
} }
public string? BLAKE3
{
get => _internal.BLAKE3;
set => _internal.BLAKE3 = value;
}
public string? ClothCoverDetectionModuleVersion public string? ClothCoverDetectionModuleVersion
{ {
get => _internal.ClothCoverDetectionModuleVersion; get => _internal.ClothCoverDetectionModuleVersion;
@@ -436,6 +442,12 @@ namespace SabreTools.Metadata.DatItems.Formats
set => _internal.Status = value; set => _internal.Status = value;
} }
public string? StatusMisc
{
get => _internal.StatusMisc;
set => _internal.StatusMisc = value;
}
public string? Summation public string? Summation
{ {
get => _internal.Summation; get => _internal.Summation;
@@ -858,6 +870,10 @@ namespace SabreTools.Metadata.DatItems.Formats
// Now determine what the key should be based on the bucketedBy value // Now determine what the key should be based on the bucketedBy value
switch (bucketedBy) switch (bucketedBy)
{ {
case ItemKey.BLAKE3:
key = BLAKE3;
break;
case ItemKey.CRC16: case ItemKey.CRC16:
key = CRC16; key = CRC16;
break; break;

View File

@@ -320,6 +320,12 @@ namespace SabreTools.Metadata.DatItems
set => _internal.Serial = value; set => _internal.Serial = value;
} }
public string? SetType
{
get => _internal.SetType;
set => _internal.SetType = value;
}
public string? Source public string? Source
{ {
get => _internal.Source; get => _internal.Source;

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -32,8 +32,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
using SabreTools.Data.Models.Metadata; using SabreTools.Data.Models.Metadata;
using SabreTools.Hashing; using SabreTools.Hashing;
using Xunit; using Xunit;
@@ -1022,6 +1023,20 @@ namespace SabreTools.Metadata.Filter.Test
Assert.True(actual); Assert.True(actual);
} }
[Theory]
[InlineData("en-US")]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void Matches_DisplayRefresh_CurrentCultureIgnored(string culture)
{
CultureInfo.CurrentCulture = new CultureInfo(culture);
var filter = new FilterObject("display.refresh", "123.45", Operation.Equals);
Display obj = new Display { Refresh = 123.45 };
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion #endregion
#region Driver #region Driver
@@ -1103,6 +1118,7 @@ namespace SabreTools.Metadata.Filter.Test
[InlineData("header.datversion", "datversion")] [InlineData("header.datversion", "datversion")]
[InlineData("header.debug", "yes")] [InlineData("header.debug", "yes")]
[InlineData("header.description", "description")] [InlineData("header.description", "description")]
[InlineData("header.dir", "noautodir")]
[InlineData("header.email", "email")] [InlineData("header.email", "email")]
[InlineData("header.emulatorversion", "emulatorversion")] [InlineData("header.emulatorversion", "emulatorversion")]
[InlineData("header.filename", "filename")] [InlineData("header.filename", "filename")]
@@ -1131,6 +1147,7 @@ namespace SabreTools.Metadata.Filter.Test
[InlineData("header.schemalocation", "schemalocation")] [InlineData("header.schemalocation", "schemalocation")]
[InlineData("header.screenshotsheight", "screenshotsheight")] [InlineData("header.screenshotsheight", "screenshotsheight")]
[InlineData("header.screenshotswidth", "screenshotswidth")] [InlineData("header.screenshotswidth", "screenshotswidth")]
[InlineData("header.subset", "subset")]
[InlineData("header.system", "system")] [InlineData("header.system", "system")]
[InlineData("header.timestamp", "timestamp")] [InlineData("header.timestamp", "timestamp")]
[InlineData("header.type", "type")] [InlineData("header.type", "type")]
@@ -1150,6 +1167,7 @@ namespace SabreTools.Metadata.Filter.Test
DatVersion = "datversion", DatVersion = "datversion",
Debug = true, Debug = true,
Description = "description", Description = "description",
DirHandling = "noautodir",
Email = "email", Email = "email",
EmulatorVersion = "emulatorversion", EmulatorVersion = "emulatorversion",
FileName = "filename", FileName = "filename",
@@ -1176,6 +1194,7 @@ namespace SabreTools.Metadata.Filter.Test
SchemaLocation = "schemalocation", SchemaLocation = "schemalocation",
ScreenshotsHeight = "screenshotsheight", ScreenshotsHeight = "screenshotsheight",
ScreenshotsWidth = "screenshotswidth", ScreenshotsWidth = "screenshotswidth",
Subset = "subset",
System = "system", System = "system",
Timestamp = "timestamp", Timestamp = "timestamp",
Type = "type", Type = "type",
@@ -1290,6 +1309,8 @@ namespace SabreTools.Metadata.Filter.Test
[InlineData("machine.sampleof", "sampleof")] [InlineData("machine.sampleof", "sampleof")]
[InlineData("machine.savetype", "savetype")] [InlineData("machine.savetype", "savetype")]
[InlineData("machine.score", "score")] [InlineData("machine.score", "score")]
[InlineData("machine.serial", "serial")]
[InlineData("machine.settype", "dir")]
[InlineData("machine.source", "source")] [InlineData("machine.source", "source")]
[InlineData("machine.sourcefile", "sourcefile")] [InlineData("machine.sourcefile", "sourcefile")]
[InlineData("machine.sourcerom", "sourcerom")] [InlineData("machine.sourcerom", "sourcerom")]
@@ -1360,6 +1381,7 @@ namespace SabreTools.Metadata.Filter.Test
SaveType = "savetype", SaveType = "savetype",
Score = "score", Score = "score",
Serial = "serial", Serial = "serial",
SetType = "dir",
Source = "source", Source = "source",
SourceFile = "sourcefile", SourceFile = "sourcefile",
SourceRom = "sourcerom", SourceRom = "sourcerom",
@@ -1582,6 +1604,7 @@ namespace SabreTools.Metadata.Filter.Test
[InlineData("rom.bitrate", "bitrate")] [InlineData("rom.bitrate", "bitrate")]
[InlineData("rom.bittorrentmagnethash", "btih")] [InlineData("rom.bittorrentmagnethash", "btih")]
[InlineData("rom.btih", "btih")] [InlineData("rom.btih", "btih")]
[InlineData("rom.blake3", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")]
[InlineData("rom.cloth_cover_detection_module_version", "cloth_cover_detection_module_version")] [InlineData("rom.cloth_cover_detection_module_version", "cloth_cover_detection_module_version")]
[InlineData("rom.clothcoverdetectionmoduleversion", "cloth_cover_detection_module_version")] [InlineData("rom.clothcoverdetectionmoduleversion", "cloth_cover_detection_module_version")]
[InlineData("rom.collection-catalog-number", "collection-catalog-number")] [InlineData("rom.collection-catalog-number", "collection-catalog-number")]
@@ -1653,6 +1676,7 @@ namespace SabreTools.Metadata.Filter.Test
[InlineData("rom.spamsum", "3::")] [InlineData("rom.spamsum", "3::")]
[InlineData("rom.start", "start")] [InlineData("rom.start", "start")]
[InlineData("rom.status", "good")] [InlineData("rom.status", "good")]
[InlineData("rom.status_misc", "status")]
[InlineData("rom.summation", "summation")] [InlineData("rom.summation", "summation")]
[InlineData("rom.ocr", "ocr")] [InlineData("rom.ocr", "ocr")]
[InlineData("rom.tesseractocr", "ocr")] [InlineData("rom.tesseractocr", "ocr")]
@@ -1719,6 +1743,7 @@ namespace SabreTools.Metadata.Filter.Test
Bios = "bios", Bios = "bios",
Bitrate = "bitrate", Bitrate = "bitrate",
BitTorrentMagnetHash = "btih", BitTorrentMagnetHash = "btih",
BLAKE3 = HashType.BLAKE3.ZeroString,
ClothCoverDetectionModuleVersion = "cloth_cover_detection_module_version", ClothCoverDetectionModuleVersion = "cloth_cover_detection_module_version",
CollectionCatalogNumber = "collection-catalog-number", CollectionCatalogNumber = "collection-catalog-number",
Comment = "comment", Comment = "comment",
@@ -1776,6 +1801,7 @@ namespace SabreTools.Metadata.Filter.Test
SpamSum = HashType.SpamSum.ZeroString, SpamSum = HashType.SpamSum.ZeroString,
Start = "start", Start = "start",
Status = ItemStatus.Good, Status = ItemStatus.Good,
StatusMisc = "status",
Summation = "summation", Summation = "summation",
TesseractOCR = "ocr", TesseractOCR = "ocr",
TesseractOCRConverted = "ocr_converted", TesseractOCRConverted = "ocr_converted",
@@ -2074,6 +2100,20 @@ namespace SabreTools.Metadata.Filter.Test
Assert.True(actual); Assert.True(actual);
} }
[Theory]
[InlineData("en-US")]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void Matches_VideoRefresh_CurrentCultureIgnored(string culture)
{
CultureInfo.CurrentCulture = new CultureInfo(culture);
var filter = new FilterObject("video.refresh", "123.45", Operation.Equals);
Video obj = new Video { Refresh = 123.45 };
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion #endregion
} }
} }

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -18,10 +19,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -309,6 +309,8 @@ namespace SabreTools.Metadata.Filter
"datversion", "datversion",
"debug", "debug",
"description", "description",
"dir",
"dirhandling",
"email", "email",
"emulatorversion", "emulatorversion",
"filename", "filename",
@@ -337,6 +339,7 @@ namespace SabreTools.Metadata.Filter
"screenshotsheight", "screenshotsheight",
"screenshotswidth", "screenshotswidth",
"skipper", "skipper",
"subset",
"system", "system",
"timestamp", "timestamp",
"type", "type",
@@ -421,6 +424,8 @@ namespace SabreTools.Metadata.Filter
"sampleof", "sampleof",
"savetype", "savetype",
"score", "score",
"serial",
"settype",
"source", "source",
"sourcefile", "sourcefile",
"sourcerom", "sourcerom",
@@ -540,6 +545,7 @@ namespace SabreTools.Metadata.Filter
"bitrate", "bitrate",
"bittorrentmagnethash", "bittorrentmagnethash",
"btih", "btih",
"blake3",
"cloth_cover_detection_module_version", "cloth_cover_detection_module_version",
"clothcoverdetectionmoduleversion", "clothcoverdetectionmoduleversion",
"collection-catalog-number", "collection-catalog-number",
@@ -619,6 +625,7 @@ namespace SabreTools.Metadata.Filter
"spamsum", "spamsum",
"start", "start",
"status", "status",
"status_misc",
"summation", "summation",
"tesseractocr", "tesseractocr",
"tesseractocrconverted", "tesseractocrconverted",

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SabreTools.Data.Extensions; using SabreTools.Data.Extensions;
using SabreTools.Data.Models.Metadata; using SabreTools.Data.Models.Metadata;
@@ -1134,7 +1135,7 @@ namespace SabreTools.Metadata.Filter
return true; return true;
case "refresh": case "refresh":
case "freq": case "freq":
checkValue = obj.Refresh?.ToString(); checkValue = obj.Refresh?.ToString(CultureInfo.InvariantCulture);
return true; return true;
case "rotate": case "rotate":
case "orientation": case "orientation":
@@ -1280,6 +1281,10 @@ namespace SabreTools.Metadata.Filter
case "description": case "description":
checkValue = obj.Description; checkValue = obj.Description;
return true; return true;
case "dir":
case "dirhandling":
checkValue = obj.DirHandling;
return true;
case "email": case "email":
checkValue = obj.Email; checkValue = obj.Email;
return true; return true;
@@ -1365,6 +1370,9 @@ namespace SabreTools.Metadata.Filter
checkValue = obj.ScreenshotsWidth; checkValue = obj.ScreenshotsWidth;
return true; return true;
// Header.Search is intentionally skipped // Header.Search is intentionally skipped
case "subset":
checkValue = obj.Subset;
return true;
case "system": case "system":
checkValue = obj.System; checkValue = obj.System;
return true; return true;
@@ -1603,6 +1611,9 @@ namespace SabreTools.Metadata.Filter
case "serial": case "serial":
checkValue = obj.Serial; checkValue = obj.Serial;
return true; return true;
case "settype":
checkValue = obj.SetType;
return true;
case "source": case "source":
checkValue = obj.Source; checkValue = obj.Source;
return true; return true;
@@ -1901,6 +1912,9 @@ namespace SabreTools.Metadata.Filter
case "bittorrentmagnethash": case "bittorrentmagnethash":
checkValue = obj.BitTorrentMagnetHash; checkValue = obj.BitTorrentMagnetHash;
return true; return true;
case "blake3":
checkValue = obj.BLAKE3;
return true;
case "cloth_cover_detection_module_version": case "cloth_cover_detection_module_version":
case "clothcoverdetectionmoduleversion": case "clothcoverdetectionmoduleversion":
checkValue = obj.ClothCoverDetectionModuleVersion; checkValue = obj.ClothCoverDetectionModuleVersion;
@@ -2086,6 +2100,9 @@ namespace SabreTools.Metadata.Filter
case "status": case "status":
checkValue = obj.Status?.AsStringValue(); checkValue = obj.Status?.AsStringValue();
return true; return true;
case "status_misc":
checkValue = obj.StatusMisc;
return true;
case "summation": case "summation":
checkValue = obj.Summation; checkValue = obj.Summation;
return true; return true;
@@ -2496,7 +2513,7 @@ namespace SabreTools.Metadata.Filter
return true; return true;
case "refresh": case "refresh":
case "freq": case "freq":
checkValue = obj.Refresh?.ToString(); checkValue = obj.Refresh?.ToString(CultureInfo.InvariantCulture);
return true; return true;
case "displaytype": case "displaytype":
case "screen": case "screen":

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -36,7 +36,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -13,7 +13,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
@@ -37,8 +37,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="SabreTools.IO" Version="[2.1.0]" /> <PackageReference Include="SabreTools.IO" Version="2.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,17 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework> <CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.0.1</Version> <Version>3.3.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>

View File

@@ -115,6 +115,7 @@ namespace SabreTools.Serialization.CrossModel.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
xxHash364 = "xxhash364", xxHash364 = "xxhash364",
xxHash3128 = "xxhash3128", xxHash3128 = "xxhash3128",
Region = "region", Region = "region",
@@ -378,6 +379,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal("sha384", rom.SHA384); Assert.Equal("sha384", rom.SHA384);
Assert.Equal("sha512", rom.SHA512); Assert.Equal("sha512", rom.SHA512);
Assert.Equal("spamsum", rom.SpamSum); Assert.Equal("spamsum", rom.SpamSum);
Assert.Equal("blake3", rom.BLAKE3);
Assert.Equal("xxhash364", rom.xxHash364); Assert.Equal("xxhash364", rom.xxHash364);
Assert.Equal("xxhash3128", rom.xxHash3128); Assert.Equal("xxhash3128", rom.xxHash3128);
Assert.Equal("region", rom.Region); Assert.Equal("region", rom.Region);

View File

@@ -213,6 +213,29 @@ namespace SabreTools.Serialization.CrossModel.Test
Validate(newSpamsum); Validate(newSpamsum);
} }
[Fact]
public void RoundTripBLAKE3Test()
{
// Get the cross-model serializer
var serializer = new Hashfile();
// Build the data
Data.Models.Hashfile.Hashfile hf = Build(HashType.BLAKE3);
// Serialize to generic model
Data.Models.Metadata.MetadataFile? metadata = serializer.Serialize(hf);
Assert.NotNull(metadata);
// Serialize back to original model
Data.Models.Hashfile.Hashfile? newHf = serializer.Deserialize(metadata, HashType.BLAKE3);
// Validate the data
Assert.NotNull(newHf);
Assert.NotNull(newHf.BLAKE3);
var newBlake3 = Assert.Single(newHf.BLAKE3);
Validate(newBlake3);
}
/// <summary> /// <summary>
/// Build model for serialization and deserialization /// Build model for serialization and deserialization
/// </summary> /// </summary>
@@ -236,6 +259,8 @@ namespace SabreTools.Serialization.CrossModel.Test
return new Data.Models.Hashfile.Hashfile { SHA512 = [new Data.Models.Hashfile.SHA512 { Hash = "hash", File = "file" }] }; return new Data.Models.Hashfile.Hashfile { SHA512 = [new Data.Models.Hashfile.SHA512 { Hash = "hash", File = "file" }] };
else if (hashType == HashType.SpamSum) else if (hashType == HashType.SpamSum)
return new Data.Models.Hashfile.Hashfile { SpamSum = [new Data.Models.Hashfile.SpamSum { Hash = "hash", File = "file" }] }; return new Data.Models.Hashfile.Hashfile { SpamSum = [new Data.Models.Hashfile.SpamSum { Hash = "hash", File = "file" }] };
else if (hashType == HashType.BLAKE3)
return new Data.Models.Hashfile.Hashfile { BLAKE3 = [new Data.Models.Hashfile.BLAKE3 { Hash = "hash", File = "file" }] };
else else
throw new ArgumentOutOfRangeException(nameof(hashType)); throw new ArgumentOutOfRangeException(nameof(hashType));
} }
@@ -329,5 +354,15 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal("hash", spamsum.Hash); Assert.Equal("hash", spamsum.Hash);
Assert.Equal("file", spamsum.File); Assert.Equal("file", spamsum.File);
} }
/// <summary>
/// Validate a BLAKE3
/// </summary>
private static void Validate(Data.Models.Hashfile.BLAKE3? blake3)
{
Assert.NotNull(blake3);
Assert.Equal("hash", blake3.Hash);
Assert.Equal("file", blake3.File);
}
} }
} }

View File

@@ -79,6 +79,7 @@ namespace SabreTools.Serialization.CrossModel.Test
ForceMerging = Data.Models.Metadata.MergingFlag.Merged, ForceMerging = Data.Models.Metadata.MergingFlag.Merged,
ForceNodump = Data.Models.Metadata.NodumpFlag.Required, ForceNodump = Data.Models.Metadata.NodumpFlag.Required,
ForcePacking = Data.Models.Metadata.PackingFlag.Zip, ForcePacking = Data.Models.Metadata.PackingFlag.Zip,
Dir = "noautodir",
}; };
var clrmamepro = new Data.Models.Logiqx.ClrMamePro var clrmamepro = new Data.Models.Logiqx.ClrMamePro
@@ -115,6 +116,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Url = "url", Url = "url",
Comment = "comment", Comment = "comment",
Type = "type", Type = "type",
Subset = "subset",
RomVault = romvault, RomVault = romvault,
ClrMamePro = clrmamepro, ClrMamePro = clrmamepro,
RomCenter = romcenter, RomCenter = romcenter,
@@ -171,6 +173,7 @@ namespace SabreTools.Serialization.CrossModel.Test
SHA384 = "sha384", SHA384 = "sha384",
SHA512 = "sha512", SHA512 = "sha512",
SpamSum = "spamsum", SpamSum = "spamsum",
BLAKE3 = "blake3",
xxHash364 = "xxhash364", xxHash364 = "xxhash364",
xxHash3128 = "xxhash3128", xxHash3128 = "xxhash3128",
Merge = "merge", Merge = "merge",
@@ -252,6 +255,7 @@ namespace SabreTools.Serialization.CrossModel.Test
gameBase.Id = "id"; gameBase.Id = "id";
gameBase.CloneOfId = "cloneofid"; gameBase.CloneOfId = "cloneofid";
gameBase.Runnable = Data.Models.Metadata.Runnable.Yes; gameBase.Runnable = Data.Models.Metadata.Runnable.Yes;
gameBase.SetType = "dir";
gameBase.Comment = ["comment"]; gameBase.Comment = ["comment"];
gameBase.Description = "description"; gameBase.Description = "description";
gameBase.Year = "year"; gameBase.Year = "year";
@@ -314,6 +318,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal("url", header.Url); Assert.Equal("url", header.Url);
Assert.Equal("comment", header.Comment); Assert.Equal("comment", header.Comment);
Assert.Equal("type", header.Type); Assert.Equal("type", header.Type);
Assert.Equal("subset", header.Subset);
Validate(header.RomVault); Validate(header.RomVault);
Validate(header.ClrMamePro); Validate(header.ClrMamePro);
Validate(header.RomCenter); Validate(header.RomCenter);
@@ -329,6 +334,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal(Data.Models.Metadata.MergingFlag.Merged, rv.ForceMerging); Assert.Equal(Data.Models.Metadata.MergingFlag.Merged, rv.ForceMerging);
Assert.Equal(Data.Models.Metadata.NodumpFlag.Required, rv.ForceNodump); Assert.Equal(Data.Models.Metadata.NodumpFlag.Required, rv.ForceNodump);
Assert.Equal(Data.Models.Metadata.PackingFlag.Zip, rv.ForcePacking); Assert.Equal(Data.Models.Metadata.PackingFlag.Zip, rv.ForcePacking);
Assert.Equal("noautodir", rv.Dir);
} }
/// <summary> /// <summary>
@@ -380,6 +386,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal("id", gb.Id); Assert.Equal("id", gb.Id);
Assert.Equal("cloneofid", gb.CloneOfId); Assert.Equal("cloneofid", gb.CloneOfId);
Assert.Equal(Data.Models.Metadata.Runnable.Yes, gb.Runnable); Assert.Equal(Data.Models.Metadata.Runnable.Yes, gb.Runnable);
Assert.Equal("dir", gb.SetType);
Assert.NotNull(gb.Comment); Assert.NotNull(gb.Comment);
string comment = Assert.Single(gb.Comment); string comment = Assert.Single(gb.Comment);
@@ -504,6 +511,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.Equal("sha384", rom.SHA384); Assert.Equal("sha384", rom.SHA384);
Assert.Equal("sha512", rom.SHA512); Assert.Equal("sha512", rom.SHA512);
Assert.Equal("spamsum", rom.SpamSum); Assert.Equal("spamsum", rom.SpamSum);
Assert.Equal("blake3", rom.BLAKE3);
Assert.Equal("xxhash364", rom.xxHash364); Assert.Equal("xxhash364", rom.xxHash364);
Assert.Equal("xxhash3128", rom.xxHash3128); Assert.Equal("xxhash3128", rom.xxHash3128);
Assert.Equal("merge", rom.Merge); Assert.Equal("merge", rom.Merge);

View File

@@ -40,11 +40,19 @@ namespace SabreTools.Serialization.CrossModel.Test
Content = "content", Content = "content",
}; };
var standalone_rom = new Data.Models.OpenMSX.Rom
{
SHA1 = "sha1",
Type = "type",
Status = "status",
Remark = "remark",
};
var rom = new Data.Models.OpenMSX.Rom var rom = new Data.Models.OpenMSX.Rom
{ {
Start = "start", Start = "start",
Type = "type", Type = "type",
Hash = "hash", SHA1 = "hash",
Remark = "remark", Remark = "remark",
}; };
@@ -59,7 +67,7 @@ namespace SabreTools.Serialization.CrossModel.Test
{ {
Start = "start", Start = "start",
Type = "type", Type = "type",
Hash = "hash", SHA1 = "hash",
Remark = "remark", Remark = "remark",
}; };
@@ -74,7 +82,7 @@ namespace SabreTools.Serialization.CrossModel.Test
{ {
Start = "start", Start = "start",
Type = "type", Type = "type",
Hash = "hash", SHA1 = "hash",
Remark = "remark", Remark = "remark",
}; };
@@ -94,6 +102,7 @@ namespace SabreTools.Serialization.CrossModel.Test
Year = "year", Year = "year",
Country = "country", Country = "country",
Dump = [dump_rom, dump_megarom, dump_sccpluscart], Dump = [dump_rom, dump_megarom, dump_sccpluscart],
Rom = [standalone_rom],
}; };
return new Data.Models.OpenMSX.SoftwareDb return new Data.Models.OpenMSX.SoftwareDb
@@ -122,6 +131,10 @@ namespace SabreTools.Serialization.CrossModel.Test
{ {
Validate(dump); Validate(dump);
} }
Assert.NotNull(software.Rom);
var rom = Assert.Single(software.Rom);
ValidateStandalone(rom);
} }
/// <summary> /// <summary>
@@ -154,7 +167,19 @@ namespace SabreTools.Serialization.CrossModel.Test
Assert.NotNull(rombase); Assert.NotNull(rombase);
Assert.Equal("start", rombase.Start); Assert.Equal("start", rombase.Start);
Assert.Equal("type", rombase.Type); Assert.Equal("type", rombase.Type);
Assert.Equal("hash", rombase.Hash); Assert.Equal("hash", rombase.SHA1);
Assert.Equal("remark", rombase.Remark);
}
/// <summary>
/// Validate a RomBase
/// </summary>
private static void ValidateStandalone(Data.Models.OpenMSX.RomBase? rombase)
{
Assert.NotNull(rombase);
Assert.Equal("sha1", rombase.SHA1);
Assert.Equal("type", rombase.Type);
Assert.Equal("status", rombase.Status);
Assert.Equal("remark", rombase.Remark); Assert.Equal("remark", rombase.Remark);
} }
} }

View File

@@ -1,18 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0" /> <PackageReference Include="coverlet.collector" Version="10.1.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" /> <PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -290,6 +290,7 @@ namespace SabreTools.Serialization.CrossModel
SHA384 = item.SHA384, SHA384 = item.SHA384,
SHA512 = item.SHA512, SHA512 = item.SHA512,
SpamSum = item.SpamSum, SpamSum = item.SpamSum,
BLAKE3 = item.BLAKE3,
xxHash364 = item.xxHash364, xxHash364 = item.xxHash364,
xxHash3128 = item.xxHash3128, xxHash3128 = item.xxHash3128,
Merge = item.Merge, Merge = item.Merge,

View File

@@ -270,6 +270,7 @@ namespace SabreTools.Serialization.CrossModel
SHA384 = item.SHA384, SHA384 = item.SHA384,
SHA512 = item.SHA512, SHA512 = item.SHA512,
SpamSum = item.SpamSum, SpamSum = item.SpamSum,
BLAKE3 = item.BLAKE3,
xxHash364 = item.xxHash364, xxHash364 = item.xxHash364,
xxHash3128 = item.xxHash3128, xxHash3128 = item.xxHash3128,
Merge = item.Merge, Merge = item.Merge,

View File

@@ -36,6 +36,7 @@ namespace SabreTools.Serialization.CrossModel
var sha384s = new List<SHA384>(); var sha384s = new List<SHA384>();
var sha512s = new List<SHA512>(); var sha512s = new List<SHA512>();
var spamsums = new List<SpamSum>(); var spamsums = new List<SpamSum>();
var blake3s = new List<BLAKE3>();
foreach (var hashfile in hashfiles) foreach (var hashfile in hashfiles)
{ {
@@ -61,6 +62,8 @@ namespace SabreTools.Serialization.CrossModel
sha512s.AddRange(hashfile.SHA512); sha512s.AddRange(hashfile.SHA512);
if (hashfile.SpamSum is not null && hashfile.SpamSum.Length > 0) if (hashfile.SpamSum is not null && hashfile.SpamSum.Length > 0)
spamsums.AddRange(hashfile.SpamSum); spamsums.AddRange(hashfile.SpamSum);
if (hashfile.BLAKE3 is not null && hashfile.BLAKE3.Length > 0)
blake3s.AddRange(hashfile.BLAKE3);
} }
var hashfileItem = new Data.Models.Hashfile.Hashfile(); var hashfileItem = new Data.Models.Hashfile.Hashfile();
@@ -87,6 +90,8 @@ namespace SabreTools.Serialization.CrossModel
hashfileItem.SHA512 = [.. sha512s]; hashfileItem.SHA512 = [.. sha512s];
if (spamsums.Count > 0) if (spamsums.Count > 0)
hashfileItem.SpamSum = [.. spamsums]; hashfileItem.SpamSum = [.. spamsums];
if (blake3s.Count > 0)
hashfileItem.BLAKE3 = [.. blake3s];
return hashfileItem; return hashfileItem;
} }
@@ -135,6 +140,14 @@ namespace SabreTools.Serialization.CrossModel
SpamSum = hash == HashType.SpamSum SpamSum = hash == HashType.SpamSum
? Array.ConvertAll(roms, ConvertToSpamSum) ? Array.ConvertAll(roms, ConvertToSpamSum)
: null, : null,
#if NET7_0_OR_GREATER
BLAKE3 = hash == HashType.BLAKE3
#else
// HACK because BLAKE3 is not supported below .NET 7
BLAKE3 = hash == HashType.CRC1_ZERO
#endif
? Array.ConvertAll(roms, ConvertToBLAKE3)
: null,
}; };
} }
@@ -280,5 +293,18 @@ namespace SabreTools.Serialization.CrossModel
}; };
return spamsum; return spamsum;
} }
/// <summary>
/// Convert from <see cref="Models.Metadata.Rom"/> to <see cref="BLAKE3"/>
/// </summary>
private static BLAKE3 ConvertToBLAKE3(Data.Models.Metadata.Rom item)
{
var spamsum = new BLAKE3
{
Hash = item.BLAKE3,
File = item.Name,
};
return spamsum;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More