mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-11-15 17:00:38 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de17b0c59c | ||
|
|
1663345195 | ||
|
|
5c3184baa7 | ||
|
|
9e39a0cf16 | ||
|
|
36a67139d9 | ||
|
|
33caf06bc1 | ||
|
|
f639f7ac24 | ||
|
|
11f9bf9fd9 | ||
|
|
373cf2d752 | ||
|
|
dc18951d0b | ||
|
|
29b9d27c33 | ||
|
|
f6ade446c6 | ||
|
|
6238fcbb46 | ||
|
|
2f6698edac | ||
|
|
04e8751f9b | ||
|
|
7262f74f7f | ||
|
|
0b6af978ff | ||
|
|
2cacaac9fd | ||
|
|
d39d8f9b61 | ||
|
|
09fd38e776 | ||
|
|
db2a6f63ff | ||
|
|
d9dd16fbb9 | ||
|
|
3c0c31cf10 | ||
|
|
d9bf16deef |
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@@ -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
|
||||||
|
|||||||
1
.github/workflows/check_pr.yml
vendored
1
.github/workflows/check_pr.yml
vendored
@@ -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
4
.vscode/launch.json
vendored
@@ -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
|
||||||
|
|||||||
@@ -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.2.0</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.5.0" />
|
<PackageReference Include="SabreTools.CommandLine" Version="1.6.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.11" 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>
|
||||||
@@ -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.2.0</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.5.0" />
|
<PackageReference Include="SabreTools.CommandLine" Version="1.6.0" />
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<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="4.0.0" />
|
<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>
|
||||||
|
|||||||
274
SabreTools.Data.Extensions.Test/UDFExtensionsTests.cs
Normal file
274
SabreTools.Data.Extensions.Test/UDFExtensionsTests.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.2.0</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.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
417
SabreTools.Data.Extensions/UDFExtensions.cs
Normal file
417
SabreTools.Data.Extensions/UDFExtensions.cs
Normal 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 << 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 << 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,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; }
|
||||||
|
|
||||||
@@ -353,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;
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.2.0</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.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
24
SabreTools.Data.Models/UDF/AllocationExtentDescriptor.cs
Normal file
24
SabreTools.Data.Models/UDF/AllocationExtentDescriptor.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SabreTools.Data.Models/UDF/AnchorVolumeDescriptorPointer.cs
Normal file
24
SabreTools.Data.Models/UDF/AnchorVolumeDescriptorPointer.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SabreTools.Data.Models/UDF/CharacterSetSpecification.cs
Normal file
20
SabreTools.Data.Models/UDF/CharacterSetSpecification.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
126
SabreTools.Data.Models/UDF/Constants.cs
Normal file
126
SabreTools.Data.Models/UDF/Constants.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
14
SabreTools.Data.Models/UDF/Descriptor.cs
Normal file
14
SabreTools.Data.Models/UDF/Descriptor.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
SabreTools.Data.Models/UDF/DescriptorTag.cs
Normal file
50
SabreTools.Data.Models/UDF/DescriptorTag.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SabreTools.Data.Models/UDF/EntityIdentifier.cs
Normal file
24
SabreTools.Data.Models/UDF/EntityIdentifier.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
306
SabreTools.Data.Models/UDF/Enums.cs
Normal file
306
SabreTools.Data.Models/UDF/Enums.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
30
SabreTools.Data.Models/UDF/ExtendedFileEntry.cs
Normal file
30
SabreTools.Data.Models/UDF/ExtendedFileEntry.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SabreTools.Data.Models/UDF/ExtentDescriptor.cs
Normal file
19
SabreTools.Data.Models/UDF/ExtentDescriptor.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
115
SabreTools.Data.Models/UDF/FileEntry.cs
Normal file
115
SabreTools.Data.Models/UDF/FileEntry.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
49
SabreTools.Data.Models/UDF/FileIdentifierDescriptor.cs
Normal file
49
SabreTools.Data.Models/UDF/FileIdentifierDescriptor.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
99
SabreTools.Data.Models/UDF/FileSetDescriptor.cs
Normal file
99
SabreTools.Data.Models/UDF/FileSetDescriptor.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
15
SabreTools.Data.Models/UDF/GenericDescriptor.cs
Normal file
15
SabreTools.Data.Models/UDF/GenericDescriptor.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
49
SabreTools.Data.Models/UDF/ICBTag.cs
Normal file
49
SabreTools.Data.Models/UDF/ICBTag.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SabreTools.Data.Models/UDF/LogicalBlockAddress.cs
Normal file
19
SabreTools.Data.Models/UDF/LogicalBlockAddress.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
69
SabreTools.Data.Models/UDF/LogicalVolumeDescriptor.cs
Normal file
69
SabreTools.Data.Models/UDF/LogicalVolumeDescriptor.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
25
SabreTools.Data.Models/UDF/LongAllocationDescriptor.cs
Normal file
25
SabreTools.Data.Models/UDF/LongAllocationDescriptor.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
45
SabreTools.Data.Models/UDF/MetadataPartitionMap.cs
Normal file
45
SabreTools.Data.Models/UDF/MetadataPartitionMap.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
64
SabreTools.Data.Models/UDF/PartitionDescriptor.cs
Normal file
64
SabreTools.Data.Models/UDF/PartitionDescriptor.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SabreTools.Data.Models/UDF/PartitionMap.cs
Normal file
19
SabreTools.Data.Models/UDF/PartitionMap.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
114
SabreTools.Data.Models/UDF/PrimaryVolumeDescriptor.cs
Normal file
114
SabreTools.Data.Models/UDF/PrimaryVolumeDescriptor.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SabreTools.Data.Models/UDF/ShortAllocationDescriptor.cs
Normal file
20
SabreTools.Data.Models/UDF/ShortAllocationDescriptor.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
60
SabreTools.Data.Models/UDF/Timestamp.cs
Normal file
60
SabreTools.Data.Models/UDF/Timestamp.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SabreTools.Data.Models/UDF/Type1PartitionMap.cs
Normal file
19
SabreTools.Data.Models/UDF/Type1PartitionMap.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
37
SabreTools.Data.Models/UDF/Type2PartitionMap.cs
Normal file
37
SabreTools.Data.Models/UDF/Type2PartitionMap.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
52
SabreTools.Data.Models/UDF/Volume.cs
Normal file
52
SabreTools.Data.Models/UDF/Volume.cs
Normal 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 << 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SabreTools.Data.Models/UDF/VolumeDescriptorPointer.cs
Normal file
24
SabreTools.Data.Models/UDF/VolumeDescriptorPointer.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
30
SabreTools.Data.Models/UDF/VolumeStructureDescriptor.cs
Normal file
30
SabreTools.Data.Models/UDF/VolumeStructureDescriptor.cs
Normal 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; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -839,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",
|
||||||
@@ -1516,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);
|
||||||
|
|||||||
@@ -1066,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);
|
||||||
|
|||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -684,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;
|
||||||
@@ -983,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");
|
||||||
|
|||||||
@@ -88,7 +88,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.BLAKE3)
|
if (string.IsNullOrEmpty(rom.BLAKE3)
|
||||||
&& string.IsNullOrEmpty(rom.CRC16)
|
&& string.IsNullOrEmpty(rom.CRC16)
|
||||||
|
|||||||
@@ -282,7 +282,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.BLAKE3)
|
if (string.IsNullOrEmpty(rom.BLAKE3)
|
||||||
&& string.IsNullOrEmpty(rom.CRC16)
|
&& string.IsNullOrEmpty(rom.CRC16)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.2.0</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.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.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>
|
||||||
|
|||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -442,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;
|
||||||
|
|||||||
@@ -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.2.0</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.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1661,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")]
|
||||||
@@ -1785,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",
|
||||||
@@ -2083,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -625,6 +625,7 @@ namespace SabreTools.Metadata.Filter
|
|||||||
"spamsum",
|
"spamsum",
|
||||||
"start",
|
"start",
|
||||||
"status",
|
"status",
|
||||||
|
"status_misc",
|
||||||
"summation",
|
"summation",
|
||||||
"tesseractocr",
|
"tesseractocr",
|
||||||
"tesseractocrconverted",
|
"tesseractocrconverted",
|
||||||
|
|||||||
@@ -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":
|
||||||
@@ -2099,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;
|
||||||
@@ -2509,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":
|
||||||
|
|||||||
@@ -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.2.0</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.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -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>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Version>3.2.0</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.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -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.2.0</Version>
|
<Version>3.3.0</Version>
|
||||||
|
|
||||||
<!-- Package Properties -->
|
<!-- Package Properties -->
|
||||||
<Authors>Matt Nadareski</Authors>
|
<Authors>Matt Nadareski</Authors>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -41,17 +41,21 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
var game = new Software
|
var game = new Software
|
||||||
{
|
{
|
||||||
Title = item.Name,
|
Title = item.Name,
|
||||||
GenMSXID = item.GenMSXID,
|
|
||||||
System = item.System,
|
System = item.System,
|
||||||
Company = item.Company,
|
Company = item.Company,
|
||||||
Year = item.Year,
|
Year = item.Year,
|
||||||
Country = item.Country,
|
Country = item.Country,
|
||||||
|
GenMSXID = item.GenMSXID,
|
||||||
};
|
};
|
||||||
|
|
||||||
var dumps = item.Dump;
|
var dumps = item.Dump;
|
||||||
if (dumps is not null && dumps.Length > 0)
|
if (dumps is not null && dumps.Length > 0)
|
||||||
game.Dump = Array.ConvertAll(dumps, ConvertFromInternalModel);
|
game.Dump = Array.ConvertAll(dumps, ConvertFromInternalModel);
|
||||||
|
|
||||||
|
var roms = item.Rom;
|
||||||
|
if (roms is not null && roms.Length > 0)
|
||||||
|
game.Rom = Array.ConvertAll(roms, ConvertRomFromInternalModel);
|
||||||
|
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +94,11 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
{
|
{
|
||||||
var megaRom = new MegaRom
|
var megaRom = new MegaRom
|
||||||
{
|
{
|
||||||
Start = item.Start,
|
SHA1 = item.SHA1,
|
||||||
Type = item.OpenMSXType,
|
Type = item.OpenMSXType,
|
||||||
Hash = item.SHA1,
|
Status = item.StatusMisc,
|
||||||
Remark = item.Remark,
|
Remark = item.Remark,
|
||||||
|
Start = item.Start,
|
||||||
};
|
};
|
||||||
return megaRom;
|
return megaRom;
|
||||||
}
|
}
|
||||||
@@ -118,10 +123,11 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
{
|
{
|
||||||
var rom = new Rom
|
var rom = new Rom
|
||||||
{
|
{
|
||||||
Start = item.Start,
|
SHA1 = item.SHA1,
|
||||||
Type = item.OpenMSXType,
|
Type = item.OpenMSXType,
|
||||||
Hash = item.SHA1,
|
Status = item.StatusMisc,
|
||||||
Remark = item.Remark,
|
Remark = item.Remark,
|
||||||
|
Start = item.Start,
|
||||||
};
|
};
|
||||||
return rom;
|
return rom;
|
||||||
}
|
}
|
||||||
@@ -133,10 +139,11 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
{
|
{
|
||||||
var sccPlusCart = new SCCPlusCart
|
var sccPlusCart = new SCCPlusCart
|
||||||
{
|
{
|
||||||
Start = item.Start,
|
SHA1 = item.SHA1,
|
||||||
Type = item.OpenMSXType,
|
Type = item.OpenMSXType,
|
||||||
Hash = item.SHA1,
|
Status = item.StatusMisc,
|
||||||
Remark = item.Remark,
|
Remark = item.Remark,
|
||||||
|
Start = item.Start,
|
||||||
};
|
};
|
||||||
return sccPlusCart;
|
return sccPlusCart;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
if (item.Dump is not null && item.Dump.Length > 0)
|
if (item.Dump is not null && item.Dump.Length > 0)
|
||||||
machine.Dump = Array.ConvertAll(item.Dump, ConvertToInternalModel);
|
machine.Dump = Array.ConvertAll(item.Dump, ConvertToInternalModel);
|
||||||
|
|
||||||
|
if (item.Rom is not null && item.Rom.Length > 0)
|
||||||
|
machine.Rom = Array.ConvertAll(item.Rom, ConvertToInternalModel);
|
||||||
|
|
||||||
return machine;
|
return machine;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +115,13 @@ namespace SabreTools.Serialization.CrossModel
|
|||||||
{
|
{
|
||||||
var rom = new Data.Models.Metadata.Rom
|
var rom = new Data.Models.Metadata.Rom
|
||||||
{
|
{
|
||||||
Start = item.Start,
|
// TODO: Figure out a better placeholder
|
||||||
|
Name = item.SHA1,
|
||||||
|
SHA1 = item.SHA1,
|
||||||
OpenMSXType = item.Type,
|
OpenMSXType = item.Type,
|
||||||
SHA1 = item.Hash,
|
StatusMisc = item.Status,
|
||||||
Remark = item.Remark,
|
Remark = item.Remark,
|
||||||
|
Start = item.Start,
|
||||||
};
|
};
|
||||||
return rom;
|
return rom;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.2.0</Version>
|
<Version>3.3.0</Version>
|
||||||
|
|
||||||
<!-- Package Properties -->
|
<!-- Package Properties -->
|
||||||
<Authors>Matt Nadareski</Authors>
|
<Authors>Matt Nadareski</Authors>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -171,6 +172,25 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
Validate(newGame);
|
Validate(newGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("en-US")]
|
||||||
|
[InlineData("de-DE")]
|
||||||
|
[InlineData("fr-FR")]
|
||||||
|
public void SerializeFreq_CurrentCultureIgnored(string culture)
|
||||||
|
{
|
||||||
|
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||||
|
var serializer = new Writers.ClrMamePro();
|
||||||
|
|
||||||
|
// Serialize to stream
|
||||||
|
Stream? actual = serializer.SerializeStream(Build(game: false));
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
|
||||||
|
// Validate the freq value
|
||||||
|
string dat = new StreamReader(actual).ReadToEnd();
|
||||||
|
Assert.Contains("123.450000", dat);
|
||||||
|
Assert.DoesNotContain("123,45", dat);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build model for serialization and deserialization
|
/// Build model for serialization and deserialization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using SabreTools.Data.Models.ISO9660;
|
||||||
|
using SabreTools.Numerics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace SabreTools.Serialization.Readers.Test
|
namespace SabreTools.Serialization.Readers.Test
|
||||||
{
|
{
|
||||||
public class ISO9660Tests
|
public class ISO9660Tests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// File flags of a plain file record
|
||||||
|
/// </summary>
|
||||||
|
private const FileFlags NoFlags = 0;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NullArray_Null()
|
public void NullArray_Null()
|
||||||
{
|
{
|
||||||
@@ -68,5 +77,224 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
var actual = deserializer.Deserialize(data);
|
var actual = deserializer.Deserialize(data);
|
||||||
Assert.Null(actual);
|
Assert.Null(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParseDirectory_AmbiguousExtentLocation_ParsesBothExtents()
|
||||||
|
{
|
||||||
|
// Little-endian location points to sector 1, big-endian location to sector 2
|
||||||
|
byte[] image = new byte[4 * 2048];
|
||||||
|
WriteDirectory(image, 1);
|
||||||
|
WriteDirectory(image, 2);
|
||||||
|
var dr = new DirectoryRecord
|
||||||
|
{
|
||||||
|
ExtentLocation = new BothInt32(1, 2),
|
||||||
|
ExtentLength = 2048,
|
||||||
|
FileFlags = FileFlags.DIRECTORY,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = ISO9660.ParseDirectory(new MemoryStream(image), 2048, 2048, dr, false);
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.True(actual.ContainsKey(1));
|
||||||
|
Assert.True(actual.ContainsKey(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParseDirectory_ConsistentExtentLocation_ReadsEachDirectoryOnce()
|
||||||
|
{
|
||||||
|
// Sector 1 holds directory A at sector 2, which holds directory B at sector 3
|
||||||
|
byte[] image = new byte[5 * 2048];
|
||||||
|
WriteDirectory(image, 1, ("A", 2, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 2, ("B", 3, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 3);
|
||||||
|
var dr = new DirectoryRecord
|
||||||
|
{
|
||||||
|
ExtentLocation = 1,
|
||||||
|
ExtentLength = 2048,
|
||||||
|
FileFlags = FileFlags.DIRECTORY,
|
||||||
|
};
|
||||||
|
var stream = new SeekCountingStream(image);
|
||||||
|
|
||||||
|
var actual = ISO9660.ParseDirectory(stream, 2048, 2048, dr, false);
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(3, actual.Count);
|
||||||
|
Assert.Equal(1, stream.SeeksTo(1 * 2048));
|
||||||
|
Assert.Equal(1, stream.SeeksTo(2 * 2048));
|
||||||
|
Assert.Equal(1, stream.SeeksTo(3 * 2048));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParseDirectoryDescriptors_ReadsExtentsInVolumeOrder()
|
||||||
|
{
|
||||||
|
// Root at sector 20 holds A at sector 30 and B at sector 22, A holds C at sector 21
|
||||||
|
byte[] image = new byte[32 * 2048];
|
||||||
|
WriteDirectory(image, 20, ("A", 30, FileFlags.DIRECTORY), ("B", 22, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 30, ("C", 21, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 22);
|
||||||
|
WriteDirectory(image, 21);
|
||||||
|
var stream = new SeekCountingStream(image);
|
||||||
|
|
||||||
|
var actual = ISO9660.ParseDirectoryDescriptors(stream, 2048, [CreatePrimaryVolumeDescriptor(20)]);
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(new[] { 20, 21, 22, 30 }, actual.Keys.OrderBy(k => k));
|
||||||
|
Assert.Equal(new long[] { 20 * 2048, 22 * 2048, 30 * 2048, 21 * 2048 }, stream.SeekOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindDirectoryRecords_NestedPath_ReadsOnlyDirectoriesOnPath()
|
||||||
|
{
|
||||||
|
// Root at sector 20 holds OTHER at sector 22 and SUB at sector 30, SUB holds FILE.TXT;1 at sector 25
|
||||||
|
byte[] image = new byte[32 * 2048];
|
||||||
|
WriteDirectory(image, 20, ("OTHER", 22, FileFlags.DIRECTORY), ("SUB", 30, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 22);
|
||||||
|
WriteDirectory(image, 30, ("FILE.TXT;1", 25, NoFlags));
|
||||||
|
var stream = new SeekCountingStream(image);
|
||||||
|
|
||||||
|
var actual = ISO9660.FindDirectoryRecords(stream, 2048, CreatePrimaryVolumeDescriptor(20), "sub/file.txt");
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(25, Assert.Single(actual).ExtentLocation.LittleEndian);
|
||||||
|
Assert.Equal(new long[] { 20 * 2048, 30 * 2048 }, stream.SeekOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindDirectoryRecords_MissingPath_Null()
|
||||||
|
{
|
||||||
|
byte[] image = new byte[32 * 2048];
|
||||||
|
WriteDirectory(image, 20, ("SUB", 30, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 30);
|
||||||
|
|
||||||
|
var actual = ISO9660.FindDirectoryRecords(new MemoryStream(image), 2048, CreatePrimaryVolumeDescriptor(20), "SUB/MISSING.TXT");
|
||||||
|
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindDirectoryRecords_MultiExtentFile_ReturnsAllRecords()
|
||||||
|
{
|
||||||
|
// BIG.BIN;1 is recorded in two extents, at sectors 25 and 26
|
||||||
|
byte[] image = new byte[32 * 2048];
|
||||||
|
WriteDirectory(image, 20, ("BIG.BIN;1", 25, FileFlags.MULTI_EXTENT), ("BIG.BIN;1", 26, NoFlags), ("NEXT.BIN;1", 27, NoFlags));
|
||||||
|
|
||||||
|
var actual = ISO9660.FindDirectoryRecords(new MemoryStream(image), 2048, CreatePrimaryVolumeDescriptor(20), "BIG.BIN");
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(new[] { 25, 26 }, actual.Select(r => r.ExtentLocation.LittleEndian));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindDirectoryRecords_SupplementaryVolumeDescriptor_UsesUnicodeNames()
|
||||||
|
{
|
||||||
|
byte[] image = new byte[32 * 2048];
|
||||||
|
WriteDirectory(image, 20, Encoding.BigEndianUnicode, ("Sub", 30, FileFlags.DIRECTORY));
|
||||||
|
WriteDirectory(image, 30, Encoding.BigEndianUnicode, ("File.txt;1", 25, NoFlags));
|
||||||
|
var svd = new SupplementaryVolumeDescriptor
|
||||||
|
{
|
||||||
|
LogicalBlockSize = 2048,
|
||||||
|
RootDirectoryRecord = CreatePrimaryVolumeDescriptor(20).RootDirectoryRecord,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = ISO9660.FindDirectoryRecords(new MemoryStream(image), 2048, svd, "Sub/File.txt");
|
||||||
|
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(25, Assert.Single(actual).ExtentLocation.LittleEndian);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a primary volume descriptor with 2048-byte blocks and a one-sector root directory
|
||||||
|
/// </summary>
|
||||||
|
private static PrimaryVolumeDescriptor CreatePrimaryVolumeDescriptor(int rootSector)
|
||||||
|
{
|
||||||
|
return new PrimaryVolumeDescriptor
|
||||||
|
{
|
||||||
|
LogicalBlockSize = 2048,
|
||||||
|
RootDirectoryRecord = new DirectoryRecord
|
||||||
|
{
|
||||||
|
ExtentLocation = rootSector,
|
||||||
|
ExtentLength = 2048,
|
||||||
|
FileFlags = FileFlags.DIRECTORY,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a one-sector directory extent with self and parent records and the given entries
|
||||||
|
/// </summary>
|
||||||
|
private static void WriteDirectory(byte[] image, int sector, params (string Name, int Location, FileFlags Flags)[] entries)
|
||||||
|
=> WriteDirectory(image, sector, Encoding.ASCII, entries);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a one-sector directory extent with self and parent records and the given entries
|
||||||
|
/// </summary>
|
||||||
|
private static void WriteDirectory(byte[] image, int sector, Encoding encoding, params (string Name, int Location, FileFlags Flags)[] entries)
|
||||||
|
{
|
||||||
|
int offset = sector * 2048;
|
||||||
|
offset = WriteRecord(image, offset, Constants.CurrentDirectory, sector, FileFlags.DIRECTORY);
|
||||||
|
offset = WriteRecord(image, offset, Constants.ParentDirectory, sector, FileFlags.DIRECTORY);
|
||||||
|
foreach (var (name, location, flags) in entries)
|
||||||
|
{
|
||||||
|
offset = WriteRecord(image, offset, encoding.GetBytes(name), location, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a directory record for a one-sector extent, returns the offset after the record
|
||||||
|
/// </summary>
|
||||||
|
private static int WriteRecord(byte[] image, int offset, byte[] identifier, int location, FileFlags flags)
|
||||||
|
{
|
||||||
|
int length = 33 + identifier.Length + (identifier.Length % 2 == 0 ? 1 : 0);
|
||||||
|
image[offset] = (byte)length;
|
||||||
|
WriteBothEndian(image, offset + 2, location);
|
||||||
|
WriteBothEndian(image, offset + 10, 2048);
|
||||||
|
image[offset + 25] = (byte)flags;
|
||||||
|
image[offset + 28] = 1;
|
||||||
|
image[offset + 31] = 1;
|
||||||
|
image[offset + 32] = (byte)identifier.Length;
|
||||||
|
identifier.CopyTo(image, offset + 33);
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a 32-bit value in both-endian format
|
||||||
|
/// </summary>
|
||||||
|
private static void WriteBothEndian(byte[] image, int offset, int value)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
image[offset + i] = (byte)(value >> (8 * i));
|
||||||
|
image[offset + 7 - i] = (byte)(value >> (8 * i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Memory stream that records absolute seeks
|
||||||
|
/// </summary>
|
||||||
|
private sealed class SeekCountingStream : MemoryStream
|
||||||
|
{
|
||||||
|
private readonly Dictionary<long, int> _seeks = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offsets of all absolute seeks, in order
|
||||||
|
/// </summary>
|
||||||
|
public List<long> SeekOrder { get; } = [];
|
||||||
|
|
||||||
|
public SeekCountingStream(byte[] buffer) : base(buffer) { }
|
||||||
|
|
||||||
|
public int SeeksTo(long offset)
|
||||||
|
=> _seeks.TryGetValue(offset, out int count) ? count : 0;
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin loc)
|
||||||
|
{
|
||||||
|
if (loc == SeekOrigin.Begin)
|
||||||
|
{
|
||||||
|
_seeks[offset] = SeeksTo(offset) + 1;
|
||||||
|
SeekOrder.Add(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Seek(offset, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -125,6 +126,25 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
Validate(newGame);
|
Validate(newGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("en-US")]
|
||||||
|
[InlineData("de-DE")]
|
||||||
|
[InlineData("fr-FR")]
|
||||||
|
public void SerializeRefresh_CurrentCultureIgnored(string culture)
|
||||||
|
{
|
||||||
|
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||||
|
var serializer = new Writers.Listxml();
|
||||||
|
|
||||||
|
// Serialize to stream
|
||||||
|
Stream? actual = serializer.SerializeStream(Build(game: false));
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
|
||||||
|
// Validate the refresh values
|
||||||
|
string xml = new StreamReader(actual).ReadToEnd();
|
||||||
|
Assert.Contains("refresh=\"123.450000\"", xml);
|
||||||
|
Assert.DoesNotContain("123,45", xml);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build model for serialization and deserialization
|
/// Build model for serialization and deserialization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -121,6 +122,25 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
Validate(newGame);
|
Validate(newGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("en-US")]
|
||||||
|
[InlineData("de-DE")]
|
||||||
|
[InlineData("fr-FR")]
|
||||||
|
public void SerializeRefresh_CurrentCultureIgnored(string culture)
|
||||||
|
{
|
||||||
|
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||||
|
var serializer = new Writers.M1();
|
||||||
|
|
||||||
|
// Serialize to stream
|
||||||
|
Stream? actual = serializer.SerializeStream(Build(game: false));
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
|
||||||
|
// Validate the refresh values
|
||||||
|
string xml = new StreamReader(actual).ReadToEnd();
|
||||||
|
Assert.Contains("refresh=\"123.450000\"", xml);
|
||||||
|
Assert.DoesNotContain("123,45", xml);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build model for serialization and deserialization
|
/// Build model for serialization and deserialization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -121,6 +122,25 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
Validate(newGame);
|
Validate(newGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("en-US")]
|
||||||
|
[InlineData("de-DE")]
|
||||||
|
[InlineData("fr-FR")]
|
||||||
|
public void SerializeRefresh_CurrentCultureIgnored(string culture)
|
||||||
|
{
|
||||||
|
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||||
|
var serializer = new Writers.Mess();
|
||||||
|
|
||||||
|
// Serialize to stream
|
||||||
|
Stream? actual = serializer.SerializeStream(Build(game: false));
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
|
||||||
|
// Validate the refresh values
|
||||||
|
string xml = new StreamReader(actual).ReadToEnd();
|
||||||
|
Assert.Contains("refresh=\"123.450000\"", xml);
|
||||||
|
Assert.DoesNotContain("123,45", xml);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build model for serialization and deserialization
|
/// Build model for serialization and deserialization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -106,11 +106,19 @@ namespace SabreTools.Serialization.Readers.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",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,7 +133,7 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
{
|
{
|
||||||
Start = "start",
|
Start = "start",
|
||||||
Type = "type",
|
Type = "type",
|
||||||
Hash = "hash",
|
SHA1 = "hash",
|
||||||
Remark = "remark",
|
Remark = "remark",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -140,7 +148,7 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
{
|
{
|
||||||
Start = "start",
|
Start = "start",
|
||||||
Type = "type",
|
Type = "type",
|
||||||
Hash = "hash",
|
SHA1 = "hash",
|
||||||
Remark = "remark",
|
Remark = "remark",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,6 +168,7 @@ namespace SabreTools.Serialization.Readers.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
|
||||||
@@ -188,6 +197,10 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
{
|
{
|
||||||
Validate(dump);
|
Validate(dump);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(software.Rom);
|
||||||
|
var rom = Assert.Single(software.Rom);
|
||||||
|
ValidateStandalone(rom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -220,7 +233,19 @@ namespace SabreTools.Serialization.Readers.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -68,5 +69,48 @@ namespace SabreTools.Serialization.Readers.Test
|
|||||||
var actual = deserializer.Deserialize(data);
|
var actual = deserializer.Deserialize(data);
|
||||||
Assert.Null(actual);
|
Assert.Null(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidStream_FileDataLocated()
|
||||||
|
{
|
||||||
|
Stream? data = CreateArchive();
|
||||||
|
var deserializer = new PKZIP();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data);
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
|
||||||
|
var localFile = Assert.Single(actual.LocalFiles!);
|
||||||
|
var header = localFile.LocalFileHeader;
|
||||||
|
Assert.Equal(30 + header.FileNameLength + header.ExtraFieldLength, localFile.FileDataOffset);
|
||||||
|
Assert.Single(actual.CentralDirectoryHeaders!);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TruncatedFileData_Null()
|
||||||
|
{
|
||||||
|
byte[] archive = CreateArchive().ToArray();
|
||||||
|
Stream? data = new MemoryStream(archive, 0, 512);
|
||||||
|
var deserializer = new PKZIP();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an archive with one stored file of 1000 bytes
|
||||||
|
/// </summary>
|
||||||
|
private static MemoryStream CreateArchive()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true))
|
||||||
|
{
|
||||||
|
var entry = archive.CreateEntry("file.bin", CompressionLevel.NoCompression);
|
||||||
|
using var entryStream = entry.Open();
|
||||||
|
entryStream.Write([.. Enumerable.Range(0, 1000).Select(i => (byte)i)], 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<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="4.0.0" />
|
<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>
|
||||||
|
|||||||
294
SabreTools.Serialization.Readers.Test/UDFImageBuilder.cs
Normal file
294
SabreTools.Serialization.Readers.Test/UDFImageBuilder.cs
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SabreTools.Serialization.Readers.Test
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a minimal UDF 1.02 image in memory (2048-byte sectors, one Type 1 partition at sector 260)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Partition blocks:
|
||||||
|
/// 0 File Set Descriptor
|
||||||
|
/// 2 root directory File Entry, contents in block 3:
|
||||||
|
/// parent, "README.TXT" (embedded data), "deleted.txt" (deleted), "Sub" (directory),
|
||||||
|
/// "Media" (last entry, no padding)
|
||||||
|
/// 4 "README.TXT" File Entry, data "hello world" embedded
|
||||||
|
/// 5 "Sub" File Entry, contents in block 6: parent, "data.bin" (16-bit name)
|
||||||
|
/// 7 "data.bin" File Entry, long_ad to block 8 (2048 bytes), then an Allocation Extent Descriptor
|
||||||
|
/// in block 10 with a long_ad to block 9 (952 bytes)
|
||||||
|
/// 8-9 "data.bin" data (byte i = i % 251)
|
||||||
|
/// 11 "Media" File Entry, one short_ad of 100 bytes allocated but not recorded
|
||||||
|
/// </remarks>
|
||||||
|
internal static class UDFImageBuilder
|
||||||
|
{
|
||||||
|
public const int SectorSize = 2048;
|
||||||
|
public const int PartitionStart = 260;
|
||||||
|
public const int PartitionLength = 40;
|
||||||
|
public const int Sectors = 300;
|
||||||
|
public const int DataBinLength = 3000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build the image
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="corruptRootEntry">True to break the checksum of the root directory File Entry</param>
|
||||||
|
/// <param name="corruptRootEntryCrc">True to change a byte of the root directory File Entry that only the CRC covers</param>
|
||||||
|
/// <param name="emptyPartitionMap">True to record the partition map with length 0 and a map count of 3</param>
|
||||||
|
/// <param name="pointerLoop">True to replace the Terminating Descriptor by a Volume Descriptor Pointer back to the start of the sequence</param>
|
||||||
|
/// <param name="subName">Name of the "Sub" directory in the root directory</param>
|
||||||
|
/// <param name="subLoop">True to add a directory "loop" to "Sub" that points back to the root directory</param>
|
||||||
|
/// <param name="mediaPastEnd">True to record "Media" in a partition block past the end of the image</param>
|
||||||
|
public static byte[] Build(bool corruptRootEntry = false,
|
||||||
|
bool corruptRootEntryCrc = false,
|
||||||
|
bool emptyPartitionMap = false,
|
||||||
|
bool pointerLoop = false,
|
||||||
|
string subName = "Sub",
|
||||||
|
bool subLoop = false,
|
||||||
|
bool mediaPastEnd = false)
|
||||||
|
{
|
||||||
|
var image = new byte[Sectors * SectorSize];
|
||||||
|
|
||||||
|
// Volume Recognition Sequence
|
||||||
|
WriteStructure(image, 16, "BEA01");
|
||||||
|
WriteStructure(image, 17, "NSR02");
|
||||||
|
WriteStructure(image, 18, "TEA01");
|
||||||
|
|
||||||
|
// Anchor Volume Descriptor Pointer: main and reserve sequence at sector 32, 4 sectors
|
||||||
|
int avdp = 256 * SectorSize;
|
||||||
|
WriteUInt32(image, avdp + 16, 4 * SectorSize);
|
||||||
|
WriteUInt32(image, avdp + 20, 32);
|
||||||
|
WriteUInt32(image, avdp + 24, 4 * SectorSize);
|
||||||
|
WriteUInt32(image, avdp + 28, 32);
|
||||||
|
WriteTag(image, avdp, 2, 256, 512);
|
||||||
|
|
||||||
|
// Primary Volume Descriptor
|
||||||
|
int pvd = 32 * SectorSize;
|
||||||
|
WriteDString(image, pvd + 24, 32, "TESTVOLUME");
|
||||||
|
WriteTag(image, pvd, 1, 32, 512);
|
||||||
|
|
||||||
|
// Partition Descriptor: partition 0 at sector 260
|
||||||
|
int pd = 33 * SectorSize;
|
||||||
|
WriteUInt32(image, pd + 16, 1);
|
||||||
|
image[pd + 22] = 0;
|
||||||
|
WriteUInt32(image, pd + 188, PartitionStart);
|
||||||
|
WriteUInt32(image, pd + 192, mediaPastEnd ? PartitionLength + 20 : PartitionLength);
|
||||||
|
WriteTag(image, pd, 5, 33, 512);
|
||||||
|
|
||||||
|
// Logical Volume Descriptor: block size, domain, File Set Descriptor at block 0, one Type 1 map
|
||||||
|
int lvd = 34 * SectorSize;
|
||||||
|
WriteUInt32(image, lvd + 16, 2);
|
||||||
|
WriteDString(image, lvd + 84, 128, "TESTVOLUME");
|
||||||
|
WriteUInt32(image, lvd + 212, SectorSize);
|
||||||
|
WriteAscii(image, lvd + 217, "*OSTA UDF Compliant");
|
||||||
|
image[lvd + 240] = 0x02;
|
||||||
|
image[lvd + 241] = 0x01;
|
||||||
|
WriteLongAd(image, lvd + 248, SectorSize, 0);
|
||||||
|
WriteUInt32(image, lvd + 264, 6);
|
||||||
|
WriteUInt32(image, lvd + 268, emptyPartitionMap ? 3 : 1);
|
||||||
|
image[lvd + 440] = 1;
|
||||||
|
image[lvd + 441] = (byte)(emptyPartitionMap ? 0 : 6);
|
||||||
|
image[lvd + 442] = 1;
|
||||||
|
WriteTag(image, lvd, 6, 34, 446);
|
||||||
|
|
||||||
|
// Terminating Descriptor, or a Volume Descriptor Pointer back to the start of the sequence
|
||||||
|
int td = 35 * SectorSize;
|
||||||
|
if (pointerLoop)
|
||||||
|
{
|
||||||
|
WriteUInt32(image, td + 20, 4 * SectorSize);
|
||||||
|
WriteUInt32(image, td + 24, 32);
|
||||||
|
WriteTag(image, td, 3, 35, 512);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteTag(image, td, 8, 35, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
// File Set Descriptor: root directory ICB at block 2
|
||||||
|
int fsd = Block(0);
|
||||||
|
WriteDString(image, fsd + 304, 32, "TESTSET");
|
||||||
|
WriteLongAd(image, fsd + 400, SectorSize, 2);
|
||||||
|
WriteTag(image, fsd, 256, 0, 512);
|
||||||
|
|
||||||
|
// Root directory contents
|
||||||
|
int root = Block(3);
|
||||||
|
int length = 0;
|
||||||
|
length += WriteFid(image, root + length, 3, 0x0A, 2, []);
|
||||||
|
length += WriteFid(image, root + length, 3, 0x00, 4, Name8("README.TXT"));
|
||||||
|
length += WriteFid(image, root + length, 3, 0x04, 4, Name8("deleted.txt"));
|
||||||
|
length += WriteFid(image, root + length, 3, 0x02, 5, Name8(subName));
|
||||||
|
length += WriteFid(image, root + length, 3, 0x00, 11, Name8("Media"));
|
||||||
|
WriteFileEntry(image, 2, 4, 0, length, ShortAd(length, 3));
|
||||||
|
if (corruptRootEntry)
|
||||||
|
image[Block(2) + 4] ^= 0xFF;
|
||||||
|
if (corruptRootEntryCrc)
|
||||||
|
image[Block(2) + 36] ^= 0xFF;
|
||||||
|
|
||||||
|
// "README.TXT", embedded
|
||||||
|
WriteFileEntry(image, 4, 5, 3, 11, Encoding.ASCII.GetBytes("hello world"));
|
||||||
|
|
||||||
|
// "Sub" contents
|
||||||
|
int sub = Block(6);
|
||||||
|
length = 0;
|
||||||
|
length += WriteFid(image, sub + length, 6, 0x0A, 2, []);
|
||||||
|
length += WriteFid(image, sub + length, 6, 0x00, 7, Name16("data.bin"));
|
||||||
|
if (subLoop)
|
||||||
|
length += WriteFid(image, sub + length, 6, 0x02, 2, Name8("loop"));
|
||||||
|
|
||||||
|
WriteFileEntry(image, 5, 4, 0, length, ShortAd(length, 6));
|
||||||
|
|
||||||
|
// "data.bin", long allocation descriptors continued in an Allocation Extent Descriptor
|
||||||
|
var ads = new byte[32];
|
||||||
|
WriteLongAd(ads, 0, SectorSize, 8);
|
||||||
|
WriteLongAd(ads, 16, (3u << 30) | SectorSize, 10);
|
||||||
|
WriteFileEntry(image, 7, 5, 1, DataBinLength, ads);
|
||||||
|
for (int i = 0; i < DataBinLength; i++)
|
||||||
|
{
|
||||||
|
image[Block(8) + i] = (byte)(i % 251);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aed = Block(10);
|
||||||
|
WriteUInt32(image, aed + 20, 16);
|
||||||
|
WriteLongAd(image, aed + 24, DataBinLength - SectorSize, 9);
|
||||||
|
WriteTag(image, aed, 258, 10, 40);
|
||||||
|
|
||||||
|
// "Media", allocated but not recorded, or recorded past the end of the image
|
||||||
|
if (mediaPastEnd)
|
||||||
|
WriteFileEntry(image, 11, 5, 0, 100, ShortAd(100, PartitionLength + 10));
|
||||||
|
else
|
||||||
|
WriteFileEntry(image, 11, 5, 0, 100, ShortAd((1u << 30) | 100, 0));
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset of a partition block in the image
|
||||||
|
/// </summary>
|
||||||
|
public static int Block(int block) => (PartitionStart + block) * SectorSize;
|
||||||
|
|
||||||
|
private static void WriteStructure(byte[] image, int sector, string identifier)
|
||||||
|
{
|
||||||
|
int offset = sector * SectorSize;
|
||||||
|
image[offset] = 0;
|
||||||
|
WriteAscii(image, offset + 1, identifier);
|
||||||
|
image[offset + 6] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int WriteFid(byte[] image, int offset, uint block, byte characteristics, uint icb, byte[] name)
|
||||||
|
{
|
||||||
|
image[offset + 16] = 1;
|
||||||
|
image[offset + 18] = characteristics;
|
||||||
|
image[offset + 19] = (byte)name.Length;
|
||||||
|
WriteLongAd(image, offset + 20, SectorSize, icb);
|
||||||
|
Array.Copy(name, 0, image, offset + 38, name.Length);
|
||||||
|
int length = (38 + name.Length + 3) & ~3;
|
||||||
|
WriteTag(image, offset, 257, block, length);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteFileEntry(byte[] image, int block, byte fileType, ushort adType, long informationLength, byte[] ads)
|
||||||
|
{
|
||||||
|
int offset = Block(block);
|
||||||
|
image[offset + 16 + 4] = 4;
|
||||||
|
image[offset + 16 + 11] = fileType;
|
||||||
|
image[offset + 16 + 18] = (byte)adType;
|
||||||
|
WriteUInt32(image, offset + 56, (uint)informationLength);
|
||||||
|
WriteUInt32(image, offset + 172, (uint)ads.Length);
|
||||||
|
Array.Copy(ads, 0, image, offset + 176, ads.Length);
|
||||||
|
WriteTag(image, offset, 261, (uint)block, 176 + ads.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ShortAd(uint length, uint position)
|
||||||
|
{
|
||||||
|
var ad = new byte[8];
|
||||||
|
WriteUInt32(ad, 0, length);
|
||||||
|
WriteUInt32(ad, 4, position);
|
||||||
|
return ad;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ShortAd(int length, uint position) => ShortAd((uint)length, position);
|
||||||
|
|
||||||
|
private static void WriteLongAd(byte[] data, int offset, uint length, uint block)
|
||||||
|
{
|
||||||
|
WriteUInt32(data, offset, length);
|
||||||
|
WriteUInt32(data, offset + 4, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteLongAd(byte[] data, int offset, int length, uint block)
|
||||||
|
=> WriteLongAd(data, offset, (uint)length, block);
|
||||||
|
|
||||||
|
private static byte[] Name8(string name)
|
||||||
|
{
|
||||||
|
var bytes = new byte[name.Length + 1];
|
||||||
|
bytes[0] = 8;
|
||||||
|
Encoding.ASCII.GetBytes(name, 0, name.Length, bytes, 1);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] Name16(string name)
|
||||||
|
{
|
||||||
|
var bytes = new byte[(name.Length * 2) + 1];
|
||||||
|
bytes[0] = 16;
|
||||||
|
Encoding.BigEndianUnicode.GetBytes(name, 0, name.Length, bytes, 1);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteDString(byte[] image, int offset, int fieldLength, string value)
|
||||||
|
{
|
||||||
|
image[offset] = 8;
|
||||||
|
WriteAscii(image, offset + 1, value);
|
||||||
|
image[offset + fieldLength - 1] = (byte)(value.Length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteAscii(byte[] image, int offset, string value)
|
||||||
|
=> Encoding.ASCII.GetBytes(value, 0, value.Length, image, offset);
|
||||||
|
|
||||||
|
private static void WriteUInt32(byte[] data, int offset, uint value)
|
||||||
|
{
|
||||||
|
data[offset] = (byte)value;
|
||||||
|
data[offset + 1] = (byte)(value >> 8);
|
||||||
|
data[offset + 2] = (byte)(value >> 16);
|
||||||
|
data[offset + 3] = (byte)(value >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteUInt32(byte[] data, int offset, int value) => WriteUInt32(data, offset, (uint)value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a descriptor tag covering the given descriptor length (version 2, CRC, checksum)
|
||||||
|
/// </summary>
|
||||||
|
private static void WriteTag(byte[] image, int offset, ushort identifier, uint location, int length)
|
||||||
|
{
|
||||||
|
image[offset] = (byte)identifier;
|
||||||
|
image[offset + 1] = (byte)(identifier >> 8);
|
||||||
|
image[offset + 2] = 2;
|
||||||
|
ushort crc = Crc(image, offset + 16, length - 16);
|
||||||
|
image[offset + 8] = (byte)crc;
|
||||||
|
image[offset + 9] = (byte)(crc >> 8);
|
||||||
|
image[offset + 10] = (byte)(length - 16);
|
||||||
|
image[offset + 11] = (byte)((length - 16) >> 8);
|
||||||
|
WriteUInt32(image, offset + 12, location);
|
||||||
|
|
||||||
|
byte checksum = 0;
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
if (i != 4)
|
||||||
|
checksum += image[offset + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
image[offset + 4] = checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort Crc(byte[] data, int offset, int length)
|
||||||
|
{
|
||||||
|
ushort crc = 0;
|
||||||
|
for (int i = offset; i < offset + length; i++)
|
||||||
|
{
|
||||||
|
crc ^= (ushort)(data[i] << 8);
|
||||||
|
for (int bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
crc = (crc & 0x8000) != 0 ? (ushort)((crc << 1) ^ 0x1021) : (ushort)(crc << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
289
SabreTools.Serialization.Readers.Test/UDFTests.cs
Normal file
289
SabreTools.Serialization.Readers.Test/UDFTests.cs
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using SabreTools.Data.Extensions;
|
||||||
|
using SabreTools.Data.Models.UDF;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SabreTools.Serialization.Readers.Test
|
||||||
|
{
|
||||||
|
public class UDFTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void NullArray_Null()
|
||||||
|
{
|
||||||
|
byte[]? data = null;
|
||||||
|
int offset = 0;
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data, offset);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EmptyArray_Null()
|
||||||
|
{
|
||||||
|
byte[]? data = [];
|
||||||
|
int offset = 0;
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data, offset);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidArray_Null()
|
||||||
|
{
|
||||||
|
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||||
|
int offset = 0;
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data, offset);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NullStream_Null()
|
||||||
|
{
|
||||||
|
Stream? data = null;
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EmptyStream_Null()
|
||||||
|
{
|
||||||
|
Stream? data = new MemoryStream([]);
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidStream_Null()
|
||||||
|
{
|
||||||
|
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||||
|
var deserializer = new UDF();
|
||||||
|
|
||||||
|
var actual = deserializer.Deserialize(data);
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidImage_VolumeStructures()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build()));
|
||||||
|
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.Equal(3, volume.VolumeRecognitionSequence.Length);
|
||||||
|
Assert.NotNull(volume.AnchorVolumeDescriptorPointer);
|
||||||
|
Assert.Equal(4, volume.VolumeDescriptorSequence.Length);
|
||||||
|
var terminator = Assert.IsType<GenericDescriptor>(volume.VolumeDescriptorSequence[3]);
|
||||||
|
Assert.Equal(TagIdentifier.TERMINATING_DESCRIPTOR, terminator.DescriptorTag.TagIdentifier);
|
||||||
|
|
||||||
|
var lvd = volume.GetLogicalVolumeDescriptor();
|
||||||
|
Assert.NotNull(lvd);
|
||||||
|
Assert.Equal("TESTVOLUME", lvd.LogicalVolumeIdentifier.DecodeDString());
|
||||||
|
Assert.IsType<Type1PartitionMap>(Assert.Single(lvd.PartitionMaps));
|
||||||
|
|
||||||
|
Assert.NotNull(volume.FileSetDescriptor);
|
||||||
|
Assert.Equal("TESTSET", volume.FileSetDescriptor.FileSetIdentifier.DecodeDString());
|
||||||
|
Assert.Equal(2u, volume.FileSetDescriptor.RootDirectoryICB.ExtentLocation.LogicalBlockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidImage_DirectoryHierarchy()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build()));
|
||||||
|
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
// Root, "README.TXT", "Sub", "data.bin", "Media"; the deleted entry is not followed
|
||||||
|
Assert.Equal(5, volume.FileEntries.Count);
|
||||||
|
Assert.Equal(2, volume.Directories.Count);
|
||||||
|
Assert.Single(volume.AllocationExtentDescriptors);
|
||||||
|
|
||||||
|
// The last root entry has no padding and ends exactly at the end of the directory
|
||||||
|
var root = volume.Directories[UDFExtensions.GetKey(0, 2)];
|
||||||
|
Assert.Equal(5, root.Length);
|
||||||
|
Assert.Equal("Media", root[4].FileIdentifier.DecodeDCharacters());
|
||||||
|
Assert.Empty(root[4].Padding);
|
||||||
|
|
||||||
|
var sub = volume.Directories[UDFExtensions.GetKey(0, 5)];
|
||||||
|
Assert.Equal("data.bin", sub[1].FileIdentifier.DecodeDCharacters());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindFileEntry_EmbeddedData()
|
||||||
|
{
|
||||||
|
var data = new MemoryStream(UDFImageBuilder.Build());
|
||||||
|
var volume = new UDF().Deserialize(data);
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
var entry = UDF.FindFileEntry(data, volume, "README.TXT", out long key);
|
||||||
|
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Equal(UDFExtensions.GetKey(0, 4), key);
|
||||||
|
Assert.Equal(AllocationDescriptorType.EMBEDDED, entry.ICBTag.GetAllocationDescriptorType());
|
||||||
|
Assert.Equal("hello world", Encoding.ASCII.GetString(entry.AllocationDescriptors, 0, (int)entry.InformationLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindFileEntry_CaseInsensitive()
|
||||||
|
{
|
||||||
|
var data = new MemoryStream(UDFImageBuilder.Build());
|
||||||
|
var volume = new UDF().Deserialize(data);
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
var entry = UDF.FindFileEntry(data, volume, "/sub\\DATA.BIN", out long key);
|
||||||
|
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Equal(UDFExtensions.GetKey(0, 7), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindFileEntry_DeletedOrMissing_Null()
|
||||||
|
{
|
||||||
|
var data = new MemoryStream(UDFImageBuilder.Build());
|
||||||
|
var volume = new UDF().Deserialize(data);
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
Assert.Null(UDF.FindFileEntry(data, volume, "deleted.txt", out long deletedKey));
|
||||||
|
Assert.Equal(-1, deletedKey);
|
||||||
|
Assert.Null(UDF.FindFileEntry(data, volume, "Sub/missing.bin", out _));
|
||||||
|
Assert.Null(UDF.FindFileEntry(data, volume, "README.TXT/data.bin", out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindFileEntry_WithoutDirectories_ReadsOnlyThePath()
|
||||||
|
{
|
||||||
|
var data = new MemoryStream(UDFImageBuilder.Build());
|
||||||
|
var volume = new UDF().Deserialize(data, includeDirectories: false);
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.Empty(volume.FileEntries);
|
||||||
|
Assert.Empty(volume.Directories);
|
||||||
|
|
||||||
|
var entry = UDF.FindFileEntry(data, volume, "Sub/data.bin", out long key);
|
||||||
|
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Equal(UDFExtensions.GetKey(0, 7), key);
|
||||||
|
|
||||||
|
// Root, "Sub" and "data.bin" only
|
||||||
|
Assert.Equal(3, volume.FileEntries.Count);
|
||||||
|
Assert.Equal(2, volume.Directories.Count);
|
||||||
|
Assert.False(volume.FileEntries.ContainsKey(UDFExtensions.GetKey(0, 4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllocationExtent_Followed()
|
||||||
|
{
|
||||||
|
byte[] image = UDFImageBuilder.Build();
|
||||||
|
var data = new MemoryStream(image);
|
||||||
|
var volume = new UDF().Deserialize(data);
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
long key = UDFExtensions.GetKey(0, 7);
|
||||||
|
var descriptors = volume.GetAllocationDescriptors(key, volume.FileEntries[key]);
|
||||||
|
|
||||||
|
Assert.NotNull(descriptors);
|
||||||
|
Assert.Equal(2, descriptors.Count);
|
||||||
|
Assert.Equal(8u, descriptors[0].ExtentLocation.LogicalBlockNumber);
|
||||||
|
Assert.Equal(9u, descriptors[1].ExtentLocation.LogicalBlockNumber);
|
||||||
|
Assert.Equal((uint)UDFImageBuilder.DataBinLength, UDFExtensions.GetExtentLength(descriptors[0].ExtentLength) + UDFExtensions.GetExtentLength(descriptors[1].ExtentLength));
|
||||||
|
|
||||||
|
Assert.True(volume.TryGetSector(descriptors[1].ExtentLocation.GetKey(), out long sector));
|
||||||
|
Assert.Equal(UDFImageBuilder.PartitionStart + 9, sector);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotRecordedExtent_Kept()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build()));
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
|
||||||
|
long key = UDFExtensions.GetKey(0, 11);
|
||||||
|
var descriptors = volume.GetAllocationDescriptors(key, volume.FileEntries[key]);
|
||||||
|
|
||||||
|
Assert.NotNull(descriptors);
|
||||||
|
var ad = Assert.Single(descriptors);
|
||||||
|
Assert.Equal(ExtentType.NOT_RECORDED_BUT_ALLOCATED, UDFExtensions.GetExtentType(ad.ExtentLength));
|
||||||
|
Assert.Equal(100u, UDFExtensions.GetExtentLength(ad.ExtentLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CorruptRootEntry_NoDirectories()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build(corruptRootEntry: true)));
|
||||||
|
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.NotNull(volume.FileSetDescriptor);
|
||||||
|
Assert.Empty(volume.FileEntries);
|
||||||
|
Assert.Empty(volume.Directories);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CorruptRootEntryCrc_NoDirectories()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build(corruptRootEntryCrc: true)));
|
||||||
|
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.NotNull(volume.FileSetDescriptor);
|
||||||
|
Assert.Empty(volume.FileEntries);
|
||||||
|
Assert.Empty(volume.Directories);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EmptyPartitionMap_EndsMapTable()
|
||||||
|
{
|
||||||
|
var data = new MemoryStream(UDFImageBuilder.Build(emptyPartitionMap: true));
|
||||||
|
var anchor = UDF.ParseAnchorVolumeDescriptorPointer(data, Constants.AnchorVolumeDescriptorPointerSector);
|
||||||
|
Assert.NotNull(anchor);
|
||||||
|
|
||||||
|
var sequence = UDF.ParseVolumeDescriptorSequence(data, anchor.MainVolumeDescriptorSequenceExtent);
|
||||||
|
|
||||||
|
var lvd = Assert.Single(sequence.OfType<LogicalVolumeDescriptor>());
|
||||||
|
Assert.Empty(lvd.PartitionMaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VolumeDescriptorPointerLoop_Stops()
|
||||||
|
{
|
||||||
|
var volume = new UDF().Deserialize(new MemoryStream(UDFImageBuilder.Build(pointerLoop: true)));
|
||||||
|
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.Equal(5, volume.FileEntries.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnreadableSector_OtherEntriesRead()
|
||||||
|
{
|
||||||
|
// The "Sub" File Entry (block 5) cannot be read; "README.TXT" (block 4) is read in the same request
|
||||||
|
byte[] image = UDFImageBuilder.Build();
|
||||||
|
long bad = UDFImageBuilder.Block(5);
|
||||||
|
var volume = new UDF().Deserialize(new ThrowingStream(image, bad, bad + UDFImageBuilder.SectorSize));
|
||||||
|
|
||||||
|
// Root, "README.TXT" and "Media"
|
||||||
|
long[] expected = [UDFExtensions.GetKey(0, 2), UDFExtensions.GetKey(0, 4), UDFExtensions.GetKey(0, 11)];
|
||||||
|
Assert.NotNull(volume);
|
||||||
|
Assert.Equal(expected, volume.FileEntries.Keys.OrderBy(k => k));
|
||||||
|
Assert.Single(volume.Directories);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream that throws an IOException for reads touching a range, like a drive with an unreadable sector
|
||||||
|
/// </summary>
|
||||||
|
private sealed class ThrowingStream(byte[] data, long badStart, long badEnd) : MemoryStream(data)
|
||||||
|
{
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (Position < badEnd && Position + count > badStart)
|
||||||
|
throw new IOException("Unreadable sector");
|
||||||
|
|
||||||
|
return base.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using SabreTools.Data.Extensions;
|
using SabreTools.Data.Extensions;
|
||||||
using SabreTools.Data.Models.ISO9660;
|
using SabreTools.Data.Models.ISO9660;
|
||||||
using SabreTools.IO.Extensions;
|
using SabreTools.IO.Extensions;
|
||||||
@@ -589,6 +591,10 @@ namespace SabreTools.Serialization.Readers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse a Stream into a map of sector numbers to Directory
|
/// Parse a Stream into a map of sector numbers to Directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The directory trees of all volume descriptors are read together, in the order of their extents on the volume:
|
||||||
|
/// always the next known extent at or after the last one read, wrapping around to the lowest one at the end
|
||||||
|
/// </remarks>
|
||||||
/// <param name="data">Stream to parse</param>
|
/// <param name="data">Stream to parse</param>
|
||||||
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||||
/// <param name="vd">Set of volume descriptors for a volume</param>
|
/// <param name="vd">Set of volume descriptors for a volume</param>
|
||||||
@@ -596,6 +602,8 @@ namespace SabreTools.Serialization.Readers
|
|||||||
public static Dictionary<int, FileExtent>? ParseDirectoryDescriptors(Stream data, short sectorLength, VolumeDescriptor[] vdSet)
|
public static Dictionary<int, FileExtent>? ParseDirectoryDescriptors(Stream data, short sectorLength, VolumeDescriptor[] vdSet)
|
||||||
{
|
{
|
||||||
var directories = new Dictionary<int, FileExtent>();
|
var directories = new Dictionary<int, FileExtent>();
|
||||||
|
var parsed = new Dictionary<long, bool>();
|
||||||
|
var pending = new List<PendingExtent>();
|
||||||
foreach (VolumeDescriptor vd in vdSet)
|
foreach (VolumeDescriptor vd in vdSet)
|
||||||
{
|
{
|
||||||
DirectoryRecord rootDirectoryRecord;
|
DirectoryRecord rootDirectoryRecord;
|
||||||
@@ -609,17 +617,25 @@ namespace SabreTools.Serialization.Readers
|
|||||||
// Determine logical block size
|
// Determine logical block size
|
||||||
short blockLength = vd.GetLogicalBlockSize(sectorLength);
|
short blockLength = vd.GetLogicalBlockSize(sectorLength);
|
||||||
|
|
||||||
// Parse the root directory pointed to from the base volume descriptor
|
// Queue the root directory pointed to from the base volume descriptor
|
||||||
var descriptors = ParseDirectory(data, sectorLength, blockLength, rootDirectoryRecord, false);
|
QueueExtent(data, blockLength, rootDirectoryRecord, pending);
|
||||||
if (descriptors is null || descriptors.Count == 0)
|
}
|
||||||
|
|
||||||
|
// Read the queued extents in volume order
|
||||||
|
long position = 0;
|
||||||
|
while (pending.Count > 0)
|
||||||
|
{
|
||||||
|
int index = FindNextExtent(pending, position);
|
||||||
|
var extent = pending[index];
|
||||||
|
pending.RemoveAt(index);
|
||||||
|
position = extent.Offset + extent.Length;
|
||||||
|
|
||||||
|
// Each extent is read once
|
||||||
|
if (parsed.ContainsKey(extent.Offset))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Merge dictionaries
|
parsed[extent.Offset] = true;
|
||||||
foreach (var kvp in descriptors)
|
ParseExtent(data, sectorLength, extent, directories, pending);
|
||||||
{
|
|
||||||
if (!directories.ContainsKey(kvp.Key))
|
|
||||||
directories.Add(kvp.Key, kvp.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return error (null) if no valid directory descriptors were found
|
// Return error (null) if no valid directory descriptors were found
|
||||||
@@ -629,6 +645,333 @@ namespace SabreTools.Serialization.Readers
|
|||||||
return directories;
|
return directories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue the extent a directory record points to, if it needs to be read
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="blockLength">Number of bytes in a logical block (usually 2048)</param>
|
||||||
|
/// <param name="dr">Directory record pointing to the extent</param>
|
||||||
|
/// <param name="pending">Queued extents, sorted by offset</param>
|
||||||
|
private static void QueueExtent(Stream data, short blockLength, DirectoryRecord dr, List<PendingExtent> pending)
|
||||||
|
{
|
||||||
|
// Only directories and files with an extended attribute record are read
|
||||||
|
if ((dr.FileFlags & FileFlags.DIRECTORY) != FileFlags.DIRECTORY && dr.ExtendedAttributeRecordLength == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QueueExtent(data, blockLength, dr, false, pending);
|
||||||
|
|
||||||
|
// If the extent location field is ambiguous, also queue the big-endian extent
|
||||||
|
if (!dr.ExtentLocation.IsValid)
|
||||||
|
QueueExtent(data, blockLength, dr, true, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue the extent a directory record points to, using one endianness
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="blockLength">Number of bytes in a logical block (usually 2048)</param>
|
||||||
|
/// <param name="dr">Directory record pointing to the extent</param>
|
||||||
|
/// <param name="bigEndian">True if the Big Endian extent location/length should be used</param>
|
||||||
|
/// <param name="pending">Queued extents, sorted by offset</param>
|
||||||
|
private static void QueueExtent(Stream data, short blockLength, DirectoryRecord dr, bool bigEndian, List<PendingExtent> pending)
|
||||||
|
{
|
||||||
|
if (!TryGetExtent(data, blockLength, dr, bigEndian, out int extentLocation, out long extentOffset, out uint extentLength))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var extent = new PendingExtent(extentLocation, extentOffset, extentLength, blockLength, dr);
|
||||||
|
int index = pending.BinarySearch(extent, PendingExtentComparer.Instance);
|
||||||
|
pending.Insert(index < 0 ? ~index : index, extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the first queued extent at or after a position, or the first one if there is none
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pending">Queued extents, sorted by offset</param>
|
||||||
|
/// <param name="position">Offset just after the last extent read</param>
|
||||||
|
/// <returns>Index of the next extent to read</returns>
|
||||||
|
private static int FindNextExtent(List<PendingExtent> pending, long position)
|
||||||
|
{
|
||||||
|
var probe = new PendingExtent(0, position, 0, 0, null);
|
||||||
|
int index = pending.BinarySearch(probe, PendingExtentComparer.Instance);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (index > 0 && pending[index - 1].Offset == position)
|
||||||
|
{
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return index < pending.Count ? index : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse one queued extent and queue the extents its directory records point to
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||||
|
/// <param name="extent">Extent to parse</param>
|
||||||
|
/// <param name="directories">Map of sector numbers to parsed extents</param>
|
||||||
|
/// <param name="pending">Queued extents, sorted by offset</param>
|
||||||
|
private static void ParseExtent(Stream data, short sectorLength, PendingExtent extent, Dictionary<int, FileExtent> directories, List<PendingExtent> pending)
|
||||||
|
{
|
||||||
|
var dr = extent.Record!;
|
||||||
|
|
||||||
|
// Move stream to extent location
|
||||||
|
data.SeekIfPossible(extent.Offset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
// Check if the current extent is a directory
|
||||||
|
if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
|
||||||
|
{
|
||||||
|
// Read all directory records in this directory
|
||||||
|
var records = ParseDirectoryRecords(data, sectorLength, extent.Length);
|
||||||
|
if (records is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add current directory to dictionary
|
||||||
|
var currentDirectory = new DirectoryExtent();
|
||||||
|
currentDirectory.DirectoryRecords = [.. records];
|
||||||
|
if (!directories.ContainsKey(extent.Location))
|
||||||
|
directories.Add(extent.Location, currentDirectory);
|
||||||
|
|
||||||
|
// Queue all child extents
|
||||||
|
foreach (var record in records)
|
||||||
|
{
|
||||||
|
// Don't traverse to parent or self
|
||||||
|
if (record.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || record.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QueueExtent(data, extent.BlockLength, record, pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dr.ExtendedAttributeRecordLength > 0)
|
||||||
|
{
|
||||||
|
// Extent is a file, parse the Extended Attribute Record
|
||||||
|
var fileExtent = new FileExtent();
|
||||||
|
var ear = ParseExtendedAttributeRecord(data);
|
||||||
|
if (ear is not null)
|
||||||
|
fileExtent.ExtendedAttributeRecord = ear;
|
||||||
|
|
||||||
|
// Put the file extent is the dictionary
|
||||||
|
if (!directories.ContainsKey(extent.Location))
|
||||||
|
directories.Add(extent.Location, fileExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the extent a directory record points to, using one endianness
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="blockLength">Number of bytes in a logical block (usually 2048)</param>
|
||||||
|
/// <param name="dr">Directory record pointing to the extent</param>
|
||||||
|
/// <param name="bigEndian">True if the Big Endian extent location/length should be used</param>
|
||||||
|
/// <param name="extentLocation">Logical block number of the extent</param>
|
||||||
|
/// <param name="extentOffset">Offset of the extent in the stream</param>
|
||||||
|
/// <param name="extentLength">Number of bytes in the extent</param>
|
||||||
|
/// <returns>True if the extent is valid, false otherwise</returns>
|
||||||
|
private static bool TryGetExtent(Stream data, short blockLength, DirectoryRecord dr, bool bigEndian, out int extentLocation, out long extentOffset, out uint extentLength)
|
||||||
|
{
|
||||||
|
// Use provided extent endinanness
|
||||||
|
extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian;
|
||||||
|
extentLength = bigEndian ? dr.ExtentLength.BigEndian : dr.ExtentLength.LittleEndian;
|
||||||
|
extentOffset = extentLocation * (long)blockLength;
|
||||||
|
long extentFinal = extentOffset + extentLength;
|
||||||
|
|
||||||
|
// Deal with extent length ambiguity
|
||||||
|
if (!dr.ExtentLength.IsValid)
|
||||||
|
{
|
||||||
|
// If provided extent length is invalid, use the other value
|
||||||
|
if (extentLength <= 0 || extentFinal > data.Length)
|
||||||
|
extentLength = bigEndian ? dr.ExtentLength.LittleEndian : dr.ExtentLength.BigEndian;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate extent length
|
||||||
|
return extentLength > 0 && extentFinal <= data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse the directory records of one directory extent
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse, positioned at the start of the directory extent</param>
|
||||||
|
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||||
|
/// <param name="extentLength">Number of bytes in the directory extent</param>
|
||||||
|
/// <returns>Filled list of DirectoryRecord on success, null if the directory is invalid</returns>
|
||||||
|
private static List<DirectoryRecord>? ParseDirectoryRecords(Stream data, short sectorLength, uint extentLength)
|
||||||
|
{
|
||||||
|
// Start of directory should not be 0
|
||||||
|
int firstRecordLength = data.PeekByteValue();
|
||||||
|
if (firstRecordLength == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Read all directory records in this directory
|
||||||
|
var records = new List<DirectoryRecord>();
|
||||||
|
uint pos = 0;
|
||||||
|
while (pos < extentLength)
|
||||||
|
{
|
||||||
|
// Peek next byte to check whether the next record length is not greater than the end of the dir extent
|
||||||
|
var recordLength = data.PeekByteValue();
|
||||||
|
|
||||||
|
// If record length of 0x00, next record begins in next sector
|
||||||
|
if (recordLength == 0)
|
||||||
|
{
|
||||||
|
int paddingLength = (int)((uint)sectorLength - (pos % (uint)sectorLength));
|
||||||
|
pos += (uint)paddingLength;
|
||||||
|
_ = data.ReadBytes(paddingLength);
|
||||||
|
|
||||||
|
// Finish parsing records if end reached
|
||||||
|
if (pos >= extentLength)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Start of sector should not be 0, ignore entire directory
|
||||||
|
int nextRecordLength = data.PeekByteValue();
|
||||||
|
if (nextRecordLength <= 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure record will end in this extent
|
||||||
|
// TODO: Smartly detect record length for invalid record lengths
|
||||||
|
pos += recordLength;
|
||||||
|
if (pos > extentLength)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Get the next directory record
|
||||||
|
var directoryRecord = ParseDirectoryRecord(data, false);
|
||||||
|
|
||||||
|
// Compare recordLength with number of bytes in directoryRecord and ignore the directory if mismatch
|
||||||
|
var readLength = 33 + directoryRecord.FileIdentifier.Length + (directoryRecord.PaddingField is null ? 0 : 1) + directoryRecord.SystemUse.Length;
|
||||||
|
if (readLength != recordLength)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
records.Add(directoryRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the directory records of one path, reading only the directories along the path
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Names are compared case-insensitively and without a version suffix (";1") or a trailing period.
|
||||||
|
/// A file recorded in several extents returns one directory record per extent, in recorded order.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||||
|
/// <param name="vd">Primary or supplementary volume descriptor whose directory hierarchy is searched</param>
|
||||||
|
/// <param name="path">Path from the root directory, separated by '/' or '\'</param>
|
||||||
|
/// <returns>Filled array of DirectoryRecord on success, null if the path was not found</returns>
|
||||||
|
public static DirectoryRecord[]? FindDirectoryRecords(Stream data, short sectorLength, VolumeDescriptor vd, string path)
|
||||||
|
{
|
||||||
|
DirectoryRecord current;
|
||||||
|
if (vd is PrimaryVolumeDescriptor pvd)
|
||||||
|
current = pvd.RootDirectoryRecord;
|
||||||
|
else if (vd is SupplementaryVolumeDescriptor svd)
|
||||||
|
current = svd.RootDirectoryRecord;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Determine logical block size
|
||||||
|
short blockLength = vd.GetLogicalBlockSize(sectorLength);
|
||||||
|
|
||||||
|
// TODO: Better encoding detection (EscapeSequences)
|
||||||
|
var encoding = vd is SupplementaryVolumeDescriptor ? Encoding.BigEndianUnicode : Encoding.UTF8;
|
||||||
|
|
||||||
|
string[] names = path.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (names.Length == 0)
|
||||||
|
return [current];
|
||||||
|
|
||||||
|
for (int i = 0; i < names.Length; i++)
|
||||||
|
{
|
||||||
|
// Only a directory can contain the next name
|
||||||
|
if ((current.FileFlags & FileFlags.DIRECTORY) != FileFlags.DIRECTORY)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// If the extent location field is ambiguous, also search the big-endian directory extent
|
||||||
|
var found = FindInDirectory(data, sectorLength, blockLength, current, false, encoding, names[i]);
|
||||||
|
if (found is null && !current.ExtentLocation.IsValid)
|
||||||
|
found = FindInDirectory(data, sectorLength, blockLength, current, true, encoding, names[i]);
|
||||||
|
|
||||||
|
if (found is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (i == names.Length - 1)
|
||||||
|
return found;
|
||||||
|
|
||||||
|
current = found[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the directory records of one name in one directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Stream to parse</param>
|
||||||
|
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||||
|
/// <param name="blockLength">Number of bytes in a logical block (usually 2048)</param>
|
||||||
|
/// <param name="dr">Directory record pointing to the directory extent</param>
|
||||||
|
/// <param name="bigEndian">True if the Big Endian extent location/length should be used</param>
|
||||||
|
/// <param name="encoding">Encoding of the file identifiers</param>
|
||||||
|
/// <param name="name">Name to find</param>
|
||||||
|
/// <returns>Filled array of DirectoryRecord on success, null if the name was not found</returns>
|
||||||
|
private static DirectoryRecord[]? FindInDirectory(Stream data, short sectorLength, short blockLength, DirectoryRecord dr, bool bigEndian, Encoding encoding, string name)
|
||||||
|
{
|
||||||
|
if (!TryGetExtent(data, blockLength, dr, bigEndian, out _, out long extentOffset, out uint extentLength))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Move stream to directory location
|
||||||
|
data.SeekIfPossible(extentOffset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var records = ParseDirectoryRecords(data, sectorLength, extentLength);
|
||||||
|
if (records is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string wanted = TrimIdentifier(name);
|
||||||
|
for (int i = 0; i < records.Count; i++)
|
||||||
|
{
|
||||||
|
// Don't match parent or self
|
||||||
|
if (records[i].FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || records[i].FileIdentifier.EqualsExactly(Constants.ParentDirectory))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!string.Equals(TrimIdentifier(encoding.GetString(records[i].FileIdentifier)), wanted, StringComparison.OrdinalIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// A multi-extent file continues in the following records, up to the final one
|
||||||
|
var matches = new List<DirectoryRecord> { records[i] };
|
||||||
|
while ((records[i].FileFlags & FileFlags.MULTI_EXTENT) == FileFlags.MULTI_EXTENT && i + 1 < records.Count)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
matches.Add(records[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. matches];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the version suffix and a trailing period from a file identifier
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">File identifier as recorded or requested</param>
|
||||||
|
/// <returns>File identifier without version suffix and trailing period</returns>
|
||||||
|
private static string TrimIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
int versionSeparator = identifier.LastIndexOf(';');
|
||||||
|
if (versionSeparator >= 0)
|
||||||
|
identifier = identifier.Substring(0, versionSeparator);
|
||||||
|
|
||||||
|
if (identifier.EndsWith("."))
|
||||||
|
identifier = identifier.Substring(0, identifier.Length - 1);
|
||||||
|
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse a Stream into a map of sector numbers to Directory
|
/// Parse a Stream into a map of sector numbers to Directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -671,54 +1014,10 @@ namespace SabreTools.Serialization.Readers
|
|||||||
if (dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
|
if (dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
// Start of directory should not be 0
|
|
||||||
int firstRecordLength = data.PeekByteValue();
|
|
||||||
if (firstRecordLength == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Read all directory records in this directory
|
// Read all directory records in this directory
|
||||||
var records = new List<DirectoryRecord>();
|
var records = ParseDirectoryRecords(data, sectorLength, extentLength);
|
||||||
uint pos = 0;
|
if (records is null)
|
||||||
while (pos < extentLength)
|
return null;
|
||||||
{
|
|
||||||
// Peek next byte to check whether the next record length is not greater than the end of the dir extent
|
|
||||||
var recordLength = data.PeekByteValue();
|
|
||||||
|
|
||||||
// If record length of 0x00, next record begins in next sector
|
|
||||||
if (recordLength == 0)
|
|
||||||
{
|
|
||||||
int paddingLength = (int)((uint)sectorLength - (pos % (uint)sectorLength));
|
|
||||||
pos += (uint)paddingLength;
|
|
||||||
_ = data.ReadBytes(paddingLength);
|
|
||||||
|
|
||||||
// Finish parsing records if end reached
|
|
||||||
if (pos >= extentLength)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Start of sector should not be 0, ignore entire directory
|
|
||||||
int nextRecordLength = data.PeekByteValue();
|
|
||||||
if (nextRecordLength <= 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure record will end in this extent
|
|
||||||
// TODO: Smartly detect record length for invalid record lengths
|
|
||||||
pos += recordLength;
|
|
||||||
if (pos > extentLength)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Get the next directory record
|
|
||||||
var directoryRecord = ParseDirectoryRecord(data, false);
|
|
||||||
|
|
||||||
// Compare recordLength with number of bytes in directoryRecord and return null if mismatch
|
|
||||||
var readLength = 33 + directoryRecord.FileIdentifier.Length + (directoryRecord.PaddingField is null ? 0 : 1) + directoryRecord.SystemUse.Length;
|
|
||||||
if (readLength != recordLength)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
records.Add(directoryRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add current directory to dictionary
|
// Add current directory to dictionary
|
||||||
var currentDirectory = new DirectoryExtent();
|
var currentDirectory = new DirectoryExtent();
|
||||||
@@ -770,7 +1069,7 @@ namespace SabreTools.Serialization.Readers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the extent location field is ambiguous, also parse the big-endian directory extent
|
// If the extent location field is ambiguous, also parse the big-endian directory extent
|
||||||
if (!bigEndian && dr.ExtentLocation.IsValid)
|
if (!bigEndian && !dr.ExtentLocation.IsValid)
|
||||||
{
|
{
|
||||||
var bigEndianDir = ParseDirectory(data, sectorLength, blockLength, dr, true);
|
var bigEndianDir = ParseDirectory(data, sectorLength, blockLength, dr, true);
|
||||||
if (bigEndianDir is not null)
|
if (bigEndianDir is not null)
|
||||||
@@ -936,5 +1235,60 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extent queued for reading
|
||||||
|
/// </summary>
|
||||||
|
private sealed class PendingExtent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logical block number of the extent
|
||||||
|
/// </summary>
|
||||||
|
public int Location { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Byte offset of the extent
|
||||||
|
/// </summary>
|
||||||
|
public long Offset { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Length of the extent in bytes
|
||||||
|
/// </summary>
|
||||||
|
public uint Length { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bytes in a logical block of the volume
|
||||||
|
/// </summary>
|
||||||
|
public short BlockLength { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directory record pointing to the extent
|
||||||
|
/// </summary>
|
||||||
|
public DirectoryRecord? Record { get; }
|
||||||
|
|
||||||
|
public PendingExtent(int location, long offset, uint length, short blockLength, DirectoryRecord? record)
|
||||||
|
{
|
||||||
|
Location = location;
|
||||||
|
Offset = offset;
|
||||||
|
Length = length;
|
||||||
|
BlockLength = blockLength;
|
||||||
|
Record = record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orders queued extents by offset
|
||||||
|
/// </summary>
|
||||||
|
private sealed class PendingExtentComparer : IComparer<PendingExtent>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shared instance
|
||||||
|
/// </summary>
|
||||||
|
public static readonly PendingExtentComparer Instance = new();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Compare(PendingExtent? x, PendingExtent? y)
|
||||||
|
=> (x?.Offset ?? 0).CompareTo(y?.Offset ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,11 @@ namespace SabreTools.Serialization.Readers
|
|||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
obj.SHA1 = reader.GetAttribute("sha1");
|
||||||
|
obj.Type = reader.GetAttribute("type");
|
||||||
|
obj.Status = reader.GetAttribute("status");
|
||||||
|
obj.Remark = reader.GetAttribute("remark");
|
||||||
|
|
||||||
// Handle empty elements
|
// Handle empty elements
|
||||||
if (reader.IsEmptyElement)
|
if (reader.IsEmptyElement)
|
||||||
return obj;
|
return obj;
|
||||||
@@ -253,11 +258,12 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
switch (reader.Name)
|
switch (reader.Name)
|
||||||
{
|
{
|
||||||
case "start":
|
case "hash":
|
||||||
if (obj.Start is not null && Debug)
|
case "sha1":
|
||||||
|
if (obj.SHA1 is not null && Debug)
|
||||||
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
|
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
|
||||||
|
|
||||||
obj.Start = reader.ReadElementContentAsString();
|
obj.SHA1 = reader.ReadElementContentAsString();
|
||||||
break;
|
break;
|
||||||
case "type":
|
case "type":
|
||||||
if (obj.Type is not null && Debug)
|
if (obj.Type is not null && Debug)
|
||||||
@@ -265,11 +271,11 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
obj.Type = reader.ReadElementContentAsString();
|
obj.Type = reader.ReadElementContentAsString();
|
||||||
break;
|
break;
|
||||||
case "hash":
|
case "status":
|
||||||
if (obj.Hash is not null && Debug)
|
if (obj.Status is not null && Debug)
|
||||||
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
|
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
|
||||||
|
|
||||||
obj.Hash = reader.ReadElementContentAsString();
|
obj.Status = reader.ReadElementContentAsString();
|
||||||
break;
|
break;
|
||||||
case "remark":
|
case "remark":
|
||||||
if (obj.Remark is not null && Debug)
|
if (obj.Remark is not null && Debug)
|
||||||
@@ -277,6 +283,12 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
obj.Remark = reader.ReadElementContentAsString();
|
obj.Remark = reader.ReadElementContentAsString();
|
||||||
break;
|
break;
|
||||||
|
case "start":
|
||||||
|
if (obj.Start is not null && Debug)
|
||||||
|
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
|
||||||
|
|
||||||
|
obj.Start = reader.ReadElementContentAsString();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
|
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
|
||||||
@@ -297,11 +309,19 @@ namespace SabreTools.Serialization.Readers
|
|||||||
{
|
{
|
||||||
var obj = new Software();
|
var obj = new Software();
|
||||||
|
|
||||||
|
obj.Title = reader.GetAttribute("title");
|
||||||
|
obj.System = reader.GetAttribute("system");
|
||||||
|
obj.Company = reader.GetAttribute("company");
|
||||||
|
obj.Year = reader.GetAttribute("year");
|
||||||
|
obj.Country = reader.GetAttribute("country");
|
||||||
|
obj.GenMSXID = reader.GetAttribute("genmsxid");
|
||||||
|
|
||||||
// Handle empty elements
|
// Handle empty elements
|
||||||
if (reader.IsEmptyElement)
|
if (reader.IsEmptyElement)
|
||||||
return obj;
|
return obj;
|
||||||
|
|
||||||
List<Dump> dumps = [];
|
List<Dump> dumps = [];
|
||||||
|
List<RomBase> roms = [];
|
||||||
|
|
||||||
reader.Read();
|
reader.Read();
|
||||||
while (!reader.EOF)
|
while (!reader.EOF)
|
||||||
@@ -369,6 +389,13 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
reader.Skip();
|
reader.Skip();
|
||||||
break;
|
break;
|
||||||
|
case "rom":
|
||||||
|
var rom = ParseRomBase(reader);
|
||||||
|
if (rom is not null)
|
||||||
|
roms.Add(rom);
|
||||||
|
|
||||||
|
reader.Skip();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
|
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
|
||||||
@@ -379,6 +406,8 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
if (dumps.Count > 0)
|
if (dumps.Count > 0)
|
||||||
obj.Dump = [.. dumps];
|
obj.Dump = [.. dumps];
|
||||||
|
if (roms.Count > 0)
|
||||||
|
obj.Rom = [.. roms];
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -472,13 +472,13 @@ namespace SabreTools.Serialization.Readers
|
|||||||
|
|
||||||
#region File Data
|
#region File Data
|
||||||
|
|
||||||
// Try to read the file data
|
// The file data is skipped, so a large archive is not read into memory
|
||||||
var fileData = data.ReadBytes((int)compressedSize);
|
if (compressedSize > (ulong)(data.Length - data.Position))
|
||||||
if (fileData.Length < (long)compressedSize)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Set the file data
|
// Set the location of the file data
|
||||||
obj.FileData = fileData;
|
obj.FileDataOffset = data.Position;
|
||||||
|
data.SeekIfPossible((long)compressedSize, SeekOrigin.Current);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -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.2.0</Version>
|
<Version>3.3.0</Version>
|
||||||
|
|
||||||
<!-- Package Properties -->
|
<!-- Package Properties -->
|
||||||
<Authors>Matt Nadareski</Authors>
|
<Authors>Matt Nadareski</Authors>
|
||||||
@@ -36,8 +36,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.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
1296
SabreTools.Serialization.Readers/UDF.cs
Normal file
1296
SabreTools.Serialization.Readers/UDF.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<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="4.0.0" />
|
<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>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using SabreTools.Data.Extensions;
|
using SabreTools.Data.Extensions;
|
||||||
@@ -261,7 +262,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
{
|
{
|
||||||
writer.WriteStartElement("rom");
|
writer.WriteStartElement("rom");
|
||||||
writer.WriteRequiredAttributeString("name", rom.Name, throwOnError: true);
|
writer.WriteRequiredAttributeString("name", rom.Name, throwOnError: true);
|
||||||
writer.WriteRequiredAttributeString("size", rom.Size?.ToString(), throwOnError: true);
|
writer.WriteOptionalAttributeString("size", rom.Size?.ToString());
|
||||||
writer.WriteOptionalAttributeString("crc16", rom.CRC16);
|
writer.WriteOptionalAttributeString("crc16", rom.CRC16);
|
||||||
writer.WriteOptionalAttributeString("crc", rom.CRC);
|
writer.WriteOptionalAttributeString("crc", rom.CRC);
|
||||||
writer.WriteOptionalAttributeString("crc64", rom.CRC64);
|
writer.WriteOptionalAttributeString("crc64", rom.CRC64);
|
||||||
@@ -419,7 +420,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("y", video.Y.ToString());
|
writer.WriteOptionalAttributeString("y", video.Y.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspectx", video.AspectX.ToString());
|
writer.WriteOptionalAttributeString("aspectx", video.AspectX.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspecty", video.AspectY.ToString());
|
writer.WriteOptionalAttributeString("aspecty", video.AspectY.ToString());
|
||||||
writer.WriteOptionalAttributeString("freq", video.Freq?.ToString("0.000000"));
|
writer.WriteOptionalAttributeString("freq", video.Freq?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
writer.WriteEndElement(); // video
|
writer.WriteEndElement(); // video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -395,7 +396,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
||||||
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
||||||
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
||||||
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
||||||
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
||||||
@@ -843,7 +844,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteStartElement("rom");
|
writer.WriteStartElement("rom");
|
||||||
|
|
||||||
writer.WriteRequiredAttributeString("name", obj.Name);
|
writer.WriteRequiredAttributeString("name", obj.Name);
|
||||||
writer.WriteRequiredAttributeString("size", obj.Size?.ToString());
|
writer.WriteOptionalAttributeString("size", obj.Size?.ToString());
|
||||||
writer.WriteOptionalAttributeString("crc16", obj.CRC16);
|
writer.WriteOptionalAttributeString("crc16", obj.CRC16);
|
||||||
writer.WriteOptionalAttributeString("crc", obj.CRC);
|
writer.WriteOptionalAttributeString("crc", obj.CRC);
|
||||||
writer.WriteOptionalAttributeString("crc64", obj.CRC64);
|
writer.WriteOptionalAttributeString("crc64", obj.CRC64);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -393,7 +394,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
||||||
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
||||||
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
||||||
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
||||||
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
||||||
@@ -841,7 +842,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -393,7 +394,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo());
|
||||||
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
writer.WriteOptionalAttributeString("width", obj.Width?.ToString());
|
||||||
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height?.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString());
|
||||||
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString());
|
||||||
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString());
|
||||||
@@ -841,7 +842,7 @@ namespace SabreTools.Serialization.Writers
|
|||||||
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
writer.WriteOptionalAttributeString("height", obj.Height.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString());
|
||||||
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString());
|
||||||
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000"));
|
writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,10 +141,11 @@ namespace SabreTools.Serialization.Writers
|
|||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
writer.WriteOptionalElementString("start", obj.Start);
|
writer.WriteOptionalElementString("sha1", obj.SHA1);
|
||||||
writer.WriteOptionalElementString("type", obj.Type);
|
writer.WriteOptionalElementString("type", obj.Type);
|
||||||
writer.WriteOptionalElementString("hash", obj.Hash);
|
writer.WriteOptionalElementString("status", obj.Status);
|
||||||
writer.WriteOptionalElementString("remark", obj.Remark);
|
writer.WriteOptionalElementString("remark", obj.Remark);
|
||||||
|
writer.WriteOptionalElementString("start", obj.Start);
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
@@ -158,12 +159,12 @@ namespace SabreTools.Serialization.Writers
|
|||||||
{
|
{
|
||||||
writer.WriteStartElement("software");
|
writer.WriteStartElement("software");
|
||||||
|
|
||||||
writer.WriteRequiredElementString("title", obj.Title);
|
writer.WriteRequiredAttributeString("title", obj.Title);
|
||||||
writer.WriteOptionalElementString("genmsxid", obj.GenMSXID);
|
writer.WriteRequiredAttributeString("system", obj.System);
|
||||||
writer.WriteRequiredElementString("system", obj.System);
|
writer.WriteRequiredAttributeString("company", obj.Company);
|
||||||
writer.WriteRequiredElementString("company", obj.Company);
|
writer.WriteRequiredAttributeString("country", obj.Country);
|
||||||
writer.WriteRequiredElementString("year", obj.Year);
|
writer.WriteRequiredAttributeString("year", obj.Year);
|
||||||
writer.WriteRequiredElementString("country", obj.Country);
|
writer.WriteOptionalAttributeString("genmsxid", obj.GenMSXID);
|
||||||
|
|
||||||
if (obj.Dump is not null && obj.Dump.Length > 0)
|
if (obj.Dump is not null && obj.Dump.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -173,6 +174,14 @@ namespace SabreTools.Serialization.Writers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.Rom is not null && obj.Rom.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (var rom in obj.Rom)
|
||||||
|
{
|
||||||
|
WriteRomBase(rom, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.2.0</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.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.0" />
|
<PackageReference Include="SabreTools.IO" Version="2.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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.2.0</Version>
|
<Version>3.3.0</Version>
|
||||||
|
|
||||||
<!-- Package Properties -->
|
<!-- Package Properties -->
|
||||||
<Authors>Matt Nadareski</Authors>
|
<Authors>Matt Nadareski</Authors>
|
||||||
@@ -58,14 +58,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GrindCore" Version="0.9.0" />
|
<PackageReference Include="GrindCore" Version="0.10.0" />
|
||||||
<PackageReference Include="GrindCore.SharpCompress" Version="0.50.5" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
|
<PackageReference Include="GrindCore.SharpCompress" Version="0.50.8" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
|
||||||
<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="Net30.LinqBridge" Version="1.6.0.2" Condition="$(TargetFramework.StartsWith(`net2`))" />
|
<PackageReference Include="Net30.LinqBridge" Version="1.6.0.2" Condition="$(TargetFramework.StartsWith(`net2`))" />
|
||||||
<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="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="SabreTools.Hashing" Version="2.1.0" />
|
<PackageReference Include="SabreTools.Hashing" Version="2.2.0" />
|
||||||
<PackageReference Include="SabreTools.IO" Version="2.3.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>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -56,5 +57,66 @@ namespace SabreTools.Wrappers.Test
|
|||||||
var actual = MicrosoftCabinet.Create(data);
|
var actual = MicrosoftCabinet.Create(data);
|
||||||
Assert.Null(actual);
|
Assert.Null(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Extract
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Extract_LastBlockEndsTwoFiles()
|
||||||
|
{
|
||||||
|
// One MS-ZIP block that holds both files
|
||||||
|
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "MicrosoftCabinet", "mszip.cab");
|
||||||
|
var cabinet = MicrosoftCabinet.Create(File.ReadAllBytes(path), 0);
|
||||||
|
Assert.NotNull(cabinet);
|
||||||
|
|
||||||
|
string outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||||
|
cabinet.Extract(outputDirectory, includeDebug: true);
|
||||||
|
|
||||||
|
Assert.Equal("Hello, ", File.ReadAllText(Path.Combine(outputDirectory, "a.txt")));
|
||||||
|
Assert.Equal("World!", File.ReadAllText(Path.Combine(outputDirectory, "b.txt")));
|
||||||
|
Directory.Delete(outputDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("single.cab", null)]
|
||||||
|
[InlineData("part1.cab", null)]
|
||||||
|
[InlineData("part2.cab", null)]
|
||||||
|
[InlineData("part1.cab", "part2.cab")]
|
||||||
|
public void Extract_FilesNotKeptOpen(string filename, string? invalidPart)
|
||||||
|
{
|
||||||
|
// Copy of the test cabinets, so they can be checked and deleted afterwards
|
||||||
|
string directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||||
|
string outputDirectory = Path.Combine(directory, "output");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
foreach (string file in Directory.GetFiles(Path.Combine(Environment.CurrentDirectory, "TestData", "MicrosoftCabinet")))
|
||||||
|
{
|
||||||
|
File.Copy(file, Path.Combine(directory, Path.GetFileName(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidPart is not null)
|
||||||
|
File.WriteAllBytes(Path.Combine(directory, invalidPart), [.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = File.Open(Path.Combine(directory, filename), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
var cabinet = MicrosoftCabinet.Create(stream);
|
||||||
|
Assert.NotNull(cabinet);
|
||||||
|
cabinet.Extract(outputDirectory, includeDebug: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening a file exclusively fails while another handle to it is still open
|
||||||
|
foreach (string file in Directory.GetFiles(directory))
|
||||||
|
{
|
||||||
|
using var exclusive = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||||
|
Assert.True(exclusive.CanRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(directory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,5 +56,32 @@ namespace SabreTools.Wrappers.Test
|
|||||||
var actual = PortableExecutable.Create(data);
|
var actual = PortableExecutable.Create(data);
|
||||||
Assert.Null(actual);
|
Assert.Null(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExtractWiseOverlay_NotWise_FileNotKeptOpen()
|
||||||
|
{
|
||||||
|
// Any executable that is not a Wise installer, here a copy of this test assembly
|
||||||
|
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||||
|
string outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||||
|
File.Copy(typeof(PortableExecutableTests).Assembly.Location, path);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
var pe = PortableExecutable.Create(stream);
|
||||||
|
Assert.NotNull(pe);
|
||||||
|
Assert.False(pe.ExtractWiseOverlay(outputDirectory, includeDebug: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the file exclusively fails while another handle to it is still open
|
||||||
|
using var exclusive = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||||
|
Assert.True(exclusive.CanRead);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,11 +19,21 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="10.0.1" />
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\SabreTools.Serialization.Readers.Test\UDFImageBuilder.cs" Link="UDFImageBuilder.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/mszip.cab
Normal file
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/mszip.cab
Normal file
Binary file not shown.
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part1.cab
Normal file
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part1.cab
Normal file
Binary file not shown.
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part2.cab
Normal file
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part2.cab
Normal file
Binary file not shown.
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part3.cab
Normal file
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/part3.cab
Normal file
Binary file not shown.
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/single.cab
Normal file
BIN
SabreTools.Wrappers.Test/TestData/MicrosoftCabinet/single.cab
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user