69 Commits

Author SHA1 Message Date
475da761a5 [App] When an error occurs in a DAT importer worker, keep raising workers. 2025-07-29 16:39:34 +01:00
5d1ef578a1 [App] Ensure DAT importer model events run on UI thread. 2025-07-29 16:38:59 +01:00
0903d2b5cc [App] Create bundle when publishing for macOS. 2025-07-29 01:06:30 +01:00
6f54ed2e80 Add application icon for Windows. 2025-07-29 01:05:52 +01:00
244bf4c7a1 Add README 2025-07-28 18:38:46 +01:00
4eef291174 [App] Ensure Linux uses specific Skia version.
Yes, again.
2025-07-28 05:03:54 +01:00
627c59bcd0 [Project] Add global.json to allow it to compile if .NET 10 SDK is installed. 2025-07-28 05:02:04 +01:00
0c5760e745 Add more missing usings..... 2025-07-27 18:30:48 +01:00
34c8edbd91 Add version and copyright to assemblies. 2025-07-27 18:30:24 +01:00
bdcb5e72f7 [Blazor] Fix missing usings
(what is happening?)
2025-07-27 18:29:58 +01:00
8c5e0ef99e [App] Publish single file 2025-07-27 18:26:27 +01:00
ab804239bb [Blazor] Use settings from appsettings.json to configure folders and log levels. 2025-07-27 18:19:01 +01:00
b62f495824 [Blazor] Fix missing using. 2025-07-27 17:51:41 +01:00
7e7660bf6c [Dependencies] Update Roslynator analyzers to version 4.14.0 2025-07-27 17:50:46 +01:00
cb4d1706f8 [Dependencies] Update .NET to 9.0.7 2025-07-27 17:50:11 +01:00
2928ac3e1f Use ReadExactly instead EnsureRead extension. 2025-07-27 17:49:50 +01:00
8c116535c5 General refactor and cleanup. 2025-07-27 17:46:22 +01:00
a551e2474d [Blazor] Add localization. 2025-07-27 17:19:25 +01:00
396ee08030 [Blazor] Enable single file publish. 2025-07-27 16:15:55 +01:00
058b8e4b92 [Blazor] Implement importing ROMs. 2025-07-27 14:32:04 +01:00
a290996037 [Blazor] Move importing DAT to single thread.
Does not take so much longer, but UX is much improved.
2025-07-27 12:16:39 +01:00
e1ced26bc5 [Blazor] Update footer. 2025-07-27 04:04:52 +01:00
9c91d76561 [Blazor] Implement home page. 2025-07-27 04:02:17 +01:00
cf7186adbb [Blazor] Implement importing DAT folder. 2025-07-27 03:30:51 +01:00
122e397d0a [Blazor] Enable site wide server side rendering. 2025-07-27 03:30:32 +01:00
a2fc47cc5b [Blazor] On development show detailed circuit error information. 2025-07-27 03:29:55 +01:00
80f1c0e28e [Blazor] Set up settings on startup. 2025-07-27 03:29:21 +01:00
f4b87f68ec [Blazor] Add menu of actions. 2025-07-26 22:22:01 +01:00
dc4646512a [Blazor] Add database. 2025-07-26 21:16:23 +01:00
00d005ba98 [Blazor] Ensure folders exist. 2025-07-26 19:04:34 +01:00
9877f3886e Add debug logs. 2025-07-26 18:44:39 +01:00
5bb39929b4 [Blazor] Always use dark mode. 2025-07-26 18:11:05 +01:00
72b2eed55c Disable HTTPS redirection. 2025-07-26 18:00:30 +01:00
cca4af8d6d [Blazor] Add Serilog. 2025-07-26 17:59:18 +01:00
8df9bb7211 Add blazor skeleton. 2025-07-26 17:55:02 +01:00
52946eca2e Apply standard styles to all views. 2025-07-26 17:21:54 +01:00
de9ad80eef Remove spurious debug message. 2025-07-26 17:02:45 +01:00
a7985955b4 Add debug message of how long it took to import ROMs from folder. 2025-07-26 17:02:02 +01:00
054b9ffd0d When using internal decompressor, bypass extracting files to temporary folder.
Not needed and is slower.
2025-07-26 17:01:30 +01:00
a8921f640d Fix settings dialog not showing internal decompressor option correctly. 2025-07-26 17:00:21 +01:00
3e731115f2 Allow to read archives if no unar, but internal decompressor is allowed. 2025-07-26 17:00:02 +01:00
dc630f3e78 Fix reading internal decompressor settings in macOS. 2025-07-26 16:59:25 +01:00
5937e0d83e Add support for internal decompressor. 2025-07-26 04:17:26 +01:00
8f789484ca Add support for uncompressed repository. 2025-07-25 22:55:30 +01:00
0bda03afee Add support to compress repository with zstd. 2025-07-25 17:49:36 +01:00
bf19439e49 Use Serilog in EntityFramework Core logging. 2025-07-24 21:23:53 +01:00
1435aa10ee Adjust visibility of items in import ROM view. 2025-07-24 21:13:45 +01:00
55a6af0c74 Added Serilog for logging. 2025-07-24 16:20:22 +01:00
69fb3c768b Add SIMD checksum generation. 2025-07-24 15:46:20 +01:00
b0e0ba4502 Migrate to CommunityToolkit.Mvvm 2025-07-24 11:11:27 +01:00
c5da48fa47 Do not install Philips.CodeAnalysis in all projects. 2025-07-24 10:25:45 +01:00
03c69d3671 Remove manual requisite of SkiaSharp for Linux. 2025-07-24 10:25:14 +01:00
2d54cbdf23 Put database context outside of worker. Reduces memory usage by quite a lot. 2025-07-24 04:26:56 +01:00
134f8c7183 Sort list of files and archives. 2025-07-24 04:25:48 +01:00
368e243bca Use a different, more agressive, method to parallelize archive import, that better uses available threads/cores. 2025-07-24 04:24:48 +01:00
23efc69abc Use runtime compression to decompress ZIP files. 2025-07-24 04:22:33 +01:00
cf0f79338a Make detection of archive be multithread. 2025-07-23 14:55:09 +01:00
743b49c4f0 Use System.Text.Json to serialize lsar output. 2025-07-23 13:54:52 +01:00
8f9bf7036b Hide progress when ROM is imported (reduces RAM usage). 2025-07-23 13:54:34 +01:00
ed7d61d926 Fix missing partial class in import ROM view. 2025-07-23 13:36:41 +01:00
38cc8da72f Implement multi-threading import of ROM files. 2025-07-23 03:23:57 +01:00
7e009c7d66 [Application] Update CheckUnArFailed method to use Dispatcher for UI updates 2025-07-23 03:22:59 +01:00
dcade0fcc0 [Refactor] Replace object lock with static Lock in DatImporter for improved thread safety 2025-07-14 16:42:36 +01:00
d92707d691 [Workers] Enhance thread safety in FileImporter with database locking 2025-07-14 16:40:46 +01:00
adb597b455 [Refactor] Improve exception handling and variable declarations in FileImporter 2025-07-14 16:37:04 +01:00
b9adceed95 [App] Make importing DAT folder multithreaded. 2025-07-14 13:50:33 +01:00
8eaca3556a [Workers] Implement database locking in DatImporter for thread safety 2025-07-11 18:40:14 +01:00
32bbb55e61 [Refactor] Add new configuration for refactoring. 2025-07-11 18:39:44 +01:00
640c40d7f7 [Refactor] General refactoring 2025-07-09 14:34:43 +01:00
113 changed files with 8803 additions and 4014 deletions

View File

@@ -311,6 +311,7 @@ resharper_bad_switch_braces_indent_highlighting
resharper_bad_symbol_spaces_highlighting = warning
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = hint
resharper_can_simplify_dictionary_lookup_with_try_add_highlighting = warning
resharper_check_for_reference_equality_instead_1_highlighting = warning
resharper_check_for_reference_equality_instead_2_highlighting = warning
resharper_check_for_reference_equality_instead_3_highlighting = warning

View File

@@ -4,6 +4,7 @@
<NeutralLanguage>en</NeutralLanguage>
<LangVersion>default</LangVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -31,10 +32,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Philips.CodeAnalysis.MaintainabilityAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -1,50 +1,56 @@
<Project ToolsVersion="15.0">
<ItemGroup>
<PackageVersion Include="Aaru.Checksums.Native" Version="6.0.0-alpha.10"/>
<PackageVersion Include="AsyncFixer" Version="1.6.0"/>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.2"/>
<PackageVersion Include="Avalonia.Desktop" Version="11.3.2"/>
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.2"/>
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.2"/>
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.3.0"/>
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.2"/>
<PackageVersion Include="Avalonia" Version="11.3.2"/>
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
<PackageVersion Include="DotNetZip" Version="1.16.0"/>
<PackageVersion Include="EFCore.BulkExtensions" Version="9.0.1"/>
<PackageVersion Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2"/>
<PackageVersion Include="ErrorProne.NET.Structs" Version="0.1.2"/>
<PackageVersion Include="InclusivenessAnalyzer" Version="1.3.0"/>
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.6"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.6"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7"/>
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.7"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.7"/>
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter" Version="4.12.1"/>
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15"/>
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0"/>
<PackageVersion Include="Mono.Fuse.NETStandard" Version="1.1.0"/>
<PackageVersion Include="Philips.CodeAnalysis.MaintainabilityAnalyzers" Version="1.6.3"/>
<PackageVersion Include="plist-cil" Version="2.2.0"/>
<PackageVersion Include="Roslynator.Analyzers" Version="4.13.1"/>
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.13.1"/>
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.13.1"/>
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.0"/>
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.0"/>
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.0"/>
<PackageVersion Include="SabreTools.Models" Version="1.5.8"/>
<PackageVersion Include="Serilog" Version="4.3.0"/>
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0"/>
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2"/>
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0"/>
<PackageVersion Include="SharpCompress" Version="0.39.0"/>
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31"/>
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0"/>
<PackageVersion Include="Text.Analyzers" Version="4.14.0"/>
<PackageVersion Include="winfsp.net" Version="2.1.25156"/>
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageVersion Include="SabreTools.Hashing" Version="1.2.3"/>
<PackageVersion Include="SabreTools.IO" Version="1.4.13"/>
<PackageVersion Include="SabreTools.Matching" Version="1.3.4"/>
<PackageVersion Include="SabreTools.Skippers" Version="1.1.3"/>
<PackageVersion Include="System.IO.Compression" Version="4.3.0"/>
<PackageVersion Include="SharpCompress" Version="0.38.0"/>
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0"/>
<PackageVersion Include="ZstdSharp.Port" Version="0.8.1"/>
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6"/>
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.12.1"/>
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.12.1"/>
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" Condition="$(RuntimeIdentifier.StartsWith('linux'))" />
</ItemGroup>
</Project>

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
<div style="text-align: center;">
<img src="logo.png" alt="logo"/>
</div>
# ROM Repository Manager
ROM Repository Manager is a versatile tool designed to manage your cold storage of ROM sets effortlessly. Whether you're a retro gaming enthusiast or a dedicated collector, ROM Repository Manager streamlines the process of organizing, compressing, and deduplicating your ROM sets.
## Features
* **Organize ROMs**: Automatically sort and categorize ROM files based on the metadata found in widely available DAT files.
* **Deduplicate**: Optimize your storage by ensuring each ROM is stored only once, even if it appears in multiple sets or duplicates within the same set.
* **Compress:** Reduce the size of your ROMs with advanced compression options. Choose LZIP for maximum space savings or ZSTD for faster performance, both surpassing the typical ZIP files used in ROM sets.
* **Cross-Platform**: Compatible with Windows, macOS, and Linux.
* **User-Friendly Interface**: Intuitive and easy-to-use interface for seamless management. Choose between the desktop application for direct access or deploy it on a server to use through your browser (e.g., in a NAS). The choice is yours!
* **Virtual filesystem**: A standout feature of our desktop application is its ability to provide direct access to the ROM set from our repository. This is achieved through a virtual filesystem, allowing you to view and interact with the ROM set in its original folder structure without the need for extraction.
## Applications
ROM Repository Manager contains two different applications:
* **Desktop Application**: Built using .NET, this application provides a robust and feature-rich interface for managing ROM repositories on your computer.
* **Blazor Application**: A web-based application built with .NET Blazor, offering seamless access to ROM management features through your browser.
Both applications are available as pre-compiled binaries in the releases section. Users do not need to compile anything themselves.
## Usage
1. Launch the application.
2. Import the DAT files to get started.
3. Import your ROM sets.
4. Let ROM Repository Manager store your ROMs deduplicated and compressed.
## How does it compare?
When comparing with the ROM set from MAME 0.278, which occupies 78.2Gb compressed, 169Gb uncompressed:
* Using LZIP compression, ROM Repository Manager reduces the size to **68.5Gb**.
* Using ZSTD compression, the size is reduced to **71.4Gb**.
### Let the filesystem do it
A filesystem can improve our deduplication, adding block-level optimization on top of our file-level strategy, and achieving slightly better results. For example:
* **ZFS**: Reduces the repository to **71.3Gb** with ZSTD compression.
* **btrfs**: Reduces the repository to **85.6Gb**.
While these options deliver marginal gains in space and speed, they come at the cost of significantly higher RAM usage. Consider them only if your system has the resources to support it.
## Blazor Application Configuration
To configure the Blazor application, you need to modify the `appsettings.json` file. Below are the valid fields you can change, along with their purposes:
- **LogLevel**: Adjusts the level of detail written to the logs. Valid values are`Information` and `Debug`
- **LogFile**: Specifies the location where the log is written.
- **Repository**: Defines the location where the repository resides.
- **ImportRoms**: Sets the location where the application will look for ROMs to import.
- **ImportDats**: Sets the location where the application will look for DATs to import.
- **ExportRoms**: Specifies the location where the application will export ROMs.
- **ExportDats**: Specifies the location where the application will export DATs.
- **Database**: Defines the location where the repository database will reside.
- **Temporary**: Specifies the location where the application will store temporary files.
- **CompressionType**: Determines the repository compression algorithm used. This setting only applies to newly added ROMs. Valid values are `Lzip`, `Zstd` and `None`
## Contributing
Contributions are welcome! If you have ideas for new features or improvements, feel free to open an issue or submit a pull request.
## License
This project is licensed under the GPL version 3. See the LICENSE file for details.
---
For more information, visit the [GitHub repository](https://github.com/claunia/RomRepoMgr).
© 2020-2025 [Nat Portillo](https://www.natportillo.es)
Happy ROM managing!

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<base href="/"/>
<link href="@Assets["app.css"]" rel="stylesheet"/>
<link href="@Assets["RomRepoMgr.Blazor.styles.css"]" rel="stylesheet"/>
<ImportMap/>
<link href="favicon.ico" rel="icon" type="image/x-icon"/>
<HeadOutlet @rendermode="InteractiveServer"/>
</head>
<body>
<Routes @rendermode="InteractiveServer"/>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -0,0 +1,29 @@
@using Microsoft.Extensions.Localization
@using RomRepoMgr.Blazor.Resources
@implements IDialogContentComponent
@inject ILogger<ImportDats> Logger
@inject IStringLocalizer<Localization> Localizer
@inject IConfiguration Configuration
<FluentDialog Width="800px" Height="400px" Title="Import DATs" Modal="true" TrapFocus="true">
<FluentDialogBody>
<div>
<p hidden="@IsBusy">@string.Format(Localizer["DATs_will_be_imported_from"], path)</p>
<FluentLabel Color="@StatusColor">@StatusMessage</FluentLabel>
<FluentProgress Max="@ProgressMax" Min="@ProgressMin" Value="@ProgressValue" Visible="@ProgressVisible"/>
</div>
@if(Progress2Visible)
{
<div>
<p>@StatusMessage2</p>
<FluentProgress Max="@Progress2Max" Min="@Progress2Min" Value="@Progress2Value"/>
</div>
}
</FluentDialogBody>
<FluentDialogFooter>
<FluentStack Orientation="Orientation.Horizontal">
<FluentButton OnClick="@Start" Disabled="@IsBusy">@Localizer["Start"]</FluentButton>
<FluentButton OnClick="@CloseAsync" Disabled="@CannotClose">@Localizer["Close"]</FluentButton>
</FluentStack>
</FluentDialogFooter>
</FluentDialog>

View File

@@ -0,0 +1,181 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using Serilog;
using Serilog.Extensions.Logging;
using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
namespace RomRepoMgr.Blazor.Components.Dialogs;
public partial class ImportDats : ComponentBase
{
readonly Stopwatch _stopwatch = new();
string[] _datFiles;
int _listPosition;
int _workers;
string path;
public string StatusMessage { get; set; }
public bool IsBusy { get; set; }
[CascadingParameter]
public FluentDialog Dialog { get; set; }
public int? ProgressMax { get; set; }
public int? ProgressMin { get; set; }
public int? ProgressValue { get; set; }
public bool CannotClose { get; set; }
public bool ProgressVisible { get; set; }
public string StatusMessage2 { get; set; }
public bool Progress2Visible { get; set; }
public int? Progress2Max { get; set; }
public int? Progress2Min { get; set; }
public int? Progress2Value { get; set; }
public Color? StatusColor { get; set; }
/// <inheritdoc />
protected override void OnInitialized()
{
base.OnInitialized();
path = Configuration["DataFolders:ImportDats"] ?? "incoming-dats";
StatusMessage = "";
StatusMessage2 = "";
IsBusy = false;
CannotClose = false;
ProgressVisible = false;
Progress2Visible = false;
}
void Start()
{
IsBusy = true;
CannotClose = true;
ProgressVisible = true;
ProgressValue = null;
StatusMessage = Localizer["SearchingForFiles"];
_stopwatch.Restart();
string[] dats = Directory.GetFiles(path, "*.dat", SearchOption.AllDirectories);
string[] xmls = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);
_datFiles = dats.Concat(xmls).Order().ToArray();
_stopwatch.Stop();
Logger.LogDebug("Took {TotalSeconds} to find {Length} DAT files",
_stopwatch.Elapsed.TotalSeconds,
_datFiles.Length);
StatusMessage = string.Format(Localizer["FoundFiles"], _datFiles.Length);
ProgressMin = 0;
ProgressMax = _datFiles.Length;
ProgressValue = 0;
Progress2Visible = true;
_listPosition = 0;
_workers = 0;
StateHasChanged();
_stopwatch.Restart();
Logger.LogDebug("Starting to import DAT files...");
Import();
}
void Import()
{
if(_listPosition >= _datFiles.Length)
{
_ = InvokeAsync(() =>
{
ProgressVisible = false;
Progress2Visible = false;
StatusMessage = Localizer["Finished"];
CannotClose = false;
StateHasChanged();
});
_stopwatch.Stop();
Logger.LogDebug("Took {TotalSeconds} seconds to import {Length} DAT files",
_stopwatch.Elapsed.TotalSeconds,
_datFiles.Length);
return;
}
_ = InvokeAsync(() =>
{
StatusMessage = string.Format(Localizer["ImportingItem"], Path.GetFileName(_datFiles[_listPosition]));
ProgressValue = _listPosition;
StateHasChanged();
});
var worker = new DatImporter(_datFiles[_listPosition], null, new SerilogLoggerFactory(Log.Logger));
worker.ErrorOccurred += OnWorkerOnErrorOccurred;
worker.SetIndeterminateProgress += OnWorkerOnSetIndeterminateProgress;
worker.SetMessage += OnWorkerOnSetMessage;
worker.SetProgress += OnWorkerOnSetProgress;
worker.SetProgressBounds += OnWorkerOnSetProgressBounds;
worker.WorkFinished += OnWorkerOnWorkFinished;
_ = Task.Run(worker.Import);
}
void OnWorkerOnWorkFinished(object? sender, MessageEventArgs args)
{
_listPosition++;
Import();
}
void OnWorkerOnSetProgressBounds(object? sender, ProgressBoundsEventArgs args)
{
_ = InvokeAsync(() =>
{
Progress2Value = 0;
Progress2Max = (int?)args.Maximum;
Progress2Min = (int?)args.Minimum;
StateHasChanged();
});
}
void OnWorkerOnSetProgress(object? sender, ProgressEventArgs args)
{
_ = InvokeAsync(() =>
{
Progress2Value = (int?)args.Value;
StateHasChanged();
});
}
void OnWorkerOnSetMessage(object? sender, MessageEventArgs args)
{
_ = InvokeAsync(() =>
{
StatusMessage2 = args.Message;
StateHasChanged();
});
}
void OnWorkerOnSetIndeterminateProgress(object? sender, EventArgs args)
{
_ = InvokeAsync(() =>
{
Progress2Value = null;
StateHasChanged();
});
}
void OnWorkerOnErrorOccurred(object? sender, ErrorEventArgs args)
{
_ = InvokeAsync(() =>
{
_listPosition++;
Import();
});
}
Task CloseAsync() => Dialog.CloseAsync();
}

View File

@@ -0,0 +1,43 @@
@using Microsoft.Extensions.Localization
@using RomRepoMgr.Blazor.Resources
@using RomRepoMgr.Database
@implements IDialogContentComponent
@inject ILogger<ImportRoms> Logger
@inject Context _ctx
@inject IStringLocalizer<Localization> Localizer
@inject IConfiguration Configuration
<FluentDialog Width="1600px" Height="800px" Title="Import DATs" Modal="true" TrapFocus="true">
<FluentDialogBody>
<p hidden="@IsBusy">@string.Format(Localizer["ROMs_will_be_imported_from"], FolderPath)</p>
@if(NotYetStarted)
{
<FluentCheckbox @bind-Value="@RemoveFilesChecked" Label="@Localizer["RemoveFilesLabel"]"/>
<FluentCheckbox @bind-Value="@KnownOnlyChecked" Label="@Localizer["KnownOnlyLabel"]"/>
<FluentCheckbox @bind-Value="@RecurseArchivesChecked"
Label=@Localizer["RecurseArchivesLabel"]/>
}
@if(Importing)
{
<div>
<FluentLabel Color="@StatusMessageColor">@StatusMessage</FluentLabel>
<FluentProgress Max="@ProgressMax" Min="@ProgressMin" Value="@ProgressValue"/>
</div>
}
@if(Progress2Visible)
{
<div>
<FluentLabel>@StatusMessage2</FluentLabel>
<FluentProgress Max="@Progress2Max" Min="@Progress2Min" Value="@Progress2Value"/>
</div>
}
</FluentDialogBody>
<FluentDialogFooter>
<FluentStack Orientation="Orientation.Horizontal">
<FluentButton OnClick="@Start" Disabled="@IsBusy">@Localizer["Start"]</FluentButton>
<FluentButton OnClick="@CloseAsync" Disabled="@CannotClose">@Localizer["Close"]</FluentButton>
</FluentStack>
</FluentDialogFooter>
</FluentDialog>

View File

@@ -0,0 +1,355 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Database.Models;
using Serilog;
using SharpCompress.Readers;
namespace RomRepoMgr.Blazor.Components.Dialogs;
public partial class ImportRoms : ComponentBase
{
readonly Stopwatch _mainStopwatch = new();
readonly ConcurrentBag<DbDisk> _newDisks = [];
readonly ConcurrentBag<DbFile> _newFiles = [];
readonly ConcurrentBag<DbMedia> _newMedias = [];
readonly Stopwatch _stopwatch = new();
int _listPosition;
FileImporter _rootImporter;
PaginationState? pagination;
[CascadingParameter]
public FluentDialog Dialog { get; set; }
public bool IsBusy { get; set; }
public bool NotYetStarted { get; set; }
public bool RemoveFilesChecked { get; set; }
public bool KnownOnlyChecked { get; set; }
public bool RecurseArchivesChecked { get; set; }
public Color? StatusMessageColor { get; set; }
public string? StatusMessage { get; set; }
public int? ProgressMax { get; set; }
public int? ProgressMin { get; set; }
public int? ProgressValue { get; set; }
public string? StatusMessage2 { get; set; }
public int? Progress2Max { get; set; }
public int? Progress2Min { get; set; }
public int? Progress2Value { get; set; }
public string? FolderPath { get; set; }
public bool Importing { get; set; }
public bool Progress2Visible { get; set; }
public bool DataGridVisible { get; set; }
public bool CannotClose { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
FolderPath = Configuration["DataFolders:ImportRoms"] ?? "incoming";
IsBusy = false;
NotYetStarted = true;
RemoveFilesChecked = false;
KnownOnlyChecked = true;
RecurseArchivesChecked = true;
StatusMessage = "";
Importing = false;
Progress2Visible = false;
DataGridVisible = false;
Logger.LogDebug("ImportRoms dialog initialized with Path: {Path}", FolderPath);
}
void Start()
{
_rootImporter = new FileImporter(_ctx, _newFiles, _newDisks, _newMedias, KnownOnlyChecked, RemoveFilesChecked);
_rootImporter.SetMessage += SetMessage;
_rootImporter.SetIndeterminateProgress += SetIndeterminateProgress;
_rootImporter.SetProgress += SetProgress;
_rootImporter.SetProgressBounds += SetProgressBounds;
_rootImporter.Finished += EnumeratingFilesFinished;
ProgressValue = null;
Importing = true;
CannotClose = true;
IsBusy = true;
NotYetStarted = false;
_mainStopwatch.Start();
_ = Task.Run(() => _rootImporter.FindFiles(FolderPath));
}
void SetProgressBounds(object? sender, ProgressBoundsEventArgs e)
{
_ = InvokeAsync(() =>
{
ProgressValue = 0;
ProgressMax = (int?)e.Maximum;
ProgressMin = (int?)e.Minimum;
});
}
void SetProgress(object? sender, ProgressEventArgs e)
{
_ = InvokeAsync(() =>
{
ProgressValue = (int?)e.Value;
StateHasChanged();
});
}
void SetIndeterminateProgress(object? sender, EventArgs e)
{
_ = InvokeAsync(() =>
{
ProgressValue = null;
StateHasChanged();
});
}
void SetMessage(object? sender, MessageEventArgs e)
{
_ = InvokeAsync(() =>
{
StatusMessage = e.Message;
StateHasChanged();
});
}
Task CloseAsync() => Dialog.CloseAsync();
void EnumeratingFilesFinished(object? sender, EventArgs e)
{
_rootImporter.Finished -= EnumeratingFilesFinished;
if(RecurseArchivesChecked)
{
_ = InvokeAsync(() =>
{
Progress2Visible = true;
StateHasChanged();
});
_rootImporter.SetMessage2 += SetMessage2;
_rootImporter.SetIndeterminateProgress2 += SetIndeterminateProgress2;
_rootImporter.SetProgress2 += SetProgress2;
_rootImporter.SetProgressBounds2 += SetProgress2Bounds;
_rootImporter.Finished += CheckArchivesFinished;
_ = Task.Run(() =>
{
_stopwatch.Restart();
_rootImporter.SeparateFilesAndArchivesManaged();
});
}
else
ProcessFiles();
}
void SetProgress2Bounds(object? sender, ProgressBoundsEventArgs e)
{
_ = InvokeAsync(() =>
{
Progress2Value = 0;
Progress2Max = (int?)e.Maximum;
Progress2Min = (int?)e.Minimum;
});
}
void SetProgress2(object? sender, ProgressEventArgs e)
{
_ = InvokeAsync(() =>
{
Progress2Value = (int?)e.Value;
StateHasChanged();
});
}
void SetIndeterminateProgress2(object? sender, EventArgs e)
{
_ = InvokeAsync(() =>
{
Progress2Value = null;
StateHasChanged();
});
}
void SetMessage2(object? sender, MessageEventArgs e)
{
_ = InvokeAsync(() =>
{
StatusMessage2 = e.Message;
StateHasChanged();
});
}
void ProcessFiles()
{
_ = InvokeAsync(() =>
{
ProgressMin = 0;
ProgressMax = _rootImporter.Files.Count;
ProgressValue = 0;
NotYetStarted = false;
CannotClose = true;
IsBusy = true;
Importing = true;
StateHasChanged();
});
_listPosition = 0;
_stopwatch.Restart();
Parallel.ForEach(_rootImporter.Files,
file =>
{
_ = InvokeAsync(() =>
{
StatusMessage = string.Format(Localizer["ImportingItem"], Path.GetFileName(file));
ProgressValue = _listPosition;
StateHasChanged();
});
var worker = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
worker.ImportFile(file);
Interlocked.Increment(ref _listPosition);
});
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to process files", _stopwatch.Elapsed.TotalSeconds);
_rootImporter.SaveChanges();
_rootImporter.UpdateRomStats();
_ = InvokeAsync(() =>
{
ProgressMin = 0;
ProgressMax = 1;
ProgressValue = 0;
CannotClose = false;
IsBusy = false;
Importing = false;
StatusMessage = Localizer["Finished"];
StateHasChanged();
});
_listPosition = 0;
_mainStopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to import ROMs", _mainStopwatch.Elapsed.TotalSeconds);
}
void CheckArchivesFinished(object? sender, EventArgs e)
{
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to check archives", _stopwatch.Elapsed.TotalSeconds);
_ = InvokeAsync(() =>
{
Progress2Visible = false;
StateHasChanged();
});
_rootImporter.Finished -= CheckArchivesFinished;
ProcessArchivesManaged();
}
void ProcessArchivesManaged()
{
_ = InvokeAsync(() =>
{
ProgressMax = _rootImporter.Archives.Count;
ProgressMin = 0;
ProgressValue = 0;
Progress2Visible = false;
StateHasChanged();
});
_listPosition = 0;
_stopwatch.Restart();
// For each archive
Parallel.ForEach(_rootImporter.Archives,
archive =>
{
_ = InvokeAsync(() =>
{
StatusMessage =
string.Format(Localizer["ProcessingArchive"], Path.GetFileName(archive));
ProgressValue = _listPosition;
StateHasChanged();
});
// Create FileImporter
var archiveImporter = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
// Open archive
try
{
using var fs = new FileStream(archive, FileMode.Open, FileAccess.Read);
using IReader reader = ReaderFactory.Open(fs);
// Process files in archive
while(reader.MoveToNextEntry())
{
if(reader.Entry.IsDirectory) continue;
if(reader.Entry.Crc == 0 && KnownOnlyChecked) continue;
if(!archiveImporter.IsCrcInDb(reader.Entry.Crc) && KnownOnlyChecked) continue;
var worker = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
worker.ImportAndHashRom(reader.OpenEntryStream(),
reader.Entry.Key,
Path.Combine(Settings.Settings.Current.RepositoryPath,
Path.GetFileName(Path.GetTempFileName())),
reader.Entry.Size);
}
}
catch(InvalidOperationException) {}
finally
{
Interlocked.Increment(ref _listPosition);
}
});
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
_ = InvokeAsync(() =>
{
Progress2Visible = false;
StateHasChanged();
});
ProcessFiles();
}
}

View File

@@ -0,0 +1,24 @@
@inherits LayoutComponentBase
<FluentLayout>
<FluentHeader>
ROM Repository Manager
</FluentHeader>
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
<FluentBodyContent Class="body-content">
<div class="content">
@Body
</div>
</FluentBodyContent>
</FluentStack>
<FluentFooter>
<a href="https://www.natportillo.es" target="_blank">Copyright © 2020-2025 Natalia Portillo</a>
</FluentFooter>
<FluentDialogProvider/>
</FluentLayout>
<div data-nosnippet id="blazor-error-ui">
An unhandled error has occurred.
<a class="reload" href=".">Reload</a>
<span class="dismiss">🗙</span>
</div>

View File

@@ -0,0 +1,37 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if(ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that
occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() => RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -0,0 +1,40 @@
@page "/"
@using Microsoft.Extensions.Localization
@using RomRepoMgr.Blazor.Resources
@using RomRepoMgr.Database
@rendermode InteractiveServer
@inject IDialogService DialogService
@inject Context ctx
@inject IStringLocalizer<Localization> Localizer
<PageTitle>ROM Repository Manager</PageTitle>
<FluentToolbar>
<FluentButton OnClick="@ImportDatsAsync">@Localizer["ImportDats"]</FluentButton>
<FluentButton Disabled="true">@Localizer["ExportDat"]</FluentButton>
<FluentButton Disabled="true">@Localizer["RemoveDat"]</FluentButton>
<FluentButton OnClick="@ImportRomsAsync">@Localizer["ImportRoms"]</FluentButton>
<FluentButton Disabled="true">@Localizer["ExportRoms"]</FluentButton>
</FluentToolbar>
<FluentDataGrid @ref="romSetsGrid" Items="@RomSets" Style="width: 100%;" AutoFit="true" Pagination="@pagination"
AutoItemsPerPage="true" ResizableColumns="true">
<PropertyColumn Property="@(p => p.Name)" Title="@Localizer["RomSetNameLabel"]"/>
<PropertyColumn Property="@(p => p.Version)" Title="@Localizer["RomSetVersionLabel"]"/>
<PropertyColumn Property="@(p => p.Author)" Title="@Localizer["RomSetAuthorLabel"]"/>
<PropertyColumn Property="@(p => p.Category)" Title="@Localizer["RomSetCategoryLabel"]"/>
<PropertyColumn Property="@(p => p.Date)" Title="@Localizer["RomSetDateLabel"]"/>
<PropertyColumn Property="@(p => p.Description)" Title="@Localizer["RomSetDescriptionLabel"]"/>
<PropertyColumn Property="@(p => p.Comment)" Title="@Localizer["RomSetCommentLabel"]"/>
<PropertyColumn Property="@(p => p.Homepage)" Title="@Localizer["HomepageLabel"]"/>
<PropertyColumn Property="@(p => p.TotalMachines)" Title="@Localizer["RomSetTotalMachinesLabel"]"/>
<PropertyColumn Property="@(p => p.CompleteMachines)" Title="@Localizer["RomSetCompleteMachinesLabel"]"/>
<PropertyColumn Property="@(p => p.IncompleteMachines)" Title="@Localizer["RomSetIncompleteMachinesLabel"]"/>
<PropertyColumn Property="@(p => p.TotalRoms)" Title="@Localizer["RomSetTotalRomsLabel"]"/>
<PropertyColumn Property="@(p => p.HaveRoms)" Title="@Localizer["RomSetHaveRomsLabel"]"/>
<PropertyColumn Property="@(p => p.MissRoms)" Title="@Localizer["RomSetMissRomsLabel"]"/>
</FluentDataGrid>
<FluentPaginator State="@pagination"/>

View File

@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using RomRepoMgr.Blazor.Components.Dialogs;
using RomRepoMgr.Core.Models;
namespace RomRepoMgr.Blazor.Components.Pages;
public partial class Home : ComponentBase
{
readonly PaginationState pagination = new()
{
ItemsPerPage = 10
};
FluentDataGrid<RomSetModel>? romSetsGrid;
public IQueryable<RomSetModel>? RomSets { get; set; }
async Task ImportDatsAsync()
{
IDialogReference dialog = await DialogService.ShowDialogAsync<ImportDats>(new DialogParameters());
}
async Task ImportRomsAsync()
{
IDialogReference dialog = await DialogService.ShowDialogAsync<ImportRoms>(new DialogParameters());
}
/// <inheritdoc />
protected override void OnInitialized()
{
base.OnInitialized();
romSetsGrid?.SetLoadingState(true);
RomSets = ctx.RomSets.OrderBy(r => r.Name)
.ThenBy(r => r.Version)
.ThenBy(r => r.Date)
.ThenBy(r => r.Description)
.ThenBy(r => r.Comment)
.ThenBy(r => r.Filename)
.Select(r => new RomSetModel
{
Id = r.Id,
Author = r.Author,
Comment = r.Comment,
Date = r.Date,
Description = r.Description,
Filename = r.Filename,
Homepage = r.Homepage,
Name = r.Name,
Sha384 = r.Sha384,
Version = r.Version,
TotalMachines = r.Statistics.TotalMachines,
CompleteMachines = r.Statistics.CompleteMachines,
IncompleteMachines = r.Statistics.IncompleteMachines,
TotalRoms = r.Statistics.TotalRoms,
HaveRoms = r.Statistics.HaveRoms,
MissRoms = r.Statistics.MissRoms,
Category = r.Category
});
romSetsGrid?.SetLoadingState(false);
}
}

View File

@@ -0,0 +1,9 @@
@using RomRepoMgr.Blazor.Components.Layout
<FluentDesignTheme StorageName="theme" Mode="DesignThemeModes.Dark">
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView DefaultLayout="typeof(MainLayout)" RouteData="routeData"/>
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
</Found>
</Router>
</FluentDesignTheme>

View File

@@ -0,0 +1,12 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.FluentUI.AspNetCore.Components
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
@using Microsoft.JSInterop
@using RomRepoMgr.Blazor
@using RomRepoMgr.Blazor.Components

View File

@@ -0,0 +1,172 @@
using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
using Microsoft.FluentUI.AspNetCore.Components;
using RomRepoMgr.Blazor.Components;
using RomRepoMgr.Database;
using RomRepoMgr.Settings;
using Serilog;
using Serilog.Events;
// Start the application
Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().Enrich.FromLogContext().CreateLogger();
Log.Information("Welcome to ROM Repository Manager!");
Log.Information("Copyright © 2020-2025 Natalia Portillo");
// Configuration and settings
// We need the builder now
Log.Debug("Creating the builder...");
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Access the full configuration
Log.Debug("Creating the configuration reader...");
ConfigurationManager config = builder.Configuration;
string logFile = config["LogFile"] ?? "logs/rom-repo-mgr.log";
string defaultLogLevel = config["Logging:LogLevel:Default"] ?? "Information";
// Parse to LogEventLevel
if(!Enum.TryParse(defaultLogLevel, true, out LogEventLevel level))
{
// Fallback if parsing fails
#if DEBUG
level = LogEventLevel.Debug;
#else
level = LogEventLevel.Information;
#endif
}
// Now create a logger with the specified log level and log file
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.WriteTo.Console()
.WriteTo.File(logFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: 10 * 1048576)
.Enrich.FromLogContext()
.CreateLogger();
// Read the rest of the configuration and settings
Log.Debug("Reading configuration settings...");
string repoFolder = config["DataFolders:Repository"] ?? "repo";
string importRoms = config["DataFolders:ImportRoms"] ?? "incoming";
string importDats = config["DataFolders:ImportDats"] ?? "incoming-dats";
string exportRoms = config["DataFolders:ExportRoms"] ?? "export";
string exportDats = config["DataFolders:ExportDats"] ?? "export-dats";
string databaseFolder = config["DataFolders:Database"] ?? "db";
string temporaryFolder = config["DataFolders:Temporary"] ?? "tmp";
string compressionTypeString = config["CompressionType"] ?? "Zstd";
// Parse the compression type
if(!Enum.TryParse(compressionTypeString, true, out CompressionType compressionType))
{
// Fallback if parsing fails
compressionType = CompressionType.Zstd;
}
// Ensure the folders exist
Log.Information("Ensuring folders exist...");
string[] folders = [repoFolder, importRoms, importDats, databaseFolder, exportRoms, exportDats, temporaryFolder];
foreach(string folder in folders)
{
// Check File.Exists for symlinks or junctions
if(!Directory.Exists(folder) && !File.Exists(folder))
{
Log.Debug("Creating folder: {Folder}", folder);
Directory.CreateDirectory(folder);
}
else
Log.Debug("Folder already exists: {Folder}", folder);
}
// ✅ Plug Serilog into the host
builder.Host.UseSerilog();
// Add services to the container.
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddFluentUIComponents();
// Localization
builder.Services.AddLocalization();
Log.Debug("Creating database context...");
builder.Services.AddDbContextFactory<Context>(options =>
{
options.UseSqlite($"Data Source={databaseFolder}/database.db");
#if DEBUG
options.EnableSensitiveDataLogging();
options.LogTo(Log.Debug);
#else
options.LogTo(Log.Information, LogLevel.Information);
#endif
});
builder.Services.AddDataGridEntityFrameworkAdapter();
Log.Debug("Setting the settings...");
Settings.Current = new SetSettings
{
DatabasePath = Path.Combine(Environment.CurrentDirectory, databaseFolder, "database.db"),
TemporaryFolder = Path.Combine(Environment.CurrentDirectory, temporaryFolder),
RepositoryPath = Path.Combine(Environment.CurrentDirectory, repoFolder),
UseInternalDecompressor = true,
Compression = compressionType
};
Log.Debug("Building the application...");
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if(!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
// Localization
string[] supportedCultures = ["en", "es"];
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions().SetDefaultCulture("en")
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
Stopwatch stopwatch = new();
using(IServiceScope scope = app.Services.CreateScope())
{
IServiceProvider services = scope.ServiceProvider;
try
{
Log.Information("Updating the database...");
stopwatch.Start();
Context dbContext = services.GetRequiredService<Context>();
await dbContext.Database.MigrateAsync();
stopwatch.Stop();
Log.Debug("Database migration: {Elapsed} seconds", stopwatch.Elapsed.TotalSeconds);
}
catch(Exception ex)
{
Log.Error(ex, "An error occurred while updating the database");
return;
}
}
Log.Debug("Running the application...");
app.Run();

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7057;http://localhost:5079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,228 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RomRepoMgr.Blazor.Resources {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Localization {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localization() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RomRepoMgr.Blazor.Resources.Localization", typeof(Localization).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string ROMs_will_be_imported_from {
get {
return ResourceManager.GetString("ROMs_will_be_imported_from", resourceCulture);
}
}
public static string RemoveFilesLabel {
get {
return ResourceManager.GetString("RemoveFilesLabel", resourceCulture);
}
}
public static string KnownOnlyLabel {
get {
return ResourceManager.GetString("KnownOnlyLabel", resourceCulture);
}
}
public static string RecurseArchivesLabel {
get {
return ResourceManager.GetString("RecurseArchivesLabel", resourceCulture);
}
}
public static string Start {
get {
return ResourceManager.GetString("Start", resourceCulture);
}
}
public static string Close {
get {
return ResourceManager.GetString("Close", resourceCulture);
}
}
public static string ImportingItem {
get {
return ResourceManager.GetString("ImportingItem", resourceCulture);
}
}
public static string ProcessingArchive {
get {
return ResourceManager.GetString("ProcessingArchive", resourceCulture);
}
}
public static string Finished {
get {
return ResourceManager.GetString("Finished", resourceCulture);
}
}
public static string DATs_will_be_imported_from {
get {
return ResourceManager.GetString("DATs_will_be_imported_from", resourceCulture);
}
}
public static string SearchingForFiles {
get {
return ResourceManager.GetString("SearchingForFiles", resourceCulture);
}
}
public static string RomSetNameLabel {
get {
return ResourceManager.GetString("RomSetNameLabel", resourceCulture);
}
}
public static string RomSetVersionLabel {
get {
return ResourceManager.GetString("RomSetVersionLabel", resourceCulture);
}
}
public static string RomSetAuthorLabel {
get {
return ResourceManager.GetString("RomSetAuthorLabel", resourceCulture);
}
}
public static string RomSetCategoryLabel {
get {
return ResourceManager.GetString("RomSetCategoryLabel", resourceCulture);
}
}
public static string RomSetDateLabel {
get {
return ResourceManager.GetString("RomSetDateLabel", resourceCulture);
}
}
public static string RomSetDescriptionLabel {
get {
return ResourceManager.GetString("RomSetDescriptionLabel", resourceCulture);
}
}
public static string RomSetCommentLabel {
get {
return ResourceManager.GetString("RomSetCommentLabel", resourceCulture);
}
}
public static string HomepageLabel {
get {
return ResourceManager.GetString("HomepageLabel", resourceCulture);
}
}
public static string RomSetTotalMachinesLabel {
get {
return ResourceManager.GetString("RomSetTotalMachinesLabel", resourceCulture);
}
}
public static string RomSetCompleteMachinesLabel {
get {
return ResourceManager.GetString("RomSetCompleteMachinesLabel", resourceCulture);
}
}
public static string RomSetIncompleteMachinesLabel {
get {
return ResourceManager.GetString("RomSetIncompleteMachinesLabel", resourceCulture);
}
}
public static string RomSetTotalRomsLabel {
get {
return ResourceManager.GetString("RomSetTotalRomsLabel", resourceCulture);
}
}
public static string RomSetHaveRomsLabel {
get {
return ResourceManager.GetString("RomSetHaveRomsLabel", resourceCulture);
}
}
public static string RomSetMissRomsLabel {
get {
return ResourceManager.GetString("RomSetMissRomsLabel", resourceCulture);
}
}
public static string ImportDats {
get {
return ResourceManager.GetString("ImportDats", resourceCulture);
}
}
public static string ImportRoms {
get {
return ResourceManager.GetString("ImportRoms", resourceCulture);
}
}
public static string ExportDat {
get {
return ResourceManager.GetString("ExportDat", resourceCulture);
}
}
public static string RemoveDat {
get {
return ResourceManager.GetString("RemoveDat", resourceCulture);
}
}
public static string ExportRoms {
get {
return ResourceManager.GetString("ExportRoms", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,108 @@
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ROMs_will_be_imported_from" xml:space="preserve">
<value>Las ROMs se importarán de {0}.</value>
</data>
<data name="DATs_will_be_imported_from" xml:space="preserve">
<value>Los ficheros DAT se importarán de {0}.</value>
</data>
<data name="Close" xml:space="preserve">
<value>Cerrar</value>
</data>
<data name="ExportDat" xml:space="preserve">
<value>Exportar DAT</value>
</data>
<data name="ExportRoms" xml:space="preserve">
<value>Exportar ROMs</value>
</data>
<data name="Finished" xml:space="preserve">
<value>Terminado</value>
</data>
<data name="HomepageLabel" xml:space="preserve">
<value>Página web</value>
</data>
<data name="ImportingItem" xml:space="preserve">
<value>Importando {0}...</value>
</data>
<data name="ImportRoms" xml:space="preserve">
<value>Importar ROMs</value>
</data>
<data name="KnownOnlyLabel" xml:space="preserve">
<value>Importar solo archivos conocidos.</value>
</data>
<data name="ProcessingArchive" xml:space="preserve">
<value>Procesando archivo: {0}</value>
</data>
<data name="RecurseArchivesLabel" xml:space="preserve">
<value>Intentar detectar archivos comprimiedos e importar su contenido.</value>
</data>
<data name="RemoveDat" xml:space="preserve">
<value>Eliminar DAT</value>
</data>
<data name="RemoveFilesLabel" xml:space="preserve">
<value>Eliminar archivos después de importar satisfactoriamente.</value>
</data>
<data name="RomSetNameLabel" xml:space="preserve">
<value>Nombre</value>
</data>
<data name="RomSetMissRomsLabel" xml:space="preserve">
<value>Faltantes</value>
</data>
<data name="RomSetIncompleteMachinesLabel" xml:space="preserve">
<value>Incompletos</value>
</data>
<data name="RomSetHaveRomsLabel" xml:space="preserve">
<value>Presentes</value>
</data>
<data name="RomSetDescriptionLabel" xml:space="preserve">
<value>Descripción</value>
</data>
<data name="RomSetDateLabel" xml:space="preserve">
<value>Incompletos</value>
</data>
<data name="RomSetCompleteMachinesLabel" xml:space="preserve">
<value>Completos</value>
</data>
<data name="RomSetCommentLabel" xml:space="preserve">
<value>Comentario</value>
</data>
<data name="RomSetCategoryLabel" xml:space="preserve">
<value>Categoría</value>
</data>
<data name="RomSetAuthorLabel" xml:space="preserve">
<value>Categoría</value>
</data>
<data name="RomSetTotalMachinesLabel" xml:space="preserve">
<value>Juegos</value>
</data>
<data name="RomSetTotalRomsLabel" xml:space="preserve">
<value>ROMs</value>
</data>
<data name="RomSetVersionLabel" xml:space="preserve">
<value>Versión</value>
</data>
<data name="SearchingForFiles" xml:space="preserve">
<value>Buscando archivos...</value>
</data>
<data name="Start" xml:space="preserve">
<value>Comenzar</value>
</data>
<data name="ImportDats" xml:space="preserve">
<value>Importar DATs</value>
</data>
</root>

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ROMs_will_be_imported_from" xml:space="preserve">
<value>ROM files will be imported from {0}.</value>
</data>
<data name="RemoveFilesLabel" xml:space="preserve">
<value>Remove files after import successful.</value>
</data>
<data name="KnownOnlyLabel" xml:space="preserve">
<value>Only import known files.</value>
</data>
<data name="RecurseArchivesLabel" xml:space="preserve">
<value>Try to detect archives and import their contents.</value>
</data>
<data name="Start" xml:space="preserve">
<value>Start</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="ImportingItem" xml:space="preserve">
<value>Importing {0}...</value>
</data>
<data name="ProcessingArchive" xml:space="preserve">
<value>Processing archive: {0}</value>
</data>
<data name="Finished" xml:space="preserve">
<value>Finished</value>
</data>
<data name="DATs_will_be_imported_from" xml:space="preserve">
<value>DAT files will be imported from {0}.</value>
</data>
<data name="SearchingForFiles" xml:space="preserve">
<value>Searching for files...</value>
</data>
<data name="RomSetNameLabel" xml:space="preserve">
<value>Name</value>
</data>
<data name="RomSetVersionLabel" xml:space="preserve">
<value>Version</value>
</data>
<data name="RomSetAuthorLabel" xml:space="preserve">
<value>Author</value>
</data>
<data name="RomSetCategoryLabel" xml:space="preserve">
<value>Category</value>
</data>
<data name="RomSetDateLabel" xml:space="preserve">
<value>Date</value>
</data>
<data name="RomSetDescriptionLabel" xml:space="preserve">
<value>Description</value>
</data>
<data name="RomSetCommentLabel" xml:space="preserve">
<value>Comment</value>
</data>
<data name="HomepageLabel" xml:space="preserve">
<value>Homepage</value>
</data>
<data name="RomSetTotalMachinesLabel" xml:space="preserve">
<value>Games</value>
</data>
<data name="RomSetCompleteMachinesLabel" xml:space="preserve">
<value>Complete</value>
</data>
<data name="RomSetIncompleteMachinesLabel" xml:space="preserve">
<value>Incomplete</value>
</data>
<data name="RomSetTotalRomsLabel" xml:space="preserve">
<value>ROMs</value>
</data>
<data name="RomSetHaveRomsLabel" xml:space="preserve">
<value>Have</value>
</data>
<data name="RomSetMissRomsLabel" xml:space="preserve">
<value>Miss</value>
</data>
<data name="ImportDats" xml:space="preserve">
<value>Import DATs</value>
</data>
<data name="ImportRoms" xml:space="preserve">
<value>Import ROMs</value>
</data>
<data name="ExportDat" xml:space="preserve">
<value>Export DAT</value>
</data>
<data name="RemoveDat" xml:space="preserve">
<value>Remove DAT</value>
</data>
<data name="ExportRoms" xml:space="preserve">
<value>Export ROMs</value>
</data>
</root>

View File

@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<PropertyGroup>
<Version>1.0.0-beta.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
<ApplicationIcon>romrepomgr.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"/>
<PackageReference Include="Microsoft.Extensions.Localization"/>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components"/>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter"/>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons"/>
<PackageReference Include="Serilog"/>
<PackageReference Include="Serilog.AspNetCore"/>
<PackageReference Include="Serilog.Sinks.Console"/>
<PackageReference Include="Serilog.Sinks.File"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RomRepoMgr.Core\RomRepoMgr.Core.csproj"/>
<ProjectReference Include="..\RomRepoMgr.Database\RomRepoMgr.Database.csproj"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Localization.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Localization.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Localization.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Localization.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft": "Debug",
"Microsoft.Extensions.Localization": "Debug"
}
},
"CircuitOptions": {
"DetailedErrors": true
}
}

View File

@@ -0,0 +1,20 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"LogFile": "logs/romrepomgr.log",
"AllowedHosts": "*",
"DataFolders": {
"Repository": "repo",
"ImportRoms": "incoming",
"ImportDats": "incoming-dats",
"ExportRoms": "export",
"ExportDats": "export-dats",
"Database": "db",
"Temporary": "tmp"
},
"CompressionType": "Zstd"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

View File

@@ -0,0 +1,191 @@
@import '_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
body {
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
font-family: var(--body-font);
font-size: var(--type-ramp-base-font-size);
line-height: var(--type-ramp-base-line-height);
margin: 0;
}
.navmenu-icon {
display: none;
}
.main {
min-height: calc(100dvh - 86px);
color: var(--neutral-foreground-rest);
align-items: stretch !important;
}
.body-content {
align-self: stretch;
height: calc(100dvh - 86px) !important;
display: flex;
}
.content {
padding: 0.5rem 1.5rem;
align-self: stretch !important;
width: 100%;
}
.manage {
width: 100dvw;
}
footer {
background: var(--neutral-layer-4);
color: var(--neutral-foreground-rest);
align-items: center;
padding: 10px 10px;
}
footer a {
color: var(--neutral-foreground-rest);
text-decoration: none;
}
footer a:focus {
outline: 1px dashed;
outline-offset: 3px;
}
footer a:hover {
text-decoration: underline;
}
.alert {
border: 1px dashed var(--accent-fill-rest);
padding: 5px;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
margin: 20px 0;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::before {
content: "An error has occurred. "
}
.loading-progress {
position: relative;
display: block;
width: 8rem;
height: 8rem;
margin: 20vh auto 1rem auto;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}
@media (max-width: 600px) {
.header-gutters {
margin: 0.5rem 3rem 0.5rem 1.5rem !important;
}
[dir="rtl"] .header-gutters {
margin: 0.5rem 1.5rem 0.5rem 3rem !important;
}
.main {
flex-direction: column !important;
row-gap: 0 !important;
}
nav.sitenav {
width: 100%;
height: 100%;
}
#main-menu {
width: 100% !important;
}
#main-menu > div:first-child:is(.expander) {
display: none;
}
.navmenu {
width: 100%;
}
#navmenu-toggle {
appearance: none;
}
#navmenu-toggle ~ nav {
display: none;
}
#navmenu-toggle:checked ~ nav {
display: block;
}
.navmenu-icon {
cursor: pointer;
z-index: 10;
display: block;
position: absolute;
top: 15px;
left: unset;
right: 20px;
width: 20px;
height: 20px;
border: none;
}
[dir="rtl"] .navmenu-icon {
left: 20px;
right: unset;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -74,17 +74,17 @@ public static class FAT
byte bpbSignature;
byte fat32Signature;
ulong hugeSectors;
var fat32Id = new byte[8];
var msxId = new byte[6];
var dosOem = new byte[8];
var atariOem = new byte[6];
byte[] fat32Id = new byte[8];
byte[] msxId = new byte[6];
byte[] dosOem = new byte[8];
byte[] atariOem = new byte[6];
ushort bootable = 0;
var bpbSector = new byte[512];
var fatSector = new byte[512];
byte[] bpbSector = new byte[512];
byte[] fatSector = new byte[512];
imageStream.Position = 0;
imageStream.EnsureRead(bpbSector, 0, 512);
imageStream.EnsureRead(fatSector, 0, 512);
imageStream.ReadExactly(bpbSector, 0, 512);
imageStream.ReadExactly(fatSector, 0, 512);
Array.Copy(bpbSector, 0x02, atariOem, 0, 6);
Array.Copy(bpbSector, 0x03, dosOem, 0, 8);
@@ -111,13 +111,13 @@ public static class FAT
string oemString = Encoding.ASCII.GetString(dosOem);
var apricotBps = BitConverter.ToUInt16(bpbSector, 0x50);
byte apricotSpc = bpbSector[0x52];
var apricotReservedSecs = BitConverter.ToUInt16(bpbSector, 0x53);
byte apricotFatsNo = bpbSector[0x55];
var apricotRootEntries = BitConverter.ToUInt16(bpbSector, 0x56);
var apricotSectors = BitConverter.ToUInt16(bpbSector, 0x58);
var apricotFatSectors = BitConverter.ToUInt16(bpbSector, 0x5B);
ushort apricotBps = BitConverter.ToUInt16(bpbSector, 0x50);
byte apricotSpc = bpbSector[0x52];
ushort apricotReservedSecs = BitConverter.ToUInt16(bpbSector, 0x53);
byte apricotFatsNo = bpbSector[0x55];
ushort apricotRootEntries = BitConverter.ToUInt16(bpbSector, 0x56);
ushort apricotSectors = BitConverter.ToUInt16(bpbSector, 0x58);
ushort apricotFatSectors = BitConverter.ToUInt16(bpbSector, 0x5B);
bool apricotCorrectSpc = apricotSpc is 1 or 2 or 4 or 8 or 16 or 32 or 64;
@@ -197,20 +197,20 @@ public static class FAT
byte z80Di = bpbSector[0];
// First FAT1 sector resides at LBA 0x14
var fat1Sector0 = new byte[512];
byte[] fat1Sector0 = new byte[512];
imageStream.Position = 0x14 * 512;
imageStream.EnsureRead(fat1Sector0, 0, 512);
imageStream.ReadExactly(fat1Sector0, 0, 512);
// First FAT2 sector resides at LBA 0x1A
var fat2Sector0 = new byte[512];
byte[] fat2Sector0 = new byte[512];
imageStream.Position = 0x1A * 512;
imageStream.EnsureRead(fat2Sector0, 0, 512);
imageStream.ReadExactly(fat2Sector0, 0, 512);
bool equalFatIds = fat1Sector0[0] == fat2Sector0[0] && fat1Sector0[1] == fat2Sector0[1];
// Volume is software interleaved 2:1
var rootMs = new MemoryStream();
var tmp = new byte[512];
byte[] tmp = new byte[512];
foreach(long position in new long[]
{
@@ -218,17 +218,17 @@ public static class FAT
})
{
imageStream.Position = position * 512;
imageStream.EnsureRead(tmp, 0, 512);
imageStream.ReadExactly(tmp, 0, 512);
rootMs.Write(tmp, 0, tmp.Length);
}
byte[] rootDir = rootMs.ToArray();
var validRootDir = true;
bool validRootDir = true;
// Iterate all root directory
for(var e = 0; e < 96 * 32; e += 32)
for(int e = 0; e < 96 * 32; e += 32)
{
for(var c = 0; c < 11; c++)
for(int c = 0; c < 11; c++)
{
if((rootDir[c + e] >= 0x20 || rootDir[c + e] == 0x00 || rootDir[c + e] == 0x05) &&
rootDir[c + e] != 0xFF &&

View File

@@ -38,17 +38,17 @@ public static class Base32
var builder = new StringBuilder(bytes.Length * _inByteSize / _outByteSize);
// Position in the input buffer
var bytesPosition = 0;
int bytesPosition = 0;
// Offset inside a single byte that <bytesPosition> points to (from left to right)
// 0 - highest bit, 7 - lowest bit
var bytesSubPosition = 0;
int bytesSubPosition = 0;
// Byte to look up in the dictionary
byte outputBase32Byte = 0;
// The number of bits filled in the current output byte
var outputBase32BytePosition = 0;
int outputBase32BytePosition = 0;
// Iterate through input buffer until we reach past the end of it
while(bytesPosition < bytes.Length)
@@ -119,22 +119,22 @@ public static class Base32
string base32StringUpperCase = base32String.ToUpperInvariant();
// Prepare output byte array
var outputBytes = new byte[base32StringUpperCase.Length * _outByteSize / _inByteSize];
byte[] outputBytes = new byte[base32StringUpperCase.Length * _outByteSize / _inByteSize];
// Check the size
if(outputBytes.Length == 0) throw new ArgumentException(Localization.Base32_Not_enought_data);
// Position in the string
var base32Position = 0;
int base32Position = 0;
// Offset inside the character in the string
var base32SubPosition = 0;
int base32SubPosition = 0;
// Position within outputBytes array
var outputBytePosition = 0;
int outputBytePosition = 0;
// The number of bits filled in the current output byte
var outputByteSubPosition = 0;
int outputByteSubPosition = 0;
// Normally we would iterate on the input array but in this case we actually iterate on the output array
// We do it because output array doesn't have overflow bits, while input does and it will cause output array overflow if we don''t stop in time

View File

@@ -0,0 +1,37 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Authors.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Aaru.Checksums.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System.Diagnostics.CodeAnalysis;
namespace RomRepoMgr.Core.Checksums;
[SuppressMessage("ReSharper", "InconsistentNaming")]
static class Authors
{
internal const string NataliaPortillo = "Natalia Portillo";
}

View File

@@ -33,7 +33,7 @@
using System;
using System.Linq;
namespace Aaru.Helpers;
namespace RomRepoMgr.Core.Checksums;
/// <summary>
/// Converts base data types to an array of bytes, and an array of bytes to base data types. All info taken from

View File

@@ -0,0 +1,132 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : arm_simd.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
// The Chromium Authors
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Compute CRC32 checksum using ARM special instructions..
//
// --[ License ] --------------------------------------------------------------
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// Copyright 2017 The Chromium Authors. All rights reserved.
// ****************************************************************************/
using System;
using System.Runtime.Intrinsics.Arm;
namespace RomRepoMgr.Core.Checksums.CRC32;
static class ArmSimd
{
internal static uint Step64(byte[] buf, long len, uint crc)
{
uint c = crc;
int bufPos = 0;
while(len >= 64)
{
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
len -= 64;
}
while(len >= 8)
{
c = Crc32.Arm64.ComputeCrc32(c, BitConverter.ToUInt64(buf, bufPos));
bufPos += 8;
len -= 8;
}
while(len-- > 0) c = Crc32.ComputeCrc32(c, buf[bufPos++]);
return c;
}
internal static uint Step32(byte[] buf, long len, uint crc)
{
uint c = crc;
int bufPos = 0;
while(len >= 32)
{
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
len -= 32;
}
while(len >= 4)
{
c = Crc32.ComputeCrc32(c, BitConverter.ToUInt32(buf, bufPos));
bufPos += 4;
len -= 4;
}
while(len-- > 0) c = Crc32.ComputeCrc32(c, buf[bufPos++]);
return c;
}
}

View File

@@ -0,0 +1,229 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : clmul.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
// Wajdi Feghali <wajdi.k.feghali@intel.com>
// Jim Guilford <james.guilford@intel.com>
// Vinodh Gopal <vinodh.gopal@intel.com>
// Erdinc Ozturk <erdinc.ozturk@intel.com>
// Jim Kukunas <james.t.kukunas@linux.intel.com>
// Marian Beermann
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Compute the CRC32 using a parallelized folding approach with the PCLMULQDQ
// instruction.
//
// A white paper describing this algorithm can be found at:
// http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
//
// --[ License ] --------------------------------------------------------------
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from
// the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// Copyright (c) 2016 Marian Beermann (add support for initial value, restructuring)
// Copyright (C) 2013 Intel Corporation. All rights reserved.
// ****************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace RomRepoMgr.Core.Checksums.CRC32;
static class Clmul
{
static readonly uint[] _crcK =
[
0xccaa009e, 0x00000000, /* rk1 */ 0x751997d0, 0x00000001, /* rk2 */ 0xccaa009e, 0x00000000, /* rk5 */
0x63cd6124, 0x00000001, /* rk6 */ 0xf7011640, 0x00000001, /* rk7 */ 0xdb710640, 0x00000001 /* rk8 */
];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Fold4(ref Vector128<uint> xmmCRC0, ref Vector128<uint> xmmCRC1, ref Vector128<uint> xmmCRC2,
ref Vector128<uint> xmmCRC3)
{
var xmmFold4 = Vector128.Create(0xc6e41596, 0x00000001, 0x54442bd4, 0x00000001);
Vector128<uint> xTmp0 = xmmCRC0;
Vector128<uint> xTmp1 = xmmCRC1;
Vector128<uint> xTmp2 = xmmCRC2;
Vector128<uint> xTmp3 = xmmCRC3;
xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32();
xTmp0 = Pclmulqdq.CarrylessMultiply(xTmp0.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32();
Vector128<float> psCRC0 = xmmCRC0.AsSingle();
Vector128<float> psT0 = xTmp0.AsSingle();
Vector128<float> psRes0 = Sse.Xor(psCRC0, psT0);
xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32();
xTmp1 = Pclmulqdq.CarrylessMultiply(xTmp1.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32();
Vector128<float> psCRC1 = xmmCRC1.AsSingle();
Vector128<float> psT1 = xTmp1.AsSingle();
Vector128<float> psRes1 = Sse.Xor(psCRC1, psT1);
xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32();
xTmp2 = Pclmulqdq.CarrylessMultiply(xTmp2.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32();
Vector128<float> psCRC2 = xmmCRC2.AsSingle();
Vector128<float> psT2 = xTmp2.AsSingle();
Vector128<float> psRes2 = Sse.Xor(psCRC2, psT2);
xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), xmmFold4.AsUInt64(), 0x01).AsUInt32();
xTmp3 = Pclmulqdq.CarrylessMultiply(xTmp3.AsUInt64(), xmmFold4.AsUInt64(), 0x10).AsUInt32();
Vector128<float> psCRC3 = xmmCRC3.AsSingle();
Vector128<float> psT3 = xTmp3.AsSingle();
Vector128<float> psRes3 = Sse.Xor(psCRC3, psT3);
xmmCRC0 = psRes0.AsUInt32();
xmmCRC1 = psRes1.AsUInt32();
xmmCRC2 = psRes2.AsUInt32();
xmmCRC3 = psRes3.AsUInt32();
}
internal static uint Step(byte[] src, long len, uint initialCRC)
{
Vector128<uint> xmmInitial = Sse2.ConvertScalarToVector128UInt32(initialCRC);
Vector128<uint> xmmCRC0 = Sse2.ConvertScalarToVector128UInt32(0x9db42487);
Vector128<uint> xmmCRC1 = Vector128<uint>.Zero;
Vector128<uint> xmmCRC2 = Vector128<uint>.Zero;
Vector128<uint> xmmCRC3 = Vector128<uint>.Zero;
int bufPos = 0;
bool first = true;
/* fold 512 to 32 step variable declarations for ISO-C90 compat. */
var xmmMask = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000);
var xmmMask2 = Vector128.Create(0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
while((len -= 64) >= 0)
{
var xmmT0 = Vector128.Create(BitConverter.ToUInt32(src, bufPos),
BitConverter.ToUInt32(src, bufPos + 4),
BitConverter.ToUInt32(src, bufPos + 8),
BitConverter.ToUInt32(src, bufPos + 12));
bufPos += 16;
var xmmT1 = Vector128.Create(BitConverter.ToUInt32(src, bufPos),
BitConverter.ToUInt32(src, bufPos + 4),
BitConverter.ToUInt32(src, bufPos + 8),
BitConverter.ToUInt32(src, bufPos + 12));
bufPos += 16;
var xmmT2 = Vector128.Create(BitConverter.ToUInt32(src, bufPos),
BitConverter.ToUInt32(src, bufPos + 4),
BitConverter.ToUInt32(src, bufPos + 8),
BitConverter.ToUInt32(src, bufPos + 12));
bufPos += 16;
var xmmT3 = Vector128.Create(BitConverter.ToUInt32(src, bufPos),
BitConverter.ToUInt32(src, bufPos + 4),
BitConverter.ToUInt32(src, bufPos + 8),
BitConverter.ToUInt32(src, bufPos + 12));
bufPos += 16;
if(first)
{
first = false;
xmmT0 = Sse2.Xor(xmmT0, xmmInitial);
}
Fold4(ref xmmCRC0, ref xmmCRC1, ref xmmCRC2, ref xmmCRC3);
xmmCRC0 = Sse2.Xor(xmmCRC0, xmmT0);
xmmCRC1 = Sse2.Xor(xmmCRC1, xmmT1);
xmmCRC2 = Sse2.Xor(xmmCRC2, xmmT2);
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmT3);
}
/* fold 512 to 32 */
/*
* k1
*/
var crcFold = Vector128.Create(_crcK[0], _crcK[1], _crcK[2], _crcK[3]);
Vector128<uint> xTmp0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32();
xmmCRC0 = Pclmulqdq.CarrylessMultiply(xmmCRC0.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32();
xmmCRC1 = Sse2.Xor(xmmCRC1, xTmp0);
xmmCRC1 = Sse2.Xor(xmmCRC1, xmmCRC0);
Vector128<uint> xTmp1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32();
xmmCRC1 = Pclmulqdq.CarrylessMultiply(xmmCRC1.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32();
xmmCRC2 = Sse2.Xor(xmmCRC2, xTmp1);
xmmCRC2 = Sse2.Xor(xmmCRC2, xmmCRC1);
Vector128<uint> xTmp2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32();
xmmCRC2 = Pclmulqdq.CarrylessMultiply(xmmCRC2.AsUInt64(), crcFold.AsUInt64(), 0x01).AsUInt32();
xmmCRC3 = Sse2.Xor(xmmCRC3, xTmp2);
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2);
/*
* k5
*/
crcFold = Vector128.Create(_crcK[4], _crcK[5], _crcK[6], _crcK[7]);
xmmCRC0 = xmmCRC3;
xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32();
xmmCRC0 = Sse2.ShiftRightLogical128BitLane(xmmCRC0, 8);
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0);
xmmCRC0 = xmmCRC3;
xmmCRC3 = Sse2.ShiftLeftLogical128BitLane(xmmCRC3, 4);
xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32();
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC0);
xmmCRC3 = Sse2.And(xmmCRC3, xmmMask2);
/*
* k7
*/
xmmCRC1 = xmmCRC3;
xmmCRC2 = xmmCRC3;
crcFold = Vector128.Create(_crcK[8], _crcK[9], _crcK[10], _crcK[11]);
xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0).AsUInt32();
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2);
xmmCRC3 = Sse2.And(xmmCRC3, xmmMask);
xmmCRC2 = xmmCRC3;
xmmCRC3 = Pclmulqdq.CarrylessMultiply(xmmCRC3.AsUInt64(), crcFold.AsUInt64(), 0x10).AsUInt32();
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC2);
xmmCRC3 = Sse2.Xor(xmmCRC3, xmmCRC1);
/*
* could just as well write xmm_crc3[2], doing a movaps and truncating, but
* no real advantage - it's a tiny bit slower per call, while no additional CPUs
* would be supported by only requiring SSSE3 and CLMUL instead of SSE4.1 + CLMUL
*/
return ~Sse41.Extract(xmmCRC3, 2);
}
}

View File

@@ -27,48 +27,326 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
using RomRepoMgr.Core.Checksums.CRC32;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Implements a CRC32 algorithm</summary>
public sealed class Crc32Context : IChecksum
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed partial class Crc32Context : IChecksum
{
const uint CRC32_ISO_POLY = 0xEDB88320;
const uint CRC32_ISO_SEED = 0xFFFFFFFF;
readonly uint _finalSeed;
readonly uint[] _table;
uint _hashInt;
internal static readonly uint[][] ISOCrc32Table =
[
[
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
],
[
0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7,
0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF,
0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192, 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496,
0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A, 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E,
0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265,
0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D,
0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034,
0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C,
0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2,
0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA,
0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97, 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93,
0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F, 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B,
0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864, 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60,
0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768,
0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31,
0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539,
0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C,
0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484,
0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD,
0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1, 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5,
0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A, 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E,
0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522, 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026,
0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F,
0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277,
0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189,
0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81,
0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8,
0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0,
0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F, 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B,
0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27, 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23,
0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E, 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A,
0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72
],
[
0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685,
0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D,
0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29, 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5,
0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91, 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D,
0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065,
0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD,
0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315,
0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD,
0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45,
0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD,
0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835,
0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151, 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D,
0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579, 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5,
0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D,
0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5,
0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D,
0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05,
0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD,
0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75,
0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711, 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD,
0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339, 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5,
0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281, 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D,
0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895,
0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D,
0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5,
0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D,
0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5,
0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D,
0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9, 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625,
0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41, 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D,
0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89, 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555,
0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED
],
[
0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9,
0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701, 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056,
0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871, 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26,
0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E, 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9,
0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787,
0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68,
0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018,
0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7,
0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084,
0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B,
0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C, 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B,
0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3, 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4,
0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED, 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA,
0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755,
0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825,
0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA,
0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82,
0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D,
0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D,
0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2,
0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB, 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC,
0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04, 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953,
0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623,
0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC,
0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF,
0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50,
0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120,
0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF,
0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981,
0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639, 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E,
0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949, 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E,
0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1
],
[
0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10,
0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111, 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1,
0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52, 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92,
0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693, 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053,
0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314,
0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5,
0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496,
0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57,
0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459,
0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98,
0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB,
0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA, 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A,
0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D, 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D,
0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C,
0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF,
0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E,
0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82,
0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743,
0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00,
0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601, 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1,
0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546, 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386,
0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87, 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847,
0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404,
0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5,
0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB,
0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A,
0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349,
0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888,
0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF,
0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE, 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E,
0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D, 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D,
0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C
],
[
0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8,
0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5,
0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035, 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223,
0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258, 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E,
0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E,
0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3,
0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715,
0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578,
0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4,
0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9,
0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F,
0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834, 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22,
0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4, 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2,
0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F,
0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79,
0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14,
0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460,
0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D,
0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB,
0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680, 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496,
0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340, 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156,
0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D, 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B,
0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD,
0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0,
0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C,
0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61,
0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97,
0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA,
0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C, 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A,
0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41, 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957,
0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7, 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1,
0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC
],
[
0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E,
0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA, 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9,
0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653, 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240,
0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834, 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27,
0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712,
0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975,
0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC,
0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB,
0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7,
0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590,
0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A, 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739,
0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D, 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E,
0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678, 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B,
0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C,
0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5,
0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2,
0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C,
0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B,
0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2,
0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5,
0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3, 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0,
0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794, 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387,
0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E,
0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49,
0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105,
0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62,
0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB,
0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC,
0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899,
0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED, 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE,
0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044, 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457,
0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30
],
[
0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919,
0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC,
0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8, 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832,
0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D, 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387,
0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F,
0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA,
0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64,
0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1,
0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4,
0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041,
0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425, 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF,
0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90, 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A,
0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758, 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2,
0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217,
0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889,
0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C,
0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3,
0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776,
0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8,
0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D,
0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F, 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95,
0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA, 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520,
0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE,
0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B,
0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E,
0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B,
0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05,
0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0,
0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78,
0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937, 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD,
0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9, 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53,
0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6
]
];
readonly uint _finalSeed;
readonly IntPtr _nativeContext;
readonly uint[][] _table;
readonly bool _useIso;
readonly bool _useNative;
uint _hashInt;
/// <summary>Initializes the CRC32 table and seed as CRC32-ISO</summary>
public Crc32Context()
{
_hashInt = CRC32_ISO_SEED;
_finalSeed = CRC32_ISO_SEED;
_table = ISOCrc32Table;
_useIso = true;
_table = new uint[256];
if(!Native.IsSupported) return;
for(var i = 0; i < 256; i++)
{
var entry = (uint)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ CRC32_ISO_POLY;
else
entry >>= 1;
}
_table[i] = entry;
}
_nativeContext = crc32_init();
_useNative = _nativeContext != IntPtr.Zero;
}
/// <summary>Initializes the CRC32 table with a custom polynomial and seed</summary>
@@ -76,14 +354,40 @@ public sealed class Crc32Context : IChecksum
{
_hashInt = seed;
_finalSeed = seed;
_useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED;
_table = new uint[256];
for(var i = 0; i < 256; i++)
if(Native.IsSupported && _useIso)
{
var entry = (uint)i;
_nativeContext = crc32_init();
_useNative = _nativeContext != IntPtr.Zero;
}
else
_table = GenerateTable(polynomial);
}
for(var j = 0; j < 8; j++)
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial IntPtr crc32_init();
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial int crc32_update(IntPtr ctx, byte[] data, uint len);
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial int crc32_final(IntPtr ctx, ref uint crc);
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial void crc32_free(IntPtr ctx);
static uint[][] GenerateTable(uint polynomial)
{
uint[][] table = new uint[8][];
for(int i = 0; i < 8; i++) table[i] = new uint[256];
for(int i = 0; i < 256; i++)
{
uint entry = (uint)i;
for(int j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
@@ -91,42 +395,103 @@ public sealed class Crc32Context : IChecksum
entry >>= 1;
}
_table[i] = entry;
table[0][i] = entry;
}
for(int slice = 1; slice < 8; slice++)
{
for(int i = 0; i < 256; i++)
table[slice][i] = table[slice - 1][i] >> 8 ^ table[0][table[slice - 1][i] & 0xFF];
}
return table;
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len)
static void Step(ref uint previousCrc, uint[][] table, byte[] data, uint len, bool useIso, bool useNative,
IntPtr nativeContext)
{
for(var i = 0; i < len; i++) _hashInt = _hashInt >> 8 ^ _table[data[i] ^ _hashInt & 0xff];
}
if(useNative && useIso)
{
crc32_update(nativeContext, data, len);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
return;
}
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final() => BigEndianBitConverter.GetBytes(_hashInt ^ _finalSeed);
int currentPos = 0;
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
var crc32Output = new StringBuilder();
if(useIso)
{
if(Pclmulqdq.IsSupported && Sse41.IsSupported && Ssse3.IsSupported && Sse2.IsSupported)
{
// Only works in blocks of 16 bytes
uint blocks = len / 64;
for(var i = 0; i < BigEndianBitConverter.GetBytes(_hashInt ^ _finalSeed).Length; i++)
crc32Output.Append(BigEndianBitConverter.GetBytes(_hashInt ^ _finalSeed)[i].ToString("x2"));
if(blocks > 0)
{
previousCrc = ~Clmul.Step(data, blocks * 64, ~previousCrc);
return crc32Output.ToString();
currentPos = (int)(blocks * 64);
len -= blocks * 64;
}
if(len == 0) return;
}
if(Crc32.Arm64.IsSupported)
{
previousCrc = ArmSimd.Step64(data, len, previousCrc);
return;
}
if(Crc32.IsSupported)
{
previousCrc = ArmSimd.Step32(data, len, previousCrc);
return;
}
}
// Unroll according to Intel slicing by uint8_t
// http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
// http://sourceforge.net/projects/slicing-by-8/
const int unroll = 4;
const int bytesAtOnce = 8 * unroll;
uint crc = previousCrc;
while(len >= bytesAtOnce)
{
int unrolling;
for(unrolling = 0; unrolling < unroll; unrolling++)
{
uint one = BitConverter.ToUInt32(data, currentPos) ^ crc;
currentPos += 4;
uint two = BitConverter.ToUInt32(data, currentPos);
currentPos += 4;
crc = table[0][two >> 24 & 0xFF] ^
table[1][two >> 16 & 0xFF] ^
table[2][two >> 8 & 0xFF] ^
table[3][two & 0xFF] ^
table[4][one >> 24 & 0xFF] ^
table[5][one >> 16 & 0xFF] ^
table[6][one >> 8 & 0xFF] ^
table[7][one & 0xFF];
}
len -= bytesAtOnce;
}
while(len-- != 0) crc = crc >> 8 ^ table[0][crc & 0xFF ^ data[currentPos++]];
previousCrc = crc;
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
// ReSharper disable once ReturnTypeCanBeEnumerable.Global
public static byte[] File(string filename)
{
File(filename, out byte[] hash);
@@ -147,32 +512,41 @@ public sealed class Crc32Context : IChecksum
/// <param name="seed">CRC seed</param>
public static string File(string filename, out byte[] hash, uint polynomial, uint seed)
{
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED;
bool useNative = Native.IsSupported;
IntPtr nativeContext = IntPtr.Zero;
uint localhashInt = seed;
var localTable = new uint[256];
for(var i = 0; i < 256; i++)
if(useNative && useIso)
{
var entry = (uint)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
else
entry >>= 1;
}
localTable[i] = entry;
nativeContext = crc32_init();
useNative = nativeContext != IntPtr.Zero;
}
for(var i = 0; i < fileStream.Length; i++)
localhashInt = localhashInt >> 8 ^ localTable[fileStream.ReadByte() ^ localhashInt & 0xff];
var fileStream = new FileStream(filename, FileMode.Open);
localhashInt ^= seed;
hash = BigEndianBitConverter.GetBytes(localhashInt);
uint localHashInt = seed;
uint[][] localTable = GenerateTable(polynomial);
byte[] buffer = new byte[65536];
int read = fileStream.EnsureRead(buffer, 0, 65536);
while(read > 0)
{
Step(ref localHashInt, localTable, buffer, (uint)read, useIso, useNative, nativeContext);
read = fileStream.EnsureRead(buffer, 0, 65536);
}
localHashInt ^= seed;
if(useNative && useIso)
{
crc32_final(nativeContext, ref localHashInt);
crc32_free(nativeContext);
}
hash = BigEndianBitConverter.GetBytes(localHashInt);
var crc32Output = new StringBuilder();
@@ -198,29 +572,31 @@ public sealed class Crc32Context : IChecksum
/// <param name="seed">CRC seed</param>
public static string Data(byte[] data, uint len, out byte[] hash, uint polynomial, uint seed)
{
uint localhashInt = seed;
bool useIso = polynomial == CRC32_ISO_POLY && seed == CRC32_ISO_SEED;
bool useNative = Native.IsSupported;
IntPtr nativeContext = IntPtr.Zero;
var localTable = new uint[256];
for(var i = 0; i < 256; i++)
if(useNative && useIso)
{
var entry = (uint)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
else
entry >>= 1;
}
localTable[i] = entry;
nativeContext = crc32_init();
useNative = nativeContext != IntPtr.Zero;
}
for(var i = 0; i < len; i++) localhashInt = localhashInt >> 8 ^ localTable[data[i] ^ localhashInt & 0xff];
uint localHashInt = seed;
localhashInt ^= seed;
hash = BigEndianBitConverter.GetBytes(localhashInt);
uint[][] localTable = GenerateTable(polynomial);
Step(ref localHashInt, localTable, data, len, useIso, useNative, nativeContext);
localHashInt ^= seed;
if(useNative && useIso)
{
crc32_final(nativeContext, ref localHashInt);
crc32_free(nativeContext);
}
hash = BigEndianBitConverter.GetBytes(localHashInt);
var crc32Output = new StringBuilder();
@@ -233,4 +609,63 @@ public sealed class Crc32Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.CRC32_Name;
/// <inheritdoc />
public Guid Id => new("BCC4E18A-79CD-4B52-8A57-2B599E5176B3");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) =>
Step(ref _hashInt, _table, data, len, _useIso, _useNative, _nativeContext);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
uint crc = _hashInt ^ _finalSeed;
if(!_useNative || !_useIso) return BigEndianBitConverter.GetBytes(crc);
crc32_final(_nativeContext, ref crc);
crc32_free(_nativeContext);
return BigEndianBitConverter.GetBytes(crc);
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
uint crc = _hashInt ^ _finalSeed;
var crc32Output = new StringBuilder();
if(_useNative && _useIso)
{
crc32_final(_nativeContext, ref crc);
crc32_free(_nativeContext);
}
for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
crc32Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2"));
return crc32Output.ToString();
}
#endregion
}

View File

@@ -0,0 +1,122 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : clmul.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Compute the CRC64 using a parallelized folding approach with the PCLMULQDQ
// instruction.
//
// --[ License ] --------------------------------------------------------------
//
// This file is under the public domain:
// https://github.com/rawrunprotected/crc
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace RomRepoMgr.Core.Checksums.CRC64;
static class Clmul
{
static readonly byte[] _shuffleMasks =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x8f, 0x8e,
0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80
];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ShiftRight128(Vector128<ulong> initial, uint n, out Vector128<ulong> outLeft,
out Vector128<ulong> outRight)
{
uint maskPos = 16 - n;
var maskA = Vector128.Create(_shuffleMasks[maskPos],
_shuffleMasks[maskPos + 1],
_shuffleMasks[maskPos + 2],
_shuffleMasks[maskPos + 3],
_shuffleMasks[maskPos + 4],
_shuffleMasks[maskPos + 5],
_shuffleMasks[maskPos + 6],
_shuffleMasks[maskPos + 7],
_shuffleMasks[maskPos + 8],
_shuffleMasks[maskPos + 9],
_shuffleMasks[maskPos + 10],
_shuffleMasks[maskPos + 11],
_shuffleMasks[maskPos + 12],
_shuffleMasks[maskPos + 13],
_shuffleMasks[maskPos + 14],
_shuffleMasks[maskPos + 15]);
Vector128<byte> maskB = Sse2.Xor(maskA, Sse2.CompareEqual(Vector128<byte>.Zero, Vector128<byte>.Zero));
outLeft = Ssse3.Shuffle(initial.AsByte(), maskB).AsUInt64();
outRight = Ssse3.Shuffle(initial.AsByte(), maskA).AsUInt64();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static Vector128<ulong> Fold(Vector128<ulong> input, Vector128<ulong> foldConstants) =>
Sse2.Xor(Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x00),
Pclmulqdq.CarrylessMultiply(input, foldConstants, 0x11));
internal static ulong Step(ulong crc, byte[] data, uint length)
{
int bufPos = 16;
const ulong k1 = 0xe05dd497ca393ae4;
const ulong k2 = 0xdabe95afc7875f40;
const ulong mu = 0x9c3e466c172963d5;
const ulong pol = 0x92d8af2baf0e1e85;
var foldConstants1 = Vector128.Create(k1, k2);
var foldConstants2 = Vector128.Create(mu, pol);
var initialCrc = Vector128.Create(~crc, 0);
length -= 16;
// Initial CRC can simply be added to data
ShiftRight128(initialCrc, 0, out Vector128<ulong> crc0, out Vector128<ulong> crc1);
Vector128<ulong> accumulator =
Sse2.Xor(Fold(Sse2.Xor(crc0,
Vector128.Create(BitConverter.ToUInt64(data, 0), BitConverter.ToUInt64(data, 8))),
foldConstants1),
crc1);
while(length >= 32)
{
accumulator =
Fold(Sse2.Xor(Vector128.Create(BitConverter.ToUInt64(data, bufPos),
BitConverter.ToUInt64(data, bufPos + 8)),
accumulator),
foldConstants1);
length -= 16;
bufPos += 16;
}
Vector128<ulong> p = Sse2.Xor(accumulator,
Vector128.Create(BitConverter.ToUInt64(data, bufPos),
BitConverter.ToUInt64(data, bufPos + 8)));
Vector128<ulong> r = Sse2.Xor(Pclmulqdq.CarrylessMultiply(p, foldConstants1, 0x10),
Sse2.ShiftRightLogical128BitLane(p, 8));
// Final Barrett reduction
Vector128<ulong> t1 = Pclmulqdq.CarrylessMultiply(r, foldConstants2, 0x00);
Vector128<ulong> t2 = Sse2.Xor(Sse2.Xor(Pclmulqdq.CarrylessMultiply(t1, foldConstants2, 0x10),
Sse2.ShiftLeftLogical128BitLane(t1, 8)),
r);
return ~((ulong)Sse41.Extract(t2.AsUInt32(), 3) << 32 | Sse41.Extract(t2.AsUInt32(), 2));
}
}

View File

@@ -27,63 +27,312 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
using RomRepoMgr.Core.Checksums.CRC64;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Implements a CRC64 algorithm</summary>
public sealed class Crc64Context : IChecksum
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
public sealed partial class Crc64Context : IChecksum
{
/// <summary>ECMA CRC64 polynomial</summary>
const ulong CRC64_ECMA_POLY = 0xC96C5795D7870F42;
/// <summary>ECMA CRC64 seed</summary>
const ulong CRC64_ECMA_SEED = 0xFFFFFFFFFFFFFFFF;
readonly ulong _finalSeed;
readonly ulong[] _table;
ulong _hashInt;
static readonly ulong[][] _ecmaCrc64Table =
[
[
0x0000000000000000, 0xB32E4CBE03A75F6F, 0xF4843657A840A05B, 0x47AA7AE9ABE7FF34, 0x7BD0C384FF8F5E33,
0xC8FE8F3AFC28015C, 0x8F54F5D357CFFE68, 0x3C7AB96D5468A107, 0xF7A18709FF1EBC66, 0x448FCBB7FCB9E309,
0x0325B15E575E1C3D, 0xB00BFDE054F94352, 0x8C71448D0091E255, 0x3F5F08330336BD3A, 0x78F572DAA8D1420E,
0xCBDB3E64AB761D61, 0x7D9BA13851336649, 0xCEB5ED8652943926, 0x891F976FF973C612, 0x3A31DBD1FAD4997D,
0x064B62BCAEBC387A, 0xB5652E02AD1B6715, 0xF2CF54EB06FC9821, 0x41E11855055BC74E, 0x8A3A2631AE2DDA2F,
0x39146A8FAD8A8540, 0x7EBE1066066D7A74, 0xCD905CD805CA251B, 0xF1EAE5B551A2841C, 0x42C4A90B5205DB73,
0x056ED3E2F9E22447, 0xB6409F5CFA457B28, 0xFB374270A266CC92, 0x48190ECEA1C193FD, 0x0FB374270A266CC9,
0xBC9D3899098133A6, 0x80E781F45DE992A1, 0x33C9CD4A5E4ECDCE, 0x7463B7A3F5A932FA, 0xC74DFB1DF60E6D95,
0x0C96C5795D7870F4, 0xBFB889C75EDF2F9B, 0xF812F32EF538D0AF, 0x4B3CBF90F69F8FC0, 0x774606FDA2F72EC7,
0xC4684A43A15071A8, 0x83C230AA0AB78E9C, 0x30EC7C140910D1F3, 0x86ACE348F355AADB, 0x3582AFF6F0F2F5B4,
0x7228D51F5B150A80, 0xC10699A158B255EF, 0xFD7C20CC0CDAF4E8, 0x4E526C720F7DAB87, 0x09F8169BA49A54B3,
0xBAD65A25A73D0BDC, 0x710D64410C4B16BD, 0xC22328FF0FEC49D2, 0x85895216A40BB6E6, 0x36A71EA8A7ACE989,
0x0ADDA7C5F3C4488E, 0xB9F3EB7BF06317E1, 0xFE5991925B84E8D5, 0x4D77DD2C5823B7BA, 0x64B62BCAEBC387A1,
0xD7986774E864D8CE, 0x90321D9D438327FA, 0x231C512340247895, 0x1F66E84E144CD992, 0xAC48A4F017EB86FD,
0xEBE2DE19BC0C79C9, 0x58CC92A7BFAB26A6, 0x9317ACC314DD3BC7, 0x2039E07D177A64A8, 0x67939A94BC9D9B9C,
0xD4BDD62ABF3AC4F3, 0xE8C76F47EB5265F4, 0x5BE923F9E8F53A9B, 0x1C4359104312C5AF, 0xAF6D15AE40B59AC0,
0x192D8AF2BAF0E1E8, 0xAA03C64CB957BE87, 0xEDA9BCA512B041B3, 0x5E87F01B11171EDC, 0x62FD4976457FBFDB,
0xD1D305C846D8E0B4, 0x96797F21ED3F1F80, 0x2557339FEE9840EF, 0xEE8C0DFB45EE5D8E, 0x5DA24145464902E1,
0x1A083BACEDAEFDD5, 0xA9267712EE09A2BA, 0x955CCE7FBA6103BD, 0x267282C1B9C65CD2, 0x61D8F8281221A3E6,
0xD2F6B4961186FC89, 0x9F8169BA49A54B33, 0x2CAF25044A02145C, 0x6B055FEDE1E5EB68, 0xD82B1353E242B407,
0xE451AA3EB62A1500, 0x577FE680B58D4A6F, 0x10D59C691E6AB55B, 0xA3FBD0D71DCDEA34, 0x6820EEB3B6BBF755,
0xDB0EA20DB51CA83A, 0x9CA4D8E41EFB570E, 0x2F8A945A1D5C0861, 0x13F02D374934A966, 0xA0DE61894A93F609,
0xE7741B60E174093D, 0x545A57DEE2D35652, 0xE21AC88218962D7A, 0x5134843C1B317215, 0x169EFED5B0D68D21,
0xA5B0B26BB371D24E, 0x99CA0B06E7197349, 0x2AE447B8E4BE2C26, 0x6D4E3D514F59D312, 0xDE6071EF4CFE8C7D,
0x15BB4F8BE788911C, 0xA6950335E42FCE73, 0xE13F79DC4FC83147, 0x521135624C6F6E28, 0x6E6B8C0F1807CF2F,
0xDD45C0B11BA09040, 0x9AEFBA58B0476F74, 0x29C1F6E6B3E0301B, 0xC96C5795D7870F42, 0x7A421B2BD420502D,
0x3DE861C27FC7AF19, 0x8EC62D7C7C60F076, 0xB2BC941128085171, 0x0192D8AF2BAF0E1E, 0x4638A2468048F12A,
0xF516EEF883EFAE45, 0x3ECDD09C2899B324, 0x8DE39C222B3EEC4B, 0xCA49E6CB80D9137F, 0x7967AA75837E4C10,
0x451D1318D716ED17, 0xF6335FA6D4B1B278, 0xB199254F7F564D4C, 0x02B769F17CF11223, 0xB4F7F6AD86B4690B,
0x07D9BA1385133664, 0x4073C0FA2EF4C950, 0xF35D8C442D53963F, 0xCF273529793B3738, 0x7C0979977A9C6857,
0x3BA3037ED17B9763, 0x888D4FC0D2DCC80C, 0x435671A479AAD56D, 0xF0783D1A7A0D8A02, 0xB7D247F3D1EA7536,
0x04FC0B4DD24D2A59, 0x3886B22086258B5E, 0x8BA8FE9E8582D431, 0xCC0284772E652B05, 0x7F2CC8C92DC2746A,
0x325B15E575E1C3D0, 0x8175595B76469CBF, 0xC6DF23B2DDA1638B, 0x75F16F0CDE063CE4, 0x498BD6618A6E9DE3,
0xFAA59ADF89C9C28C, 0xBD0FE036222E3DB8, 0x0E21AC88218962D7, 0xC5FA92EC8AFF7FB6, 0x76D4DE52895820D9,
0x317EA4BB22BFDFED, 0x8250E80521188082, 0xBE2A516875702185, 0x0D041DD676D77EEA, 0x4AAE673FDD3081DE,
0xF9802B81DE97DEB1, 0x4FC0B4DD24D2A599, 0xFCEEF8632775FAF6, 0xBB44828A8C9205C2, 0x086ACE348F355AAD,
0x34107759DB5DFBAA, 0x873E3BE7D8FAA4C5, 0xC094410E731D5BF1, 0x73BA0DB070BA049E, 0xB86133D4DBCC19FF,
0x0B4F7F6AD86B4690, 0x4CE50583738CB9A4, 0xFFCB493D702BE6CB, 0xC3B1F050244347CC, 0x709FBCEE27E418A3,
0x3735C6078C03E797, 0x841B8AB98FA4B8F8, 0xADDA7C5F3C4488E3, 0x1EF430E13FE3D78C, 0x595E4A08940428B8,
0xEA7006B697A377D7, 0xD60ABFDBC3CBD6D0, 0x6524F365C06C89BF, 0x228E898C6B8B768B, 0x91A0C532682C29E4,
0x5A7BFB56C35A3485, 0xE955B7E8C0FD6BEA, 0xAEFFCD016B1A94DE, 0x1DD181BF68BDCBB1, 0x21AB38D23CD56AB6,
0x9285746C3F7235D9, 0xD52F0E859495CAED, 0x6601423B97329582, 0xD041DD676D77EEAA, 0x636F91D96ED0B1C5,
0x24C5EB30C5374EF1, 0x97EBA78EC690119E, 0xAB911EE392F8B099, 0x18BF525D915FEFF6, 0x5F1528B43AB810C2,
0xEC3B640A391F4FAD, 0x27E05A6E926952CC, 0x94CE16D091CE0DA3, 0xD3646C393A29F297, 0x604A2087398EADF8,
0x5C3099EA6DE60CFF, 0xEF1ED5546E415390, 0xA8B4AFBDC5A6ACA4, 0x1B9AE303C601F3CB, 0x56ED3E2F9E224471,
0xE5C372919D851B1E, 0xA26908783662E42A, 0x114744C635C5BB45, 0x2D3DFDAB61AD1A42, 0x9E13B115620A452D,
0xD9B9CBFCC9EDBA19, 0x6A978742CA4AE576, 0xA14CB926613CF817, 0x1262F598629BA778, 0x55C88F71C97C584C,
0xE6E6C3CFCADB0723, 0xDA9C7AA29EB3A624, 0x69B2361C9D14F94B, 0x2E184CF536F3067F, 0x9D36004B35545910,
0x2B769F17CF112238, 0x9858D3A9CCB67D57, 0xDFF2A94067518263, 0x6CDCE5FE64F6DD0C, 0x50A65C93309E7C0B,
0xE388102D33392364, 0xA4226AC498DEDC50, 0x170C267A9B79833F, 0xDCD7181E300F9E5E, 0x6FF954A033A8C131,
0x28532E49984F3E05, 0x9B7D62F79BE8616A, 0xA707DB9ACF80C06D, 0x14299724CC279F02, 0x5383EDCD67C06036,
0xE0ADA17364673F59
],
[
0x0000000000000000, 0x54E979925CD0F10D, 0xA9D2F324B9A1E21A, 0xFD3B8AB6E5711317, 0xC17D4962DC4DDAB1,
0x959430F0809D2BBC, 0x68AFBA4665EC38AB, 0x3C46C3D4393CC9A6, 0x10223DEE1795ABE7, 0x44CB447C4B455AEA,
0xB9F0CECAAE3449FD, 0xED19B758F2E4B8F0, 0xD15F748CCBD87156, 0x85B60D1E9708805B, 0x788D87A87279934C,
0x2C64FE3A2EA96241, 0x20447BDC2F2B57CE, 0x74AD024E73FBA6C3, 0x899688F8968AB5D4, 0xDD7FF16ACA5A44D9,
0xE13932BEF3668D7F, 0xB5D04B2CAFB67C72, 0x48EBC19A4AC76F65, 0x1C02B80816179E68, 0x3066463238BEFC29,
0x648F3FA0646E0D24, 0x99B4B516811F1E33, 0xCD5DCC84DDCFEF3E, 0xF11B0F50E4F32698, 0xA5F276C2B823D795,
0x58C9FC745D52C482, 0x0C2085E60182358F, 0x4088F7B85E56AF9C, 0x14618E2A02865E91, 0xE95A049CE7F74D86,
0xBDB37D0EBB27BC8B, 0x81F5BEDA821B752D, 0xD51CC748DECB8420, 0x28274DFE3BBA9737, 0x7CCE346C676A663A,
0x50AACA5649C3047B, 0x0443B3C41513F576, 0xF9783972F062E661, 0xAD9140E0ACB2176C, 0x91D78334958EDECA,
0xC53EFAA6C95E2FC7, 0x380570102C2F3CD0, 0x6CEC098270FFCDDD, 0x60CC8C64717DF852, 0x3425F5F62DAD095F,
0xC91E7F40C8DC1A48, 0x9DF706D2940CEB45, 0xA1B1C506AD3022E3, 0xF558BC94F1E0D3EE, 0x086336221491C0F9,
0x5C8A4FB0484131F4, 0x70EEB18A66E853B5, 0x2407C8183A38A2B8, 0xD93C42AEDF49B1AF, 0x8DD53B3C839940A2,
0xB193F8E8BAA58904, 0xE57A817AE6757809, 0x18410BCC03046B1E, 0x4CA8725E5FD49A13, 0x8111EF70BCAD5F38,
0xD5F896E2E07DAE35, 0x28C31C54050CBD22, 0x7C2A65C659DC4C2F, 0x406CA61260E08589, 0x1485DF803C307484,
0xE9BE5536D9416793, 0xBD572CA48591969E, 0x9133D29EAB38F4DF, 0xC5DAAB0CF7E805D2, 0x38E121BA129916C5,
0x6C0858284E49E7C8, 0x504E9BFC77752E6E, 0x04A7E26E2BA5DF63, 0xF99C68D8CED4CC74, 0xAD75114A92043D79,
0xA15594AC938608F6, 0xF5BCED3ECF56F9FB, 0x088767882A27EAEC, 0x5C6E1E1A76F71BE1, 0x6028DDCE4FCBD247,
0x34C1A45C131B234A, 0xC9FA2EEAF66A305D, 0x9D135778AABAC150, 0xB177A9428413A311, 0xE59ED0D0D8C3521C,
0x18A55A663DB2410B, 0x4C4C23F46162B006, 0x700AE020585E79A0, 0x24E399B2048E88AD, 0xD9D81304E1FF9BBA,
0x8D316A96BD2F6AB7, 0xC19918C8E2FBF0A4, 0x9570615ABE2B01A9, 0x684BEBEC5B5A12BE, 0x3CA2927E078AE3B3,
0x00E451AA3EB62A15, 0x540D28386266DB18, 0xA936A28E8717C80F, 0xFDDFDB1CDBC73902, 0xD1BB2526F56E5B43,
0x85525CB4A9BEAA4E, 0x7869D6024CCFB959, 0x2C80AF90101F4854, 0x10C66C44292381F2, 0x442F15D675F370FF,
0xB9149F60908263E8, 0xEDFDE6F2CC5292E5, 0xE1DD6314CDD0A76A, 0xB5341A8691005667, 0x480F903074714570,
0x1CE6E9A228A1B47D, 0x20A02A76119D7DDB, 0x744953E44D4D8CD6, 0x8972D952A83C9FC1, 0xDD9BA0C0F4EC6ECC,
0xF1FF5EFADA450C8D, 0xA51627688695FD80, 0x582DADDE63E4EE97, 0x0CC4D44C3F341F9A, 0x308217980608D63C,
0x646B6E0A5AD82731, 0x9950E4BCBFA93426, 0xCDB99D2EE379C52B, 0x90FB71CAD654A0F5, 0xC41208588A8451F8,
0x392982EE6FF542EF, 0x6DC0FB7C3325B3E2, 0x518638A80A197A44, 0x056F413A56C98B49, 0xF854CB8CB3B8985E,
0xACBDB21EEF686953, 0x80D94C24C1C10B12, 0xD43035B69D11FA1F, 0x290BBF007860E908, 0x7DE2C69224B01805,
0x41A405461D8CD1A3, 0x154D7CD4415C20AE, 0xE876F662A42D33B9, 0xBC9F8FF0F8FDC2B4, 0xB0BF0A16F97FF73B,
0xE4567384A5AF0636, 0x196DF93240DE1521, 0x4D8480A01C0EE42C, 0x71C2437425322D8A, 0x252B3AE679E2DC87,
0xD810B0509C93CF90, 0x8CF9C9C2C0433E9D, 0xA09D37F8EEEA5CDC, 0xF4744E6AB23AADD1, 0x094FC4DC574BBEC6,
0x5DA6BD4E0B9B4FCB, 0x61E07E9A32A7866D, 0x350907086E777760, 0xC8328DBE8B066477, 0x9CDBF42CD7D6957A,
0xD073867288020F69, 0x849AFFE0D4D2FE64, 0x79A1755631A3ED73, 0x2D480CC46D731C7E, 0x110ECF10544FD5D8,
0x45E7B682089F24D5, 0xB8DC3C34EDEE37C2, 0xEC3545A6B13EC6CF, 0xC051BB9C9F97A48E, 0x94B8C20EC3475583,
0x698348B826364694, 0x3D6A312A7AE6B799, 0x012CF2FE43DA7E3F, 0x55C58B6C1F0A8F32, 0xA8FE01DAFA7B9C25,
0xFC177848A6AB6D28, 0xF037FDAEA72958A7, 0xA4DE843CFBF9A9AA, 0x59E50E8A1E88BABD, 0x0D0C771842584BB0,
0x314AB4CC7B648216, 0x65A3CD5E27B4731B, 0x989847E8C2C5600C, 0xCC713E7A9E159101, 0xE015C040B0BCF340,
0xB4FCB9D2EC6C024D, 0x49C73364091D115A, 0x1D2E4AF655CDE057, 0x216889226CF129F1, 0x7581F0B03021D8FC,
0x88BA7A06D550CBEB, 0xDC53039489803AE6, 0x11EA9EBA6AF9FFCD, 0x4503E72836290EC0, 0xB8386D9ED3581DD7,
0xECD1140C8F88ECDA, 0xD097D7D8B6B4257C, 0x847EAE4AEA64D471, 0x794524FC0F15C766, 0x2DAC5D6E53C5366B,
0x01C8A3547D6C542A, 0x5521DAC621BCA527, 0xA81A5070C4CDB630, 0xFCF329E2981D473D, 0xC0B5EA36A1218E9B,
0x945C93A4FDF17F96, 0x6967191218806C81, 0x3D8E608044509D8C, 0x31AEE56645D2A803, 0x65479CF41902590E,
0x987C1642FC734A19, 0xCC956FD0A0A3BB14, 0xF0D3AC04999F72B2, 0xA43AD596C54F83BF, 0x59015F20203E90A8,
0x0DE826B27CEE61A5, 0x218CD888524703E4, 0x7565A11A0E97F2E9, 0x885E2BACEBE6E1FE, 0xDCB7523EB73610F3,
0xE0F191EA8E0AD955, 0xB418E878D2DA2858, 0x492362CE37AB3B4F, 0x1DCA1B5C6B7BCA42, 0x5162690234AF5051,
0x058B1090687FA15C, 0xF8B09A268D0EB24B, 0xAC59E3B4D1DE4346, 0x901F2060E8E28AE0, 0xC4F659F2B4327BED,
0x39CDD344514368FA, 0x6D24AAD60D9399F7, 0x414054EC233AFBB6, 0x15A92D7E7FEA0ABB, 0xE892A7C89A9B19AC,
0xBC7BDE5AC64BE8A1, 0x803D1D8EFF772107, 0xD4D4641CA3A7D00A, 0x29EFEEAA46D6C31D, 0x7D0697381A063210,
0x712612DE1B84079F, 0x25CF6B4C4754F692, 0xD8F4E1FAA225E585, 0x8C1D9868FEF51488, 0xB05B5BBCC7C9DD2E,
0xE4B2222E9B192C23, 0x1989A8987E683F34, 0x4D60D10A22B8CE39, 0x61042F300C11AC78, 0x35ED56A250C15D75,
0xC8D6DC14B5B04E62, 0x9C3FA586E960BF6F, 0xA0796652D05C76C9, 0xF4901FC08C8C87C4, 0x09AB957669FD94D3,
0x5D42ECE4352D65DE
],
[
0x0000000000000000, 0x3F0BE14A916A6DCB, 0x7E17C29522D4DB96, 0x411C23DFB3BEB65D, 0xFC2F852A45A9B72C,
0xC3246460D4C3DAE7, 0x823847BF677D6CBA, 0xBD33A6F5F6170171, 0x6A87A57F245D70DD, 0x558C4435B5371D16,
0x149067EA0689AB4B, 0x2B9B86A097E3C680, 0x96A8205561F4C7F1, 0xA9A3C11FF09EAA3A, 0xE8BFE2C043201C67,
0xD7B4038AD24A71AC, 0xD50F4AFE48BAE1BA, 0xEA04ABB4D9D08C71, 0xAB18886B6A6E3A2C, 0x94136921FB0457E7,
0x2920CFD40D135696, 0x162B2E9E9C793B5D, 0x57370D412FC78D00, 0x683CEC0BBEADE0CB, 0xBF88EF816CE79167,
0x80830ECBFD8DFCAC, 0xC19F2D144E334AF1, 0xFE94CC5EDF59273A, 0x43A76AAB294E264B, 0x7CAC8BE1B8244B80,
0x3DB0A83E0B9AFDDD, 0x02BB49749AF09016, 0x38C63AD73E7BDDF1, 0x07CDDB9DAF11B03A, 0x46D1F8421CAF0667,
0x79DA19088DC56BAC, 0xC4E9BFFD7BD26ADD, 0xFBE25EB7EAB80716, 0xBAFE7D685906B14B, 0x85F59C22C86CDC80,
0x52419FA81A26AD2C, 0x6D4A7EE28B4CC0E7, 0x2C565D3D38F276BA, 0x135DBC77A9981B71, 0xAE6E1A825F8F1A00,
0x9165FBC8CEE577CB, 0xD079D8177D5BC196, 0xEF72395DEC31AC5D, 0xEDC9702976C13C4B, 0xD2C29163E7AB5180,
0x93DEB2BC5415E7DD, 0xACD553F6C57F8A16, 0x11E6F50333688B67, 0x2EED1449A202E6AC, 0x6FF1379611BC50F1,
0x50FAD6DC80D63D3A, 0x874ED556529C4C96, 0xB845341CC3F6215D, 0xF95917C370489700, 0xC652F689E122FACB,
0x7B61507C1735FBBA, 0x446AB136865F9671, 0x057692E935E1202C, 0x3A7D73A3A48B4DE7, 0x718C75AE7CF7BBE2,
0x4E8794E4ED9DD629, 0x0F9BB73B5E236074, 0x30905671CF490DBF, 0x8DA3F084395E0CCE, 0xB2A811CEA8346105,
0xF3B432111B8AD758, 0xCCBFD35B8AE0BA93, 0x1B0BD0D158AACB3F, 0x2400319BC9C0A6F4, 0x651C12447A7E10A9,
0x5A17F30EEB147D62, 0xE72455FB1D037C13, 0xD82FB4B18C6911D8, 0x9933976E3FD7A785, 0xA6387624AEBDCA4E,
0xA4833F50344D5A58, 0x9B88DE1AA5273793, 0xDA94FDC5169981CE, 0xE59F1C8F87F3EC05, 0x58ACBA7A71E4ED74,
0x67A75B30E08E80BF, 0x26BB78EF533036E2, 0x19B099A5C25A5B29, 0xCE049A2F10102A85, 0xF10F7B65817A474E,
0xB01358BA32C4F113, 0x8F18B9F0A3AE9CD8, 0x322B1F0555B99DA9, 0x0D20FE4FC4D3F062, 0x4C3CDD90776D463F,
0x73373CDAE6072BF4, 0x494A4F79428C6613, 0x7641AE33D3E60BD8, 0x375D8DEC6058BD85, 0x08566CA6F132D04E,
0xB565CA530725D13F, 0x8A6E2B19964FBCF4, 0xCB7208C625F10AA9, 0xF479E98CB49B6762, 0x23CDEA0666D116CE,
0x1CC60B4CF7BB7B05, 0x5DDA28934405CD58, 0x62D1C9D9D56FA093, 0xDFE26F2C2378A1E2, 0xE0E98E66B212CC29,
0xA1F5ADB901AC7A74, 0x9EFE4CF390C617BF, 0x9C4505870A3687A9, 0xA34EE4CD9B5CEA62, 0xE252C71228E25C3F,
0xDD592658B98831F4, 0x606A80AD4F9F3085, 0x5F6161E7DEF55D4E, 0x1E7D42386D4BEB13, 0x2176A372FC2186D8,
0xF6C2A0F82E6BF774, 0xC9C941B2BF019ABF, 0x88D5626D0CBF2CE2, 0xB7DE83279DD54129, 0x0AED25D26BC24058,
0x35E6C498FAA82D93, 0x74FAE74749169BCE, 0x4BF1060DD87CF605, 0xE318EB5CF9EF77C4, 0xDC130A1668851A0F,
0x9D0F29C9DB3BAC52, 0xA204C8834A51C199, 0x1F376E76BC46C0E8, 0x203C8F3C2D2CAD23, 0x6120ACE39E921B7E,
0x5E2B4DA90FF876B5, 0x899F4E23DDB20719, 0xB694AF694CD86AD2, 0xF7888CB6FF66DC8F, 0xC8836DFC6E0CB144,
0x75B0CB09981BB035, 0x4ABB2A430971DDFE, 0x0BA7099CBACF6BA3, 0x34ACE8D62BA50668, 0x3617A1A2B155967E,
0x091C40E8203FFBB5, 0x4800633793814DE8, 0x770B827D02EB2023, 0xCA382488F4FC2152, 0xF533C5C265964C99,
0xB42FE61DD628FAC4, 0x8B2407574742970F, 0x5C9004DD9508E6A3, 0x639BE59704628B68, 0x2287C648B7DC3D35,
0x1D8C270226B650FE, 0xA0BF81F7D0A1518F, 0x9FB460BD41CB3C44, 0xDEA84362F2758A19, 0xE1A3A228631FE7D2,
0xDBDED18BC794AA35, 0xE4D530C156FEC7FE, 0xA5C9131EE54071A3, 0x9AC2F254742A1C68, 0x27F154A1823D1D19,
0x18FAB5EB135770D2, 0x59E69634A0E9C68F, 0x66ED777E3183AB44, 0xB15974F4E3C9DAE8, 0x8E5295BE72A3B723,
0xCF4EB661C11D017E, 0xF045572B50776CB5, 0x4D76F1DEA6606DC4, 0x727D1094370A000F, 0x3361334B84B4B652,
0x0C6AD20115DEDB99, 0x0ED19B758F2E4B8F, 0x31DA7A3F1E442644, 0x70C659E0ADFA9019, 0x4FCDB8AA3C90FDD2,
0xF2FE1E5FCA87FCA3, 0xCDF5FF155BED9168, 0x8CE9DCCAE8532735, 0xB3E23D8079394AFE, 0x64563E0AAB733B52,
0x5B5DDF403A195699, 0x1A41FC9F89A7E0C4, 0x254A1DD518CD8D0F, 0x9879BB20EEDA8C7E, 0xA7725A6A7FB0E1B5,
0xE66E79B5CC0E57E8, 0xD96598FF5D643A23, 0x92949EF28518CC26, 0xAD9F7FB81472A1ED, 0xEC835C67A7CC17B0,
0xD388BD2D36A67A7B, 0x6EBB1BD8C0B17B0A, 0x51B0FA9251DB16C1, 0x10ACD94DE265A09C, 0x2FA73807730FCD57,
0xF8133B8DA145BCFB, 0xC718DAC7302FD130, 0x8604F9188391676D, 0xB90F185212FB0AA6, 0x043CBEA7E4EC0BD7,
0x3B375FED7586661C, 0x7A2B7C32C638D041, 0x45209D785752BD8A, 0x479BD40CCDA22D9C, 0x789035465CC84057,
0x398C1699EF76F60A, 0x0687F7D37E1C9BC1, 0xBBB45126880B9AB0, 0x84BFB06C1961F77B, 0xC5A393B3AADF4126,
0xFAA872F93BB52CED, 0x2D1C7173E9FF5D41, 0x121790397895308A, 0x530BB3E6CB2B86D7, 0x6C0052AC5A41EB1C,
0xD133F459AC56EA6D, 0xEE3815133D3C87A6, 0xAF2436CC8E8231FB, 0x902FD7861FE85C30, 0xAA52A425BB6311D7,
0x9559456F2A097C1C, 0xD44566B099B7CA41, 0xEB4E87FA08DDA78A, 0x567D210FFECAA6FB, 0x6976C0456FA0CB30,
0x286AE39ADC1E7D6D, 0x176102D04D7410A6, 0xC0D5015A9F3E610A, 0xFFDEE0100E540CC1, 0xBEC2C3CFBDEABA9C,
0x81C922852C80D757, 0x3CFA8470DA97D626, 0x03F1653A4BFDBBED, 0x42ED46E5F8430DB0, 0x7DE6A7AF6929607B,
0x7F5DEEDBF3D9F06D, 0x40560F9162B39DA6, 0x014A2C4ED10D2BFB, 0x3E41CD0440674630, 0x83726BF1B6704741,
0xBC798ABB271A2A8A, 0xFD65A96494A49CD7, 0xC26E482E05CEF11C, 0x15DA4BA4D78480B0, 0x2AD1AAEE46EEED7B,
0x6BCD8931F5505B26, 0x54C6687B643A36ED, 0xE9F5CE8E922D379C, 0xD6FE2FC403475A57, 0x97E20C1BB0F9EC0A,
0xA8E9ED51219381C1
],
[
0x0000000000000000, 0x1DEE8A5E222CA1DC, 0x3BDD14BC445943B8, 0x26339EE26675E264, 0x77BA297888B28770,
0x6A54A326AA9E26AC, 0x4C673DC4CCEBC4C8, 0x5189B79AEEC76514, 0xEF7452F111650EE0, 0xF29AD8AF3349AF3C,
0xD4A9464D553C4D58, 0xC947CC137710EC84, 0x98CE7B8999D78990, 0x8520F1D7BBFB284C, 0xA3136F35DD8ECA28,
0xBEFDE56BFFA26BF4, 0x4C300AC98DC40345, 0x51DE8097AFE8A299, 0x77ED1E75C99D40FD, 0x6A03942BEBB1E121,
0x3B8A23B105768435, 0x2664A9EF275A25E9, 0x0057370D412FC78D, 0x1DB9BD5363036651, 0xA34458389CA10DA5,
0xBEAAD266BE8DAC79, 0x98994C84D8F84E1D, 0x8577C6DAFAD4EFC1, 0xD4FE714014138AD5, 0xC910FB1E363F2B09,
0xEF2365FC504AC96D, 0xF2CDEFA2726668B1, 0x986015931B88068A, 0x858E9FCD39A4A756, 0xA3BD012F5FD14532,
0xBE538B717DFDE4EE, 0xEFDA3CEB933A81FA, 0xF234B6B5B1162026, 0xD4072857D763C242, 0xC9E9A209F54F639E,
0x771447620AED086A, 0x6AFACD3C28C1A9B6, 0x4CC953DE4EB44BD2, 0x5127D9806C98EA0E, 0x00AE6E1A825F8F1A,
0x1D40E444A0732EC6, 0x3B737AA6C606CCA2, 0x269DF0F8E42A6D7E, 0xD4501F5A964C05CF, 0xC9BE9504B460A413,
0xEF8D0BE6D2154677, 0xF26381B8F039E7AB, 0xA3EA36221EFE82BF, 0xBE04BC7C3CD22363, 0x9837229E5AA7C107,
0x85D9A8C0788B60DB, 0x3B244DAB87290B2F, 0x26CAC7F5A505AAF3, 0x00F95917C3704897, 0x1D17D349E15CE94B,
0x4C9E64D30F9B8C5F, 0x5170EE8D2DB72D83, 0x7743706F4BC2CFE7, 0x6AADFA3169EE6E3B, 0xA218840D981E1391,
0xBFF60E53BA32B24D, 0x99C590B1DC475029, 0x842B1AEFFE6BF1F5, 0xD5A2AD7510AC94E1, 0xC84C272B3280353D,
0xEE7FB9C954F5D759, 0xF391339776D97685, 0x4D6CD6FC897B1D71, 0x50825CA2AB57BCAD, 0x76B1C240CD225EC9,
0x6B5F481EEF0EFF15, 0x3AD6FF8401C99A01, 0x273875DA23E53BDD, 0x010BEB384590D9B9, 0x1CE5616667BC7865,
0xEE288EC415DA10D4, 0xF3C6049A37F6B108, 0xD5F59A785183536C, 0xC81B102673AFF2B0, 0x9992A7BC9D6897A4,
0x847C2DE2BF443678, 0xA24FB300D931D41C, 0xBFA1395EFB1D75C0, 0x015CDC3504BF1E34, 0x1CB2566B2693BFE8,
0x3A81C88940E65D8C, 0x276F42D762CAFC50, 0x76E6F54D8C0D9944, 0x6B087F13AE213898, 0x4D3BE1F1C854DAFC,
0x50D56BAFEA787B20, 0x3A78919E8396151B, 0x27961BC0A1BAB4C7, 0x01A58522C7CF56A3, 0x1C4B0F7CE5E3F77F,
0x4DC2B8E60B24926B, 0x502C32B8290833B7, 0x761FAC5A4F7DD1D3, 0x6BF126046D51700F, 0xD50CC36F92F31BFB,
0xC8E24931B0DFBA27, 0xEED1D7D3D6AA5843, 0xF33F5D8DF486F99F, 0xA2B6EA171A419C8B, 0xBF586049386D3D57,
0x996BFEAB5E18DF33, 0x848574F57C347EEF, 0x76489B570E52165E, 0x6BA611092C7EB782, 0x4D958FEB4A0B55E6,
0x507B05B56827F43A, 0x01F2B22F86E0912E, 0x1C1C3871A4CC30F2, 0x3A2FA693C2B9D296, 0x27C12CCDE095734A,
0x993CC9A61F3718BE, 0x84D243F83D1BB962, 0xA2E1DD1A5B6E5B06, 0xBF0F57447942FADA, 0xEE86E0DE97859FCE,
0xF3686A80B5A93E12, 0xD55BF462D3DCDC76, 0xC8B57E3CF1F07DAA, 0xD6E9A7309F3239A7, 0xCB072D6EBD1E987B,
0xED34B38CDB6B7A1F, 0xF0DA39D2F947DBC3, 0xA1538E481780BED7, 0xBCBD041635AC1F0B, 0x9A8E9AF453D9FD6F,
0x876010AA71F55CB3, 0x399DF5C18E573747, 0x24737F9FAC7B969B, 0x0240E17DCA0E74FF, 0x1FAE6B23E822D523,
0x4E27DCB906E5B037, 0x53C956E724C911EB, 0x75FAC80542BCF38F, 0x6814425B60905253, 0x9AD9ADF912F63AE2,
0x873727A730DA9B3E, 0xA104B94556AF795A, 0xBCEA331B7483D886, 0xED6384819A44BD92, 0xF08D0EDFB8681C4E,
0xD6BE903DDE1DFE2A, 0xCB501A63FC315FF6, 0x75ADFF0803933402, 0x6843755621BF95DE, 0x4E70EBB447CA77BA,
0x539E61EA65E6D666, 0x0217D6708B21B372, 0x1FF95C2EA90D12AE, 0x39CAC2CCCF78F0CA, 0x24244892ED545116,
0x4E89B2A384BA3F2D, 0x536738FDA6969EF1, 0x7554A61FC0E37C95, 0x68BA2C41E2CFDD49, 0x39339BDB0C08B85D,
0x24DD11852E241981, 0x02EE8F674851FBE5, 0x1F0005396A7D5A39, 0xA1FDE05295DF31CD, 0xBC136A0CB7F39011,
0x9A20F4EED1867275, 0x87CE7EB0F3AAD3A9, 0xD647C92A1D6DB6BD, 0xCBA943743F411761, 0xED9ADD965934F505,
0xF07457C87B1854D9, 0x02B9B86A097E3C68, 0x1F5732342B529DB4, 0x3964ACD64D277FD0, 0x248A26886F0BDE0C,
0x7503911281CCBB18, 0x68ED1B4CA3E01AC4, 0x4EDE85AEC595F8A0, 0x53300FF0E7B9597C, 0xEDCDEA9B181B3288,
0xF02360C53A379354, 0xD610FE275C427130, 0xCBFE74797E6ED0EC, 0x9A77C3E390A9B5F8, 0x879949BDB2851424,
0xA1AAD75FD4F0F640, 0xBC445D01F6DC579C, 0x74F1233D072C2A36, 0x691FA96325008BEA, 0x4F2C37814375698E,
0x52C2BDDF6159C852, 0x034B0A458F9EAD46, 0x1EA5801BADB20C9A, 0x38961EF9CBC7EEFE, 0x257894A7E9EB4F22,
0x9B8571CC164924D6, 0x866BFB923465850A, 0xA05865705210676E, 0xBDB6EF2E703CC6B2, 0xEC3F58B49EFBA3A6,
0xF1D1D2EABCD7027A, 0xD7E24C08DAA2E01E, 0xCA0CC656F88E41C2, 0x38C129F48AE82973, 0x252FA3AAA8C488AF,
0x031C3D48CEB16ACB, 0x1EF2B716EC9DCB17, 0x4F7B008C025AAE03, 0x52958AD220760FDF, 0x74A614304603EDBB,
0x69489E6E642F4C67, 0xD7B57B059B8D2793, 0xCA5BF15BB9A1864F, 0xEC686FB9DFD4642B, 0xF186E5E7FDF8C5F7,
0xA00F527D133FA0E3, 0xBDE1D8233113013F, 0x9BD246C15766E35B, 0x863CCC9F754A4287, 0xEC9136AE1CA42CBC,
0xF17FBCF03E888D60, 0xD74C221258FD6F04, 0xCAA2A84C7AD1CED8, 0x9B2B1FD69416ABCC, 0x86C59588B63A0A10,
0xA0F60B6AD04FE874, 0xBD188134F26349A8, 0x03E5645F0DC1225C, 0x1E0BEE012FED8380, 0x383870E3499861E4,
0x25D6FABD6BB4C038, 0x745F4D278573A52C, 0x69B1C779A75F04F0, 0x4F82599BC12AE694, 0x526CD3C5E3064748,
0xA0A13C6791602FF9, 0xBD4FB639B34C8E25, 0x9B7C28DBD5396C41, 0x8692A285F715CD9D, 0xD71B151F19D2A889,
0xCAF59F413BFE0955, 0xECC601A35D8BEB31, 0xF1288BFD7FA74AED, 0x4FD56E9680052119, 0x523BE4C8A22980C5,
0x74087A2AC45C62A1, 0x69E6F074E670C37D, 0x386F47EE08B7A669, 0x2581CDB02A9B07B5, 0x03B253524CEEE5D1,
0x1E5CD90C6EC2440D
]
];
readonly ulong _finalSeed;
readonly IntPtr _nativeContext;
readonly ulong[][] _table;
readonly bool _useEcma;
readonly bool _useNative;
ulong _hashInt;
/// <summary>Initializes the CRC64 table and seed as CRC64-ECMA</summary>
public Crc64Context()
{
_hashInt = CRC64_ECMA_SEED;
_table = new ulong[256];
for(var i = 0; i < 256; i++)
{
var entry = (ulong)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ CRC64_ECMA_POLY;
else
entry >>= 1;
}
_table[i] = entry;
}
_hashInt = CRC64_ECMA_SEED;
_table = _ecmaCrc64Table;
_finalSeed = CRC64_ECMA_SEED;
_useEcma = true;
if(!Native.IsSupported) return;
_nativeContext = crc64_init();
_useNative = _nativeContext != IntPtr.Zero;
}
/// <summary>Initializes the CRC16 table with a custom polynomial and seed</summary>
public Crc64Context(ulong polynomial, ulong seed)
{
_hashInt = seed;
_hashInt = seed;
_finalSeed = seed;
_useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED;
_table = new ulong[256];
for(var i = 0; i < 256; i++)
if(Native.IsSupported && _useEcma)
{
var entry = (ulong)i;
_nativeContext = crc64_init();
_useNative = _nativeContext != IntPtr.Zero;
}
else
_table = GenerateTable(polynomial);
}
for(var j = 0; j < 8; j++)
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial IntPtr crc64_init();
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial int crc64_update(IntPtr ctx, byte[] data, uint len);
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial int crc64_final(IntPtr ctx, ref ulong crc);
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial void crc64_free(IntPtr ctx);
static ulong[][] GenerateTable(ulong polynomial)
{
ulong[][] table = new ulong[8][];
for(int i = 0; i < 8; i++) table[i] = new ulong[256];
for(int i = 0; i < 256; i++)
{
ulong entry = (ulong)i;
for(int j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
@@ -91,44 +340,79 @@ public sealed class Crc64Context : IChecksum
entry >>= 1;
}
_table[i] = entry;
table[0][i] = entry;
}
_finalSeed = seed;
for(int slice = 1; slice < 4; slice++)
{
for(int i = 0; i < 256; i++)
table[slice][i] = table[slice - 1][i] >> 8 ^ table[0][table[slice - 1][i] & 0xFF];
}
return table;
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len)
static void Step(ref ulong previousCrc, ulong[][] table, byte[] data, uint len, bool useEcma, bool useNative,
IntPtr nativeContext)
{
for(var i = 0; i < len; i++) _hashInt = _hashInt >> 8 ^ _table[data[i] ^ _hashInt & 0xff];
}
if(useNative && useEcma)
{
crc64_update(nativeContext, data, len);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
return;
}
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final() => BigEndianBitConverter.GetBytes(_hashInt ^= _finalSeed);
int dataOff = 0;
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
var crc64Output = new StringBuilder();
if(useEcma && Pclmulqdq.IsSupported && Sse41.IsSupported && Ssse3.IsSupported && Sse2.IsSupported)
{
// Only works in blocks of 32 bytes
uint blocks = len / 32;
for(var i = 0; i < BigEndianBitConverter.GetBytes(_hashInt ^= _finalSeed).Length; i++)
crc64Output.Append(BigEndianBitConverter.GetBytes(_hashInt ^= _finalSeed)[i].ToString("x2"));
if(blocks > 0)
{
previousCrc = ~Clmul.Step(~previousCrc, data, blocks * 32);
return crc64Output.ToString();
dataOff = (int)(blocks * 32);
len -= blocks * 32;
}
if(len == 0) return;
}
// Unroll according to Intel slicing by uint8_t
// http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
// http://sourceforge.net/projects/slicing-by-8/
ulong crc = previousCrc;
if(len > 4)
{
long limit = dataOff + (len & ~(uint)3);
len &= 3;
while(dataOff < limit)
{
uint tmp = (uint)(crc ^ BitConverter.ToUInt32(data, dataOff));
dataOff += 4;
crc = table[3][tmp & 0xFF] ^
table[2][tmp >> 8 & 0xFF] ^
crc >> 32 ^
table[1][tmp >> 16 & 0xFF] ^
table[0][tmp >> 24];
}
}
while(len-- != 0) crc = table[0][data[dataOff++] ^ crc & 0xFF] ^ crc >> 8;
previousCrc = crc;
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
// ReSharper disable once ReturnTypeCanBeEnumerable.Global
public static byte[] File(string filename)
{
File(filename, out byte[] localHash);
@@ -149,32 +433,41 @@ public sealed class Crc64Context : IChecksum
/// <param name="seed">CRC seed</param>
public static string File(string filename, out byte[] hash, ulong polynomial, ulong seed)
{
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED;
bool useNative = Native.IsSupported;
IntPtr nativeContext = IntPtr.Zero;
ulong localhashInt = seed;
var localTable = new ulong[256];
for(var i = 0; i < 256; i++)
if(useNative && useEcma)
{
var entry = (ulong)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
else
entry >>= 1;
}
localTable[i] = entry;
nativeContext = crc64_init();
useNative = nativeContext != IntPtr.Zero;
}
for(var i = 0; i < fileStream.Length; i++)
localhashInt = localhashInt >> 8 ^ localTable[(ulong)fileStream.ReadByte() ^ localhashInt & 0xffL];
var fileStream = new FileStream(filename, FileMode.Open);
localhashInt ^= seed;
hash = BigEndianBitConverter.GetBytes(localhashInt);
ulong localHashInt = seed;
ulong[][] localTable = GenerateTable(polynomial);
byte[] buffer = new byte[65536];
int read = fileStream.EnsureRead(buffer, 0, 65536);
while(read > 0)
{
Step(ref localHashInt, localTable, buffer, (uint)read, useEcma, useNative, nativeContext);
read = fileStream.EnsureRead(buffer, 0, 65536);
}
localHashInt ^= seed;
if(useNative && useEcma)
{
crc64_final(nativeContext, ref localHashInt);
crc64_free(nativeContext);
}
hash = BigEndianBitConverter.GetBytes(localHashInt);
var crc64Output = new StringBuilder();
@@ -200,29 +493,31 @@ public sealed class Crc64Context : IChecksum
/// <param name="seed">CRC seed</param>
public static string Data(byte[] data, uint len, out byte[] hash, ulong polynomial, ulong seed)
{
ulong localhashInt = seed;
bool useEcma = polynomial == CRC64_ECMA_POLY && seed == CRC64_ECMA_SEED;
bool useNative = Native.IsSupported;
IntPtr nativeContext = IntPtr.Zero;
var localTable = new ulong[256];
for(var i = 0; i < 256; i++)
if(useNative && useEcma)
{
var entry = (ulong)i;
for(var j = 0; j < 8; j++)
{
if((entry & 1) == 1)
entry = entry >> 1 ^ polynomial;
else
entry >>= 1;
}
localTable[i] = entry;
nativeContext = crc64_init();
useNative = nativeContext != IntPtr.Zero;
}
for(var i = 0; i < len; i++) localhashInt = localhashInt >> 8 ^ localTable[data[i] ^ localhashInt & 0xff];
ulong localHashInt = seed;
localhashInt ^= seed;
hash = BigEndianBitConverter.GetBytes(localhashInt);
ulong[][] localTable = GenerateTable(polynomial);
Step(ref localHashInt, localTable, data, len, useEcma, useNative, nativeContext);
localHashInt ^= seed;
if(useNative && useEcma)
{
crc64_final(nativeContext, ref localHashInt);
crc64_free(nativeContext);
}
hash = BigEndianBitConverter.GetBytes(localHashInt);
var crc64Output = new StringBuilder();
@@ -235,4 +530,63 @@ public sealed class Crc64Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.CRC64_ECMA_Name;
/// <inheritdoc />
public Guid Id => new("D0C0D902-420A-45DA-A235-9D48BEE4B1CE");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) =>
Step(ref _hashInt, _table, data, len, _useEcma, _useNative, _nativeContext);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
ulong crc = _hashInt ^ _finalSeed;
if(!_useNative || !_useEcma) return BigEndianBitConverter.GetBytes(crc);
crc64_final(_nativeContext, ref crc);
crc64_free(_nativeContext);
return BigEndianBitConverter.GetBytes(crc);
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
ulong crc = _hashInt ^ _finalSeed;
var crc64Output = new StringBuilder();
if(_useNative && _useEcma)
{
crc64_final(_nativeContext, ref crc);
crc64_free(_nativeContext);
}
for(int i = 0; i < BigEndianBitConverter.GetBytes(crc).Length; i++)
crc64Output.Append(BigEndianBitConverter.GetBytes(crc)[i].ToString("x2"));
return crc64Output.ToString();
}
#endregion
}

View File

@@ -36,7 +36,7 @@
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.CommonTypes.Interfaces;
namespace RomRepoMgr.Core.Checksums;
public interface IChecksum
{

View File

@@ -0,0 +1,417 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RomRepoMgr.Core.Checksums.Localization {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localization {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localization() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Aaru.Checksums.Localization.Localization", typeof(Localization).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Adler-32.
/// </summary>
internal static string Adler32_Name {
get {
return ResourceManager.GetString("Adler32_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Assertion failed.
/// </summary>
internal static string Assertion_failed {
get {
return ResourceManager.GetString("Assertion_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CD-Text Pack 4 CRC 0x{0:X4}, expected 0x{1:X4}.
/// </summary>
internal static string CD_Text_Pack_four_CRC_0_expected_1 {
get {
return ResourceManager.GetString("CD_Text_Pack_four_CRC_0_expected_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CD-Text Pack 1 CRC 0x{0:X4}, expected 0x{1:X4}.
/// </summary>
internal static string CD_Text_Pack_one_CRC_0_expected_1 {
get {
return ResourceManager.GetString("CD_Text_Pack_one_CRC_0_expected_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CD-Text Pack 3 CRC 0x{0:X4}, expected 0x{1:X4}.
/// </summary>
internal static string CD_Text_Pack_three_CRC_0_expected_1 {
get {
return ResourceManager.GetString("CD_Text_Pack_three_CRC_0_expected_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CD-Text Pack 2 CRC 0x{0:X4}, expected 0x{1:X4}.
/// </summary>
internal static string CD_Text_Pack_two_CRC_0_expected_1 {
get {
return ResourceManager.GetString("CD_Text_Pack_two_CRC_0_expected_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CRC-16 (CCITT).
/// </summary>
internal static string CRC16_CCITT_Name {
get {
return ResourceManager.GetString("CRC16_CCITT_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CRC-16 (IBM).
/// </summary>
internal static string CRC16_IBM_Name {
get {
return ResourceManager.GetString("CRC16_IBM_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CRC-32.
/// </summary>
internal static string CRC32_Name {
get {
return ResourceManager.GetString("CRC32_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CRC-64 (ECMA).
/// </summary>
internal static string CRC64_ECMA_Name {
get {
return ResourceManager.GetString("CRC64_ECMA_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cyclic CDTP2 0x{0:X4}, Calc CDTP2 0x{1:X4}.
/// </summary>
internal static string Cyclic_CDTP2_0_Calc_CDTP2_1 {
get {
return ResourceManager.GetString("Cyclic_CDTP2_0_Calc_CDTP2_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cyclic CDTP3 0x{0:X4}, Calc CDTP3 0x{1:X4}.
/// </summary>
internal static string Cyclic_CDTP3_0_Calc_CDTP3_1 {
get {
return ResourceManager.GetString("Cyclic_CDTP3_0_Calc_CDTP3_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cyclic CDTP4 0x{0:X4}, Calc CDTP4 0x{1:X4}.
/// </summary>
internal static string Cyclic_CDTP4_0_Calc_CDTP4_1 {
get {
return ResourceManager.GetString("Cyclic_CDTP4_0_Calc_CDTP4_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected CD+EG Pack in subchannel.
/// </summary>
internal static string Detected_CD_EG_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_CD_EG_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected CD+G Pack in subchannel.
/// </summary>
internal static string Detected_CD_G_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_CD_G_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected CD+MIDI Pack in subchannel.
/// </summary>
internal static string Detected_CD_MIDI_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_CD_MIDI_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected CD-TEXT Pack in subchannel.
/// </summary>
internal static string Detected_CD_TEXT_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_CD_TEXT_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected Line Graphics Pack in subchannel.
/// </summary>
internal static string Detected_Line_Graphics_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_Line_Graphics_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected unknown Pack type in subchannel: mode {0}, item {1}.
/// </summary>
internal static string Detected_unknown_Pack_type_in_subchannel_mode_0_item_1 {
get {
return ResourceManager.GetString("Detected_unknown_Pack_type_in_subchannel_mode_0_item_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected User Pack in subchannel.
/// </summary>
internal static string Detected_User_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_User_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detected Zero Pack in subchannel.
/// </summary>
internal static string Detected_Zero_Pack_in_subchannel {
get {
return ResourceManager.GetString("Detected_Zero_Pack_in_subchannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
/// Erasure positions as determined by roots of Eras Loc Poly:
///.
/// </summary>
internal static string Erasure_positions_as_determined_by_roots_of_Eras_Loc_Poly {
get {
return ResourceManager.GetString("Erasure_positions_as_determined_by_roots_of_Eras_Loc_Poly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
/// ERROR: denominator = 0
///.
/// </summary>
internal static string ERROR_denominator_equals_zero {
get {
return ResourceManager.GetString("ERROR_denominator_equals_zero", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
/// Final error positions: .
/// </summary>
internal static string Final_error_positions {
get {
return ResourceManager.GetString("Final_error_positions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fletcher-16.
/// </summary>
internal static string Fletcher16_Name {
get {
return ResourceManager.GetString("Fletcher16_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fletcher-32.
/// </summary>
internal static string Fletcher32_Name {
get {
return ResourceManager.GetString("Fletcher32_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
/// lambda(x) is WRONG
///.
/// </summary>
internal static string lambda_is_wrong {
get {
return ResourceManager.GetString("lambda_is_wrong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to m must be between 2 and 16 inclusive.
/// </summary>
internal static string m_must_be_between_2_and_16_inclusive {
get {
return ResourceManager.GetString("m_must_be_between_2_and_16_inclusive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to MD5.
/// </summary>
internal static string MD5_Name {
get {
return ResourceManager.GetString("MD5_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not yet implemented..
/// </summary>
internal static string Not_yet_implemented {
get {
return ResourceManager.GetString("Not_yet_implemented", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Q subchannel CRC 0x{0:X4}, expected 0x{1:X4}.
/// </summary>
internal static string Q_subchannel_CRC_0_expected_1 {
get {
return ResourceManager.GetString("Q_subchannel_CRC_0_expected_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SHA1.
/// </summary>
internal static string SHA1_Name {
get {
return ResourceManager.GetString("SHA1_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SHA256.
/// </summary>
internal static string SHA256_Name {
get {
return ResourceManager.GetString("SHA256_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SHA384.
/// </summary>
internal static string SHA384_Name {
get {
return ResourceManager.GetString("SHA384_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SHA512.
/// </summary>
internal static string SHA512_Name {
get {
return ResourceManager.GetString("SHA512_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SpamSum does not have a binary representation..
/// </summary>
internal static string SpamSum_does_not_have_a_binary_representation {
get {
return ResourceManager.GetString("SpamSum_does_not_have_a_binary_representation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SpamSum.
/// </summary>
internal static string SpamSum_Name {
get {
return ResourceManager.GetString("SpamSum_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The input exceeds data types..
/// </summary>
internal static string The_input_exceeds_data_types {
get {
return ResourceManager.GetString("The_input_exceeds_data_types", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Trying to calculate RS without initializing!.
/// </summary>
internal static string Trying_to_calculate_RS_without_initializing {
get {
return ResourceManager.GetString("Trying_to_calculate_RS_without_initializing", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,136 @@
<root>
<!-- ReSharper disable MarkupTextTypo -->
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="Assertion_failed" xml:space="preserve">
<value>Error de aserción</value>
</data>
<data name="CD_Text_Pack_four_CRC_0_expected_1" xml:space="preserve">
<value>CRC del paquete 4 de CD-Text es 0x{0:X4} se esperaba 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_one_CRC_0_expected_1" xml:space="preserve">
<value>CRC del paquete 1 de CD-Text es 0x{0:X4} se esperaba 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_three_CRC_0_expected_1" xml:space="preserve">
<value>CRC del paquete 3 de CD-Text es 0x{0:X4} se esperaba 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_two_CRC_0_expected_1" xml:space="preserve">
<value>CRC del paquete 4 de CD-Text es 0x{0:X4} se esperaba 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP2_0_Calc_CDTP2_1" xml:space="preserve">
<value>CDTP2 cíclico 0x{0:X4}, CDTP2 calculado 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP3_0_Calc_CDTP3_1" xml:space="preserve">
<value>CDTP3 cíclico 0x{0:X4}, CDTP3 calculado 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP4_0_Calc_CDTP4_1" xml:space="preserve">
<value>CDTP4 cíclico 0x{0:X4}, CDTP4 calculado 0x{1:X4}</value>
</data>
<data name="Detected_CD_EG_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete CD+EG en el subcanal</value>
</data>
<data name="Detected_CD_G_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete CD+G en el subcanal</value>
</data>
<data name="Detected_CD_MIDI_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete CD+MIDI en el subcanal</value>
</data>
<data name="Detected_CD_TEXT_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete CD-TEXT en el subcanal</value>
</data>
<data name="Detected_Line_Graphics_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete de gráficos lineales en el subcanal</value>
</data>
<data name="Detected_unknown_Pack_type_in_subchannel_mode_0_item_1" xml:space="preserve">
<value>Detectado paquete de tipo desconocido en el subcanal: modo {0}, elemento {1}</value>
</data>
<data name="Detected_User_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete de usuario en el subcanal</value>
</data>
<data name="Detected_Zero_Pack_in_subchannel" xml:space="preserve">
<value>Detectado paquete cero en el subcanal</value>
</data>
<data name="Erasure_positions_as_determined_by_roots_of_Eras_Loc_Poly" xml:space="preserve">
<value>Posiciones de borrado determinadas por el polinomio:</value>
</data>
<data name="ERROR_denominator_equals_zero" xml:space="preserve">
<value>ERROR: denominador = 0</value>
</data>
<data name="Final_error_positions" xml:space="preserve">
<value>Posiciones finales de error:</value>
</data>
<data name="lambda_is_wrong" xml:space="preserve">
<value>lambda(x) es INCORRECTO</value>
</data>
<data name="m_must_be_between_2_and_16_inclusive" xml:space="preserve">
<value>m debe estar entre 2 y 16 inclusivos</value>
</data>
<data name="Not_yet_implemented" xml:space="preserve">
<value>Aún no implementado.</value>
</data>
<data name="Q_subchannel_CRC_0_expected_1" xml:space="preserve">
<value>CRC del subcanal Q es 0x{0:X4}, se esperaba 0x{1:X4}</value>
</data>
<data name="SpamSum_does_not_have_a_binary_representation" xml:space="preserve">
<value>SpamSum no posee una representación binaria.</value>
</data>
<data name="The_input_exceeds_data_types" xml:space="preserve">
<value>La entrada excede los tipos de datos</value>
</data>
<data name="Trying_to_calculate_RS_without_initializing" xml:space="preserve">
<value>¡Intentando calcular RS sin inicializar!</value>
</data>
<data name="Adler32_Name" xml:space="preserve">
<value>Adler-32</value>
</data>
<data name="CRC16_CCITT_Name" xml:space="preserve">
<value>CRC-16 (CCITT)</value>
</data>
<data name="CRC16_IBM_Name" xml:space="preserve">
<value>CRC-16 (IBM)</value>
</data>
<data name="CRC32_Name" xml:space="preserve">
<value>CRC-32</value>
</data>
<data name="CRC64_ECMA_Name" xml:space="preserve">
<value>CRC-64 (ECMA)</value>
</data>
<data name="Fletcher32_Name" xml:space="preserve">
<value>Fletcher-32</value>
</data>
<data name="Fletcher16_Name" xml:space="preserve">
<value>Fletcher-16</value>
</data>
<data name="MD5_Name" xml:space="preserve">
<value>MD5</value>
</data>
<data name="SHA1_Name" xml:space="preserve">
<value>SHA1</value>
</data>
<data name="SHA256_Name" xml:space="preserve">
<value>SHA256</value>
</data>
<data name="SHA384_Name" xml:space="preserve">
<value>SHA384</value>
</data>
<data name="SHA512_Name" xml:space="preserve">
<value>SHA512</value>
</data>
<data name="SpamSum_Name" xml:space="preserve">
<value>SpamSum</value>
</data>
</root>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8" ?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root" xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="Detected_Zero_Pack_in_subchannel" xml:space="preserve">
<value>Detected Zero Pack in subchannel</value>
</data>
<data name="Detected_Line_Graphics_Pack_in_subchannel" xml:space="preserve">
<value>Detected Line Graphics Pack in subchannel</value>
</data>
<data name="Detected_CD_G_Pack_in_subchannel" xml:space="preserve">
<value>Detected CD+G Pack in subchannel</value>
</data>
<data name="Detected_CD_EG_Pack_in_subchannel" xml:space="preserve">
<value>Detected CD+EG Pack in subchannel</value>
</data>
<data name="Detected_CD_TEXT_Pack_in_subchannel" xml:space="preserve">
<value>Detected CD-TEXT Pack in subchannel</value>
</data>
<data name="Detected_CD_MIDI_Pack_in_subchannel" xml:space="preserve">
<value>Detected CD+MIDI Pack in subchannel</value>
</data>
<data name="Detected_User_Pack_in_subchannel" xml:space="preserve">
<value>Detected User Pack in subchannel</value>
</data>
<data name="Detected_unknown_Pack_type_in_subchannel_mode_0_item_1" xml:space="preserve">
<value>Detected unknown Pack type in subchannel: mode {0}, item {1}</value>
</data>
<data name="Q_subchannel_CRC_0_expected_1" xml:space="preserve">
<value>Q subchannel CRC 0x{0:X4}, expected 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_one_CRC_0_expected_1" xml:space="preserve">
<value>CD-Text Pack 1 CRC 0x{0:X4}, expected 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP2_0_Calc_CDTP2_1" xml:space="preserve">
<value>Cyclic CDTP2 0x{0:X4}, Calc CDTP2 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_two_CRC_0_expected_1" xml:space="preserve">
<value>CD-Text Pack 2 CRC 0x{0:X4}, expected 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP3_0_Calc_CDTP3_1" xml:space="preserve">
<value>Cyclic CDTP3 0x{0:X4}, Calc CDTP3 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_three_CRC_0_expected_1" xml:space="preserve">
<value>CD-Text Pack 3 CRC 0x{0:X4}, expected 0x{1:X4}</value>
</data>
<data name="Cyclic_CDTP4_0_Calc_CDTP4_1" xml:space="preserve">
<value>Cyclic CDTP4 0x{0:X4}, Calc CDTP4 0x{1:X4}</value>
</data>
<data name="CD_Text_Pack_four_CRC_0_expected_1" xml:space="preserve">
<value>CD-Text Pack 4 CRC 0x{0:X4}, expected 0x{1:X4}</value>
</data>
<data name="m_must_be_between_2_and_16_inclusive" xml:space="preserve">
<value>m must be between 2 and 16 inclusive</value>
</data>
<data name="Trying_to_calculate_RS_without_initializing" xml:space="preserve">
<value>Trying to calculate RS without initializing!</value>
</data>
<data name="lambda_is_wrong" xml:space="preserve">
<value>
lambda(x) is WRONG
</value>
</data>
<data name="Erasure_positions_as_determined_by_roots_of_Eras_Loc_Poly" xml:space="preserve">
<value>
Erasure positions as determined by roots of Eras Loc Poly:
</value>
</data>
<data name="Final_error_positions" xml:space="preserve">
<value>
Final error positions: </value>
</data>
<data name="ERROR_denominator_equals_zero" xml:space="preserve">
<value>
ERROR: denominator = 0
</value>
</data>
<data name="SpamSum_does_not_have_a_binary_representation" xml:space="preserve">
<value>SpamSum does not have a binary representation.</value>
</data>
<data name="Assertion_failed" xml:space="preserve">
<value>Assertion failed</value>
</data>
<data name="The_input_exceeds_data_types" xml:space="preserve">
<value>The input exceeds data types.</value>
</data>
<data name="Not_yet_implemented" xml:space="preserve">
<value>Not yet implemented.</value>
</data>
<data name="Adler32_Name" xml:space="preserve">
<value>Adler-32</value>
</data>
<data name="CRC16_CCITT_Name" xml:space="preserve">
<value>CRC-16 (CCITT)</value>
</data>
<data name="CRC16_IBM_Name" xml:space="preserve">
<value>CRC-16 (IBM)</value>
</data>
<data name="CRC32_Name" xml:space="preserve">
<value>CRC-32</value>
</data>
<data name="CRC64_ECMA_Name" xml:space="preserve">
<value>CRC-64 (ECMA)</value>
</data>
<data name="Fletcher32_Name" xml:space="preserve">
<value>Fletcher-32</value>
</data>
<data name="Fletcher16_Name" xml:space="preserve">
<value>Fletcher-16</value>
</data>
<data name="MD5_Name" xml:space="preserve">
<value>MD5</value>
</data>
<data name="SHA1_Name" xml:space="preserve">
<value>SHA1</value>
</data>
<data name="SHA256_Name" xml:space="preserve">
<value>SHA256</value>
</data>
<data name="SHA384_Name" xml:space="preserve">
<value>SHA384</value>
</data>
<data name="SHA512_Name" xml:space="preserve">
<value>SHA512</value>
</data>
<data name="SpamSum_Name" xml:space="preserve">
<value>SpamSum</value>
</data>
</root>

View File

@@ -27,16 +27,17 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Wraps up .NET MD5 implementation to a Init(), Update(), Final() context.</summary>
public sealed class Md5Context : IChecksum
{
@@ -45,44 +46,12 @@ public sealed class Md5Context : IChecksum
/// <summary>Initializes the MD5 hash provider</summary>
public Md5Context() => _provider = MD5.Create();
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var md5Output = new StringBuilder();
foreach(byte h in _provider.Hash) md5Output.Append(h.ToString("x2"));
return md5Output.ToString();
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
public static byte[] File(string filename)
{
var localMd5Provider = MD5.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
byte[] result = localMd5Provider.ComputeHash(fileStream);
fileStream.Close();
@@ -95,7 +64,7 @@ public sealed class Md5Context : IChecksum
public static string File(string filename, out byte[] hash)
{
var localMd5Provider = MD5.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
hash = localMd5Provider.ComputeHash(fileStream);
var md5Output = new StringBuilder();
@@ -125,4 +94,51 @@ public sealed class Md5Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.MD5_Name;
/// <inheritdoc />
public Guid Id => new("C78674C4-F699-4FAB-A618-1661AF659A7C");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var md5Output = new StringBuilder();
if(_provider.Hash is null) return null;
foreach(byte h in _provider.Hash) md5Output.Append(h.ToString("x2"));
return md5Output.ToString();
}
#endregion
}

View File

@@ -0,0 +1,81 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Native.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Checks that Aaru.Checksums.Native library is available and usable.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System.Runtime.InteropServices;
namespace RomRepoMgr.Core.Checksums;
/// <summary>Handles native implementations of compression algorithms</summary>
public static partial class Native
{
static bool _checked;
static bool _supported;
/// <summary>Set to return native as never supported</summary>
public static bool ForceManaged { get; set; }
/// <summary>
/// If set to <c>true</c> the native library was found and loaded correctly and its reported version is
/// compatible.
/// </summary>
public static bool IsSupported
{
get
{
if(ForceManaged) return false;
if(_checked) return _supported;
ulong version;
_checked = true;
try
{
version = get_acn_version();
}
catch
{
_supported = false;
return false;
}
// TODO: Check version compatibility
_supported = version >= 0x06000000;
return _supported;
}
}
[LibraryImport("libAaru.Checksums.Native", SetLastError = true)]
private static partial ulong get_acn_version();
}

View File

@@ -27,17 +27,22 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Wraps up .NET SHA1 implementation to a Init(), Update(), Final() context.</summary>
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed class Sha1Context : IChecksum
{
readonly SHA1 _provider;
@@ -45,44 +50,14 @@ public sealed class Sha1Context : IChecksum
/// <summary>Initializes the SHA1 hash provider</summary>
public Sha1Context() => _provider = SHA1.Create();
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha1Output = new StringBuilder();
foreach(byte h in _provider.Hash) sha1Output.Append(h.ToString("x2"));
return sha1Output.ToString();
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
// ReSharper disable once ReturnTypeCanBeEnumerable.Global
public static byte[] File(string filename)
{
var localSha1Provider = SHA1.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
byte[] result = localSha1Provider.ComputeHash(fileStream);
fileStream.Close();
@@ -95,7 +70,7 @@ public sealed class Sha1Context : IChecksum
public static string File(string filename, out byte[] hash)
{
var localSha1Provider = SHA1.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
hash = localSha1Provider.ComputeHash(fileStream);
var sha1Output = new StringBuilder();
@@ -125,4 +100,51 @@ public sealed class Sha1Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.SHA1_Name;
/// <inheritdoc />
public Guid Id => new("5C28939D-DCBB-4C6E-8498-C509ABD99FC2");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha1Output = new StringBuilder();
if(_provider.Hash is null) return null;
foreach(byte h in _provider.Hash) sha1Output.Append(h.ToString("x2"));
return sha1Output.ToString();
}
#endregion
}

View File

@@ -27,17 +27,22 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Wraps up .NET SHA256 implementation to a Init(), Update(), Final() context.</summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed class Sha256Context : IChecksum
{
readonly SHA256 _provider;
@@ -45,44 +50,14 @@ public sealed class Sha256Context : IChecksum
/// <summary>Initializes the SHA256 hash provider</summary>
public Sha256Context() => _provider = SHA256.Create();
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha256Output = new StringBuilder();
foreach(byte h in _provider.Hash) sha256Output.Append(h.ToString("x2"));
return sha256Output.ToString();
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
// ReSharper disable once ReturnTypeCanBeEnumerable.Global
public static byte[] File(string filename)
{
var localSha256Provider = SHA256.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
byte[] result = localSha256Provider.ComputeHash(fileStream);
fileStream.Close();
@@ -95,7 +70,7 @@ public sealed class Sha256Context : IChecksum
public static string File(string filename, out byte[] hash)
{
var localSha256Provider = SHA256.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
hash = localSha256Provider.ComputeHash(fileStream);
var sha256Output = new StringBuilder();
@@ -125,4 +100,51 @@ public sealed class Sha256Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.SHA256_Name;
/// <inheritdoc />
public Guid Id => new("A6F0EF52-064D-41D1-8619-240481749B70");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha256Output = new StringBuilder();
if(_provider.Hash is null) return null;
foreach(byte h in _provider.Hash) sha256Output.Append(h.ToString("x2"));
return sha256Output.ToString();
}
#endregion
}

View File

@@ -27,17 +27,23 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Wraps up .NET SHA384 implementation to a Init(), Update(), Final() context.</summary>
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class Sha384Context : IChecksum
{
readonly SHA384 _provider;
@@ -45,44 +51,14 @@ public sealed class Sha384Context : IChecksum
/// <summary>Initializes the SHA384 hash provider</summary>
public Sha384Context() => _provider = SHA384.Create();
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha384Output = new StringBuilder();
foreach(byte h in _provider.Hash) sha384Output.Append(h.ToString("x2"));
return sha384Output.ToString();
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
// ReSharper disable once ReturnTypeCanBeEnumerable.Global
public static byte[] File(string filename)
{
var localSha384Provider = SHA384.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
byte[] result = localSha384Provider.ComputeHash(fileStream);
fileStream.Close();
@@ -95,7 +71,7 @@ public sealed class Sha384Context : IChecksum
public static string File(string filename, out byte[] hash)
{
var localSha384Provider = SHA384.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
hash = localSha384Provider.ComputeHash(fileStream);
var sha384Output = new StringBuilder();
@@ -125,4 +101,51 @@ public sealed class Sha384Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.SHA384_Name;
/// <inheritdoc />
public Guid Id => new("4A2A1820-E157-4842-B1E2-0E629FA60DDD");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha384Output = new StringBuilder();
if(_provider.Hash is null) return null;
foreach(byte h in _provider.Hash) sha384Output.Append(h.ToString("x2"));
return sha384Output.ToString();
}
#endregion
}

View File

@@ -27,17 +27,23 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Wraps up .NET SHA512 implementation to a Init(), Update(), Final() context.</summary>
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class Sha512Context : IChecksum
{
readonly SHA512 _provider;
@@ -45,44 +51,12 @@ public sealed class Sha512Context : IChecksum
/// <summary>Initializes the SHA512 hash provider</summary>
public Sha512Context() => _provider = SHA512.Create();
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha512Output = new StringBuilder();
foreach(byte h in _provider.Hash) sha512Output.Append(h.ToString("x2"));
return sha512Output.ToString();
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
public static byte[] File(string filename)
{
var localSha512Provider = SHA512.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
byte[] result = localSha512Provider.ComputeHash(fileStream);
fileStream.Close();
@@ -95,7 +69,7 @@ public sealed class Sha512Context : IChecksum
public static string File(string filename, out byte[] hash)
{
var localSha512Provider = SHA512.Create();
var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
var fileStream = new FileStream(filename, FileMode.Open);
hash = localSha512Provider.ComputeHash(fileStream);
var sha512Output = new StringBuilder();
@@ -125,4 +99,51 @@ public sealed class Sha512Context : IChecksum
/// <param name="data">Data buffer.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string Data(byte[] data, out byte[] hash) => Data(data, (uint)data.Length, out hash);
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.SHA512_Name;
/// <inheritdoc />
public Guid Id => new("1E167BCB-2362-44DA-B5B0-B7ED3A22D5A6");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len) => _provider.TransformBlock(data, 0, (int)len, data, 0);
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
_provider.TransformFinalBlock([], 0, 0);
return _provider.Hash;
}
/// <inheritdoc />
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
public string End()
{
_provider.TransformFinalBlock([], 0, 0);
var sha512Output = new StringBuilder();
if(_provider.Hash is null) return null;
foreach(byte h in _provider.Hash) sha512Output.Append(h.ToString("x2"));
return sha512Output.ToString();
}
#endregion
}

View File

@@ -27,7 +27,7 @@
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
// Based on ssdeep
@@ -40,14 +40,19 @@
// http://ssdeep.sf.net/
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using Aaru.CommonTypes.Interfaces;
using RomRepoMgr.Core.Resources;
namespace Aaru.Checksums;
namespace RomRepoMgr.Core.Checksums;
/// <inheritdoc />
/// <summary>Implements the SpamSum fuzzy hashing algorithm.</summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "OutParameterValueIsAlwaysDiscarded.Global")]
public sealed class SpamSumContext : IChecksum
{
const uint ROLLING_WINDOW = 7;
@@ -59,15 +64,8 @@ public sealed class SpamSumContext : IChecksum
const uint FUZZY_MAX_RESULT = 2 * SPAMSUM_LENGTH + 20;
//"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
readonly byte[] _b64 =
[
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31,
0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F
];
FuzzyState _self;
readonly byte[] _b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"u8.ToArray();
FuzzyState _self;
/// <summary>Initializes the SpamSum structures</summary>
public SpamSumContext()
@@ -77,7 +75,7 @@ public sealed class SpamSumContext : IChecksum
Bh = new BlockhashContext[NUM_BLOCKHASHES]
};
for(var i = 0; i < NUM_BLOCKHASHES; i++) _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH];
for(int i = 0; i < NUM_BLOCKHASHES; i++) _self.Bh[i].Digest = new byte[SPAMSUM_LENGTH];
_self.Bhstart = 0;
_self.Bhend = 1;
@@ -90,35 +88,6 @@ public sealed class SpamSumContext : IChecksum
roll_init();
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len)
{
_self.TotalSize += len;
for(var i = 0; i < len; i++) fuzzy_engine_step(data[i]);
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final() => throw new NotImplementedException(Localization.Spamsum_no_binary);
/// <inheritdoc />
/// <summary>Returns a base64 representation of the hash value.</summary>
public string End()
{
FuzzyDigest(out byte[] result);
return CToString(result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void roll_init() => _self.Roll = new RollState
{
@@ -167,10 +136,15 @@ public sealed class SpamSumContext : IChecksum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void fuzzy_try_fork_blockhash()
{
if(_self.Bhend >= NUM_BLOCKHASHES) return;
switch(_self.Bhend)
{
case >= NUM_BLOCKHASHES:
return;
if(_self.Bhend == 0) // assert
throw new Exception(Localization.Assertion_failed);
// assert
case 0:
throw new Exception(Localization.Localization.Assertion_failed);
}
uint obh = _self.Bhend - 1;
uint nbh = _self.Bhend;
@@ -185,7 +159,7 @@ public sealed class SpamSumContext : IChecksum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void fuzzy_try_reduce_blockhash()
{
if(_self.Bhstart >= _self.Bhend) throw new Exception(Localization.Assertion_failed);
if(_self.Bhstart >= _self.Bhend) throw new Exception(Localization.Localization.Assertion_failed);
if(_self.Bhend - _self.Bhstart < 2)
/* Need at least two working hashes. */
@@ -266,21 +240,20 @@ public sealed class SpamSumContext : IChecksum
var sb = new StringBuilder();
uint bi = _self.Bhstart;
uint h = roll_sum();
var remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */
int remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */
result = new byte[FUZZY_MAX_RESULT];
/* Verify that our elimination was not overeager. */
if(!(bi == 0 || (ulong)SSDEEP_BS(bi) / 2 * SPAMSUM_LENGTH < _self.TotalSize))
throw new Exception(Localization.Assertion_failed);
var resultOff = 0;
throw new Exception(Localization.Localization.Assertion_failed);
/* Initial blocksize guess. */
while((ulong)SSDEEP_BS(bi) * SPAMSUM_LENGTH < _self.TotalSize)
{
++bi;
if(bi >= NUM_BLOCKHASHES) throw new OverflowException(Localization.Spamsum_Input_exceeds_data);
if(bi >= NUM_BLOCKHASHES)
throw new OverflowException(Localization.Localization.The_input_exceeds_data_types);
}
/* Adapt blocksize guess to actual digest length. */
@@ -288,26 +261,27 @@ public sealed class SpamSumContext : IChecksum
while(bi > _self.Bhstart && _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) --bi;
if(bi > 0 && _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2) throw new Exception(Localization.Assertion_failed);
if(bi > 0 && _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2)
throw new Exception(Localization.Localization.Assertion_failed);
sb.AppendFormat("{0}:", SSDEEP_BS(bi));
sb.Append($"{SSDEEP_BS(bi)}:");
int i = Encoding.ASCII.GetBytes(sb.ToString()).Length;
if(i <= 0)
/* Maybe snprintf has set errno here? */
throw new OverflowException(Localization.Spamsum_Input_exceeds_data);
throw new OverflowException(Localization.Localization.The_input_exceeds_data_types);
if(i >= remain) throw new Exception(Localization.Assertion_failed);
if(i >= remain) throw new Exception(Localization.Localization.Assertion_failed);
remain -= i;
Array.Copy(Encoding.ASCII.GetBytes(sb.ToString()), 0, result, 0, i);
resultOff = i;
int resultOff = i;
i = (int)_self.Bh[bi].Dlen;
if(i > remain) throw new Exception(Localization.Assertion_failed);
if(i > remain) throw new Exception(Localization.Localization.Assertion_failed);
Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i);
resultOff += i;
@@ -315,7 +289,7 @@ public sealed class SpamSumContext : IChecksum
if(h != 0)
{
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
result[resultOff] = _b64[_self.Bh[bi].H % 64];
@@ -330,7 +304,7 @@ public sealed class SpamSumContext : IChecksum
}
else if(_self.Bh[bi].Digest[i] != 0)
{
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
result[resultOff] = _self.Bh[bi].Digest[i];
@@ -344,7 +318,7 @@ public sealed class SpamSumContext : IChecksum
}
}
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
result[resultOff++] = 0x3A; // ':'
--remain;
@@ -354,7 +328,7 @@ public sealed class SpamSumContext : IChecksum
++bi;
i = (int)_self.Bh[bi].Dlen;
if(i > remain) throw new Exception(Localization.Assertion_failed);
if(i > remain) throw new Exception(Localization.Localization.Assertion_failed);
Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i);
resultOff += i;
@@ -362,7 +336,7 @@ public sealed class SpamSumContext : IChecksum
if(h != 0)
{
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
h = _self.Bh[bi].Halfh;
result[resultOff] = _b64[h % 64];
@@ -382,7 +356,7 @@ public sealed class SpamSumContext : IChecksum
if(i != 0)
{
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
result[resultOff] = (byte)i;
@@ -399,9 +373,9 @@ public sealed class SpamSumContext : IChecksum
}
else if(h != 0)
{
if(_self.Bh[bi].Dlen != 0) throw new Exception(Localization.Assertion_failed);
if(_self.Bh[bi].Dlen != 0) throw new Exception(Localization.Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Assertion_failed);
if(remain <= 0) throw new Exception(Localization.Localization.Assertion_failed);
result[resultOff++] = _b64[_self.Bh[bi].H % 64];
/* No need to bother with FUZZY_FLAG_ELIMSEQ, because this
@@ -414,13 +388,14 @@ public sealed class SpamSumContext : IChecksum
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
public static byte[] File(string filename) => throw new NotImplementedException(Localization.Spamsum_no_binary);
public static byte[] File(string filename) =>
throw new NotImplementedException(Localization.Localization.SpamSum_does_not_have_a_binary_representation);
/// <summary>Gets the hash of a file in hexadecimal and as a byte array.</summary>
/// <param name="filename">File path.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string File(string filename, out byte[] hash) =>
throw new NotImplementedException(Localization.Not_yet_implemented);
throw new NotImplementedException(Localization.Localization.Not_yet_implemented);
/// <summary>Gets the hash of the specified data buffer.</summary>
/// <param name="data">Data buffer.</param>
@@ -448,7 +423,7 @@ public sealed class SpamSumContext : IChecksum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static string CToString(byte[] cString)
{
var count = 0;
int count = 0;
// ReSharper disable once LoopCanBeConvertedToQuery
// LINQ is six times slower
@@ -462,16 +437,7 @@ public sealed class SpamSumContext : IChecksum
return Encoding.ASCII.GetString(cString, 0, count);
}
struct RollState
{
public byte[] Window;
// ROLLING_WINDOW
public uint H1;
public uint H2;
public uint H3;
public uint N;
}
#region Nested type: BlockhashContext
/* A blockhash contains a signature state for a specific (implicit) blocksize.
* The blocksize is given by SSDEEP_BS(index). The h and halfh members are the
@@ -489,6 +455,10 @@ public sealed class SpamSumContext : IChecksum
public uint Dlen;
}
#endregion
#region Nested type: FuzzyState
struct FuzzyState
{
public uint Bhstart;
@@ -499,4 +469,64 @@ public sealed class SpamSumContext : IChecksum
public ulong TotalSize;
public RollState Roll;
}
#endregion
#region Nested type: RollState
struct RollState
{
public byte[] Window;
// ROLLING_WINDOW
public uint H1;
public uint H2;
public uint H3;
public uint N;
}
#endregion
#region IChecksum Members
/// <inheritdoc />
public string Name => Localization.Localization.SpamSum_Name;
/// <inheritdoc />
public Guid Id => new("DA692981-3291-47D8-B8B9-A87F0605F6E9");
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len)
{
_self.TotalSize += len;
for(int i = 0; i < len; i++) fuzzy_engine_step(data[i]);
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final() =>
throw new NotImplementedException(Localization.Localization.SpamSum_does_not_have_a_binary_representation);
/// <inheritdoc />
/// <summary>Returns a base64 representation of the hash value.</summary>
public string End()
{
FuzzyDigest(out byte[] result);
return CToString(result);
}
#endregion
}

View File

@@ -58,7 +58,7 @@ public static class Extensions
/// </returns>
public static int EnsureRead(this Stream s, byte[] buffer, int offset, int count)
{
var pos = 0;
int pos = 0;
int read;
do

View File

@@ -509,7 +509,7 @@ public sealed class Fuse : FileSystem
{
xattr = new byte[hash.Length / 2];
for(var i = 0; i < xattr.Length; i++)
for(int i = 0; i < xattr.Length; i++)
{
if(hash[i * 2] >= 0x30 && hash[i * 2] <= 0x39)
xattr[i] = (byte)((hash[i * 2] - 0x30) * 0x10);
@@ -823,8 +823,8 @@ public sealed class Fuse : FileSystem
public void Umount()
{
var rnd = new Random();
var token = new byte[64];
var rnd = new Random();
byte[] token = new byte[64];
rnd.NextBytes(token);
_umountToken = Base32.ToBase32String(token);
setxattr(Path.Combine(MountPoint, ".fuse_umount"), _umountToken, IntPtr.Zero, 0, 0);

View File

@@ -7,10 +7,12 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using SharpCompress.Compressors;
using SharpCompress.Compressors.LZMA;
using ZstdSharp;
namespace RomRepoMgr.Core.Filesystem;
@@ -18,7 +20,7 @@ namespace RomRepoMgr.Core.Filesystem;
// TODO: Invalidate caches
// TODO: Mount options
// TODO: Do not show machines or romsets with no ROMs in repo
public class Vfs : IDisposable
public class Vfs(ILoggerFactory loggerFactory) : IDisposable
{
readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedDisk>> _machineDisksCache = [];
readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedFile>> _machineFilesCache = [];
@@ -98,7 +100,7 @@ public class Vfs : IDisposable
internal void GetInfo(out ulong files, out ulong totalSize)
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
totalSize = (ulong)(ctx.Files.Where(f => f.IsInRepo).Sum(f => (double)f.Size) +
ctx.Disks.Where(f => f.IsInRepo).Sum(f => (double)f.Size) +
@@ -114,7 +116,7 @@ public class Vfs : IDisposable
void FillRootDirectoryCache()
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
var rootCache = new ConcurrentDictionary<string, long>();
@@ -180,7 +182,7 @@ public class Vfs : IDisposable
{
if(_romSetsCache.TryGetValue(id, out RomSet romSet)) return romSet;
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
romSet = ctx.RomSets.Find(id);
@@ -199,7 +201,7 @@ public class Vfs : IDisposable
cachedMachines = [];
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
foreach(Machine mach in ctx.Machines.Where(m => m.RomSet.Id == id))
{
@@ -231,7 +233,7 @@ public class Vfs : IDisposable
if(cachedMachineFiles != null) return cachedMachineFiles;
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
cachedMachineFiles = [];
@@ -267,7 +269,7 @@ public class Vfs : IDisposable
if(cachedMachineDisks != null) return cachedMachineDisks;
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
cachedMachineDisks = [];
@@ -301,7 +303,7 @@ public class Vfs : IDisposable
if(cachedMachineMedias != null) return cachedMachineMedias;
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
cachedMachineMedias = [];
@@ -364,9 +366,9 @@ public class Vfs : IDisposable
internal long Open(string sha384, long fileSize)
{
var sha384Bytes = new byte[48];
byte[] sha384Bytes = new byte[48];
for(var i = 0; i < 48; i++)
for(int i = 0; i < 48; i++)
{
if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39)
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);
@@ -385,19 +387,66 @@ public class Vfs : IDisposable
string sha384B32 = Base32.ToBase32String(sha384Bytes);
string repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32 + ".lz");
if(!File.Exists(repoPath)) return -1;
string repoPath;
repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32 + ".lz");
long handle;
if(!File.Exists(repoPath))
{
repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32 + ".zst");
if(!File.Exists(repoPath))
{
repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32);
if(!File.Exists(repoPath)) return -1;
_lastHandle++;
handle = _lastHandle;
_streamsCache[handle] = Stream.Synchronized(new FileStream(repoPath, FileMode.Open, FileAccess.Read));
return handle;
}
_lastHandle++;
handle = _lastHandle;
_streamsCache[handle] =
Stream.Synchronized(new ForcedSeekStream<DecompressionStream>(fileSize,
new FileStream(repoPath,
FileMode.Open,
FileAccess.Read)));
return handle;
}
_lastHandle++;
long handle = _lastHandle;
handle = _lastHandle;
_streamsCache[handle] =
Stream.Synchronized(new ForcedSeekStream<LZipStream>(fileSize,
@@ -456,9 +505,9 @@ public class Vfs : IDisposable
if(sha1 != null)
{
var sha1Bytes = new byte[20];
byte[] sha1Bytes = new byte[20];
for(var i = 0; i < 20; i++)
for(int i = 0; i < 20; i++)
{
if(sha1[i * 2] >= 0x30 && sha1[i * 2] <= 0x39)
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
@@ -489,9 +538,9 @@ public class Vfs : IDisposable
if(md5 != null)
{
var md5Bytes = new byte[16];
byte[] md5Bytes = new byte[16];
for(var i = 0; i < 16; i++)
for(int i = 0; i < 16; i++)
{
if(md5[i * 2] >= 0x30 && md5[i * 2] <= 0x39)
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);
@@ -544,9 +593,9 @@ public class Vfs : IDisposable
if(sha256 != null)
{
var sha256Bytes = new byte[32];
byte[] sha256Bytes = new byte[32];
for(var i = 0; i < 32; i++)
for(int i = 0; i < 32; i++)
{
if(sha256[i * 2] >= 0x30 && sha256[i * 2] <= 0x39)
sha256Bytes[i] = (byte)((sha256[i * 2] - 0x30) * 0x10);
@@ -578,9 +627,9 @@ public class Vfs : IDisposable
if(sha1 != null)
{
var sha1Bytes = new byte[20];
byte[] sha1Bytes = new byte[20];
for(var i = 0; i < 20; i++)
for(int i = 0; i < 20; i++)
{
if(sha1[i * 2] >= 0x30 && sha1[i * 2] <= 0x39)
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
@@ -611,9 +660,9 @@ public class Vfs : IDisposable
if(md5 != null)
{
var md5Bytes = new byte[16];
byte[] md5Bytes = new byte[16];
for(var i = 0; i < 16; i++)
for(int i = 0; i < 16; i++)
{
if(md5[i * 2] >= 0x30 && md5[i * 2] <= 0x39)
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);

View File

@@ -369,7 +369,7 @@ public class Winfsp(Vfs vfs) : FileSystemBase
if(fileNode is not FileNode { Handle: > 0 } node) return STATUS_INVALID_HANDLE;
var buf = new byte[length];
byte[] buf = new byte[length];
int ret = vfs.Read(node.Handle, buf, (long)offset);
@@ -575,10 +575,10 @@ public class Winfsp(Vfs vfs) : FileSystemBase
if(securityDescriptor == null) return STATUS_SUCCESS;
var rootSddl = "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
string rootSddl = "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
var rootSecurityDescriptor = new RawSecurityDescriptor(rootSddl);
var fileSecurity = new byte[rootSecurityDescriptor.BinaryLength];
var rootSecurityDescriptor = new RawSecurityDescriptor(rootSddl);
byte[] fileSecurity = new byte[rootSecurityDescriptor.BinaryLength];
rootSecurityDescriptor.GetBinaryForm(fileSecurity, 0);
securityDescriptor = fileSecurity;

View File

@@ -89,7 +89,7 @@ internal sealed class ForcedSeekStream<T> : Stream where T : Stream
do
{
var buffer = new byte[BUFFER_LEN];
byte[] buffer = new byte[BUFFER_LEN];
read = _baseStream.Read(buffer, 0, BUFFER_LEN);
_backStream.Write(buffer, 0, read);
} while(read == BUFFER_LEN);
@@ -111,19 +111,19 @@ internal sealed class ForcedSeekStream<T> : Stream where T : Stream
_backStream.Position = _backStream.Length;
long toPosition = position - _backStream.Position;
var fullBufferReads = (int)(toPosition / BUFFER_LEN);
var restToRead = (int)(toPosition % BUFFER_LEN);
int fullBufferReads = (int)(toPosition / BUFFER_LEN);
int restToRead = (int)(toPosition % BUFFER_LEN);
byte[] buffer;
for(var i = 0; i < fullBufferReads; i++)
for(int i = 0; i < fullBufferReads; i++)
{
buffer = new byte[BUFFER_LEN];
_baseStream.EnsureRead(buffer, 0, BUFFER_LEN);
_baseStream.ReadExactly(buffer, 0, BUFFER_LEN);
_backStream.Write(buffer, 0, BUFFER_LEN);
}
buffer = new byte[restToRead];
_baseStream.EnsureRead(buffer, 0, restToRead);
_baseStream.ReadExactly(buffer, 0, restToRead);
_backStream.Write(buffer, 0, restToRead);
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Text.Json.Serialization;
namespace RomRepoMgr.Core.Models;
[JsonSerializable(typeof(lsar))]
public partial class lsarJsonContext : JsonSerializerContext {}
public class lsar
{
public int lsarFormatVersion { get; set; }
public string lsarEncoding { get; set; }
public int lsarConfidence { get; set; }
public string lsarFormatName { get; set; }
public lsarProperties lsarProperties { get; set; }
public lsarContents[] lsarContents { get; set; }
public int lsarError { get; set; }
public string lsarInnerFormatName { get; set; }
public lsarProperties lsarInnerProperties { get; set; }
}
public class lsarProperties
{
public string[] XADVolumes { get; set; }
public string XADComment { get; set; }
public string XADArchiveName { get; set; }
public bool XADIsSolid { get; set; }
public DateTime XADCreationDate { get; set; }
public DateTime XADLastModificationDate { get; set; }
public string ARJOriginalArchiveName { get; set; }
}
public class lsarContents
{
public long XADCompressedSize { get; set; }
public long XADDataLength { get; set; }
public short ZipFlags { get; set; }
public short ZipFileAttributes { get; set; }
public long XADDataOffset { get; set; }
public long XADIndex { get; set; }
public string ZipOSName { get; set; }
public short XADPosixPermissions { get; set; }
public uint ZipCRC32 { get; set; }
public int ZipLocalDate { get; set; }
public short ZipOS { get; set; }
public short ZipCompressionMethod { get; set; }
public string XADCompressionName { get; set; }
public short ZipExtractVersion { get; set; }
public string XADFileName { get; set; }
public DateTime XADLastModificationDate { get; set; }
public short XADDOSFileAttributes { get; set; }
public long XADFileSize { get; set; }
public bool TARIsSparseFile { get; set; }
public long XADFirstSolidEntry { get; set; }
public string XADSolidObject { get; set; }
public long XADFirstSolidIndex { get; set; }
public short XADPosixUser { get; set; }
public short XADPosixGroup { get; set; }
public bool XADIsSolid { get; set; }
public DateTime XADLastAccessDate { get; set; }
public int ARJCRC32 { get; set; }
public int ARJMethod { get; set; }
public int ARJMinimumVersion { get; set; }
public int ARJFileType { get; set; }
public int ARJFlags { get; set; }
public string ARJOSName { get; set; }
public int ARJOS { get; set; }
public int ARJVersion { get; set; }
public int GzipExtraFlags { get; set; }
public string GzipFilename { get; set; }
public int GzipOS { get; set; }
public short LHAHeaderLevel { get; set; }
public string LHAExtFileNameData { get; set; }
public short LHACRC16 { get; set; }
public string LHAOSName { get; set; }
public int LHAOS { get; set; }
public string RAR5OSName { get; set; }
public int RAR5Attributes { get; set; }
public long RAR5DataLength { get; set; }
public long RAR5CompressionMethod { get; set; }
public long RAR5DictionarySize { get; set; }
public uint RAR5CRC32 { get; set; }
public int RAR5Flags { get; set; }
public int RAR5DataOffset { get; set; }
public int RAR5CompressionVersion { get; set; }
public int RAR5CompressionInformation { get; set; }
public int RAR5OS { get; set; }
public RAR5InputParts[] RAR5InputParts { get; set; }
}
public class RAR5InputParts
{
public uint CRC32 { get; set; }
public long InputLength { get; set; }
public long Offset { get; set; }
}

View File

@@ -11,46 +11,32 @@ namespace RomRepoMgr.Core.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localization {
private static global::System.Resources.ResourceManager resourceMan;
private static System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
private static System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localization() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RomRepoMgr.Core.Resources.Localization", typeof(Localization).Assembly);
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RomRepoMgr.Core.Resources.Localization", typeof(Localization).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -59,525 +45,357 @@ namespace RomRepoMgr.Core.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Adding DAT to database....
/// </summary>
internal static string AddingDatToDatabase {
get {
return ResourceManager.GetString("AddingDatToDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding disks....
/// </summary>
internal static string AddingDisks {
get {
return ResourceManager.GetString("AddingDisks", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding machines (games)....
/// </summary>
internal static string AddingMachines {
get {
return ResourceManager.GetString("AddingMachines", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding medias....
/// </summary>
internal static string AddingMedias {
get {
return ResourceManager.GetString("AddingMedias", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding ROMs....
/// </summary>
internal static string AddingRoms {
get {
return ResourceManager.GetString("AddingRoms", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Assertion failed.
/// </summary>
internal static string Assertion_failed {
get {
return ResourceManager.GetString("Assertion_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specified string is not valid Base32 format because character &quot;{0}&quot; does not exist in Base32 alphabet.
/// </summary>
internal static string Base32_Invalid_format {
get {
return ResourceManager.GetString("Base32_Invalid_format", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specified string is not valid Base32 format because it doesn&apos;t have enough data to construct a complete byte array.
/// </summary>
internal static string Base32_Not_enought_data {
get {
return ResourceManager.GetString("Base32_Not_enought_data", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot seek after stream end..
/// </summary>
internal static string Cannot_seek_after_end {
internal static string Base32_Invalid_format {
get {
return ResourceManager.GetString("Cannot_seek_after_end", resourceCulture);
return ResourceManager.GetString("Base32_Invalid_format", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot seek before stream start..
/// </summary>
internal static string Cannot_seek_before_start {
get {
return ResourceManager.GetString("Cannot_seek_before_start", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot find file with hash {0} in the repository.
/// </summary>
internal static string CannotFindHashInRepository {
internal static string Cannot_seek_after_end {
get {
return ResourceManager.GetString("CannotFindHashInRepository", resourceCulture);
return ResourceManager.GetString("Cannot_seek_after_end", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot find lsar executable..
/// </summary>
internal static string CannotFindLsAr {
get {
return ResourceManager.GetString("CannotFindLsAr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot find unar executable at {0}..
/// </summary>
internal static string CannotFindUnArAtPath {
get {
return ResourceManager.GetString("CannotFindUnArAtPath", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot find requested zip entry in hashes dictionary.
/// </summary>
internal static string CannotFindZipEntryInDictionary {
get {
return ResourceManager.GetString("CannotFindZipEntryInDictionary", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot run lsar..
/// </summary>
internal static string CannotRunLsAr {
get {
return ResourceManager.GetString("CannotRunLsAr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot run unar..
/// </summary>
internal static string CannotRunUnAr {
get {
return ResourceManager.GetString("CannotRunUnAr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checking if file is an archive....
/// </summary>
internal static string CheckingIfFIleIsAnArchive {
get {
return ResourceManager.GetString("CheckingIfFIleIsAnArchive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Compressing {0}....
/// </summary>
internal static string Compressing {
get {
return ResourceManager.GetString("Compressing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Compressing DAT file....
/// </summary>
internal static string CompressingDatFile {
get {
return ResourceManager.GetString("CompressingDatFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Compressing file....
/// </summary>
internal static string CompressingFile {
get {
return ResourceManager.GetString("CompressingFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copying {0}....
/// </summary>
internal static string Copying {
get {
return ResourceManager.GetString("Copying", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copying file....
/// </summary>
internal static string CopyingFile {
get {
return ResourceManager.GetString("CopyingFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not find ROM set in database..
/// </summary>
internal static string CouldNotFindRomSetInDatabase {
get {
return ResourceManager.GetString("CouldNotFindRomSetInDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to File exists.
/// </summary>
internal static string DatabaseFileExistsMsgBoxTitle {
get {
return ResourceManager.GetString("DatabaseFileExistsMsgBoxTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DAT file is already in database, not importing duplicates..
/// </summary>
internal static string DatAlreadyInDatabase {
get {
return ResourceManager.GetString("DatAlreadyInDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Imported {0} machines with {1} ROMs..
/// </summary>
internal static string DatImportSuccess {
get {
return ResourceManager.GetString("DatImportSuccess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enumerating files....
/// </summary>
internal static string EnumeratingFiles {
get {
return ResourceManager.GetString("EnumeratingFiles", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error: {0}.
/// </summary>
internal static string ErrorWithMessage {
get {
return ResourceManager.GetString("ErrorWithMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exporting ROMs....
/// </summary>
internal static string ExportingRoms {
get {
return ResourceManager.GetString("ExportingRoms", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extracted contents.
/// </summary>
internal static string ExtractedContents {
get {
return ResourceManager.GetString("ExtractedContents", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extracting archive contents....
/// </summary>
internal static string ExtractingArchive {
get {
return ResourceManager.GetString("ExtractingArchive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished.
/// </summary>
internal static string Finished {
get {
return ResourceManager.GetString("Finished", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finishing....
/// </summary>
internal static string Finishing {
get {
return ResourceManager.GetString("Finishing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Found a disk with an unknown machine, this should not happen..
/// </summary>
internal static string FoundDiskWithoutMachine {
get {
return ResourceManager.GetString("FoundDiskWithoutMachine", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Found media with an unknown machine, this should not happen..
/// </summary>
internal static string FoundMediaWithoutMachine {
get {
return ResourceManager.GetString("FoundMediaWithoutMachine", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Found a ROM with an unknown machine, this should not happen..
/// </summary>
internal static string FoundRomWithoutMachine {
get {
return ResourceManager.GetString("FoundRomWithoutMachine", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Getting machine (game) names....
/// </summary>
internal static string GettingMachineNames {
get {
return ResourceManager.GetString("GettingMachineNames", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hashing DAT file....
/// </summary>
internal static string HashingDatFile {
get {
return ResourceManager.GetString("HashingDatFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hashing file....
/// </summary>
internal static string HashingFile {
get {
return ResourceManager.GetString("HashingFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Importing {0}....
/// </summary>
internal static string Importing {
get {
return ResourceManager.GetString("Importing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No checksums found..
/// </summary>
internal static string NoChecksumsFound {
get {
return ResourceManager.GetString("NoChecksumsFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not yet implemented..
/// </summary>
internal static string Not_yet_implemented {
get {
return ResourceManager.GetString("Not_yet_implemented", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not a CHD file..
/// </summary>
internal static string NotAChdFile {
get {
return ResourceManager.GetString("NotAChdFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not an AaruFormat file..
/// </summary>
internal static string NotAnAaruFormatFile {
get {
return ResourceManager.GetString("NotAnAaruFormatFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not the correct lsar executable.
/// </summary>
internal static string NotCorrectLsAr {
get {
return ResourceManager.GetString("NotCorrectLsAr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not the correct unar executable.
/// </summary>
internal static string NotCorrectUnAr {
get {
return ResourceManager.GetString("NotCorrectUnAr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
internal static string OK {
get {
return ResourceManager.GetString("OK", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Parsing DAT file....
/// </summary>
internal static string ParsinDatFile {
get {
return ResourceManager.GetString("ParsinDatFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Removing temporary path....
/// </summary>
internal static string RemovingTemporaryPath {
get {
return ResourceManager.GetString("RemovingTemporaryPath", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving ROMs and disks....
/// </summary>
internal static string RetrievingRomsAndDisks {
get {
return ResourceManager.GetString("RetrievingRomsAndDisks", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving ROM set from database..
/// </summary>
internal static string RetrievingRomSetFromDatabase {
get {
return ResourceManager.GetString("RetrievingRomSetFromDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Saving changes to database....
/// </summary>
internal static string SavingChangesToDatabase {
get {
return ResourceManager.GetString("SavingChangesToDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The input exceeds data types..
/// </summary>
internal static string Spamsum_Input_exceeds_data {
get {
return ResourceManager.GetString("Spamsum_Input_exceeds_data", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SpamSum does not have a binary representation..
/// </summary>
internal static string Spamsum_no_binary {
get {
return ResourceManager.GetString("Spamsum_no_binary", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to unar path is not set..
/// </summary>
internal static string Assertion_failed {
get {
return ResourceManager.GetString("Assertion_failed", resourceCulture);
}
}
internal static string Spamsum_Input_exceeds_data {
get {
return ResourceManager.GetString("Spamsum_Input_exceeds_data", resourceCulture);
}
}
internal static string Not_yet_implemented {
get {
return ResourceManager.GetString("Not_yet_implemented", resourceCulture);
}
}
internal static string DatabaseFileExistsMsgBoxTitle {
get {
return ResourceManager.GetString("DatabaseFileExistsMsgBoxTitle", resourceCulture);
}
}
internal static string UnArPathNotSet {
get {
return ResourceManager.GetString("UnArPathNotSet", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unhandled exception occurred..
/// </summary>
internal static string CannotFindUnArAtPath {
get {
return ResourceManager.GetString("CannotFindUnArAtPath", resourceCulture);
}
}
internal static string CannotFindLsAr {
get {
return ResourceManager.GetString("CannotFindLsAr", resourceCulture);
}
}
internal static string CannotRunUnAr {
get {
return ResourceManager.GetString("CannotRunUnAr", resourceCulture);
}
}
internal static string CannotRunLsAr {
get {
return ResourceManager.GetString("CannotRunLsAr", resourceCulture);
}
}
internal static string NotCorrectUnAr {
get {
return ResourceManager.GetString("NotCorrectUnAr", resourceCulture);
}
}
internal static string NotCorrectLsAr {
get {
return ResourceManager.GetString("NotCorrectLsAr", resourceCulture);
}
}
internal static string ParsinDatFile {
get {
return ResourceManager.GetString("ParsinDatFile", resourceCulture);
}
}
internal static string HashingDatFile {
get {
return ResourceManager.GetString("HashingDatFile", resourceCulture);
}
}
internal static string DatAlreadyInDatabase {
get {
return ResourceManager.GetString("DatAlreadyInDatabase", resourceCulture);
}
}
internal static string AddingDatToDatabase {
get {
return ResourceManager.GetString("AddingDatToDatabase", resourceCulture);
}
}
internal static string CompressingDatFile {
get {
return ResourceManager.GetString("CompressingDatFile", resourceCulture);
}
}
internal static string GettingMachineNames {
get {
return ResourceManager.GetString("GettingMachineNames", resourceCulture);
}
}
internal static string AddingMachines {
get {
return ResourceManager.GetString("AddingMachines", resourceCulture);
}
}
internal static string SavingChangesToDatabase {
get {
return ResourceManager.GetString("SavingChangesToDatabase", resourceCulture);
}
}
internal static string RetrievingRomsAndDisks {
get {
return ResourceManager.GetString("RetrievingRomsAndDisks", resourceCulture);
}
}
internal static string AddingRoms {
get {
return ResourceManager.GetString("AddingRoms", resourceCulture);
}
}
internal static string FoundRomWithoutMachine {
get {
return ResourceManager.GetString("FoundRomWithoutMachine", resourceCulture);
}
}
internal static string UnhandledException {
get {
return ResourceManager.GetString("UnhandledException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unhandled exception when importing file..
/// </summary>
internal static string RetrievingRomSetFromDatabase {
get {
return ResourceManager.GetString("RetrievingRomSetFromDatabase", resourceCulture);
}
}
internal static string CouldNotFindRomSetInDatabase {
get {
return ResourceManager.GetString("CouldNotFindRomSetInDatabase", resourceCulture);
}
}
internal static string ExportingRoms {
get {
return ResourceManager.GetString("ExportingRoms", resourceCulture);
}
}
internal static string Finished {
get {
return ResourceManager.GetString("Finished", resourceCulture);
}
}
internal static string CannotFindZipEntryInDictionary {
get {
return ResourceManager.GetString("CannotFindZipEntryInDictionary", resourceCulture);
}
}
internal static string CannotFindHashInRepository {
get {
return ResourceManager.GetString("CannotFindHashInRepository", resourceCulture);
}
}
internal static string Compressing {
get {
return ResourceManager.GetString("Compressing", resourceCulture);
}
}
internal static string EnumeratingFiles {
get {
return ResourceManager.GetString("EnumeratingFiles", resourceCulture);
}
}
internal static string Importing {
get {
return ResourceManager.GetString("Importing", resourceCulture);
}
}
internal static string CheckingIfFIleIsAnArchive {
get {
return ResourceManager.GetString("CheckingIfFIleIsAnArchive", resourceCulture);
}
}
internal static string OK {
get {
return ResourceManager.GetString("OK", resourceCulture);
}
}
internal static string ErrorWithMessage {
get {
return ResourceManager.GetString("ErrorWithMessage", resourceCulture);
}
}
internal static string ExtractingArchive {
get {
return ResourceManager.GetString("ExtractingArchive", resourceCulture);
}
}
internal static string RemovingTemporaryPath {
get {
return ResourceManager.GetString("RemovingTemporaryPath", resourceCulture);
}
}
internal static string ExtractedContents {
get {
return ResourceManager.GetString("ExtractedContents", resourceCulture);
}
}
internal static string HashingFile {
get {
return ResourceManager.GetString("HashingFile", resourceCulture);
}
}
internal static string UnknownFile {
get {
return ResourceManager.GetString("UnknownFile", resourceCulture);
}
}
internal static string CompressingFile {
get {
return ResourceManager.GetString("CompressingFile", resourceCulture);
}
}
internal static string Finishing {
get {
return ResourceManager.GetString("Finishing", resourceCulture);
}
}
internal static string UnhandledExceptionWhenImporting {
get {
return ResourceManager.GetString("UnhandledExceptionWhenImporting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unknown file..
/// </summary>
internal static string UnknownFile {
internal static string AddingDisks {
get {
return ResourceManager.GetString("UnknownFile", resourceCulture);
return ResourceManager.GetString("AddingDisks", resourceCulture);
}
}
internal static string FoundDiskWithoutMachine {
get {
return ResourceManager.GetString("FoundDiskWithoutMachine", resourceCulture);
}
}
internal static string NotAChdFile {
get {
return ResourceManager.GetString("NotAChdFile", resourceCulture);
}
}
internal static string NoChecksumsFound {
get {
return ResourceManager.GetString("NoChecksumsFound", resourceCulture);
}
}
internal static string Copying {
get {
return ResourceManager.GetString("Copying", resourceCulture);
}
}
internal static string CopyingFile {
get {
return ResourceManager.GetString("CopyingFile", resourceCulture);
}
}
internal static string AddingMedias {
get {
return ResourceManager.GetString("AddingMedias", resourceCulture);
}
}
internal static string FoundMediaWithoutMachine {
get {
return ResourceManager.GetString("FoundMediaWithoutMachine", resourceCulture);
}
}
internal static string NotAnAaruFormatFile {
get {
return ResourceManager.GetString("NotAnAaruFormatFile", resourceCulture);
}
}
internal static string DatImportSuccess {
get {
return ResourceManager.GetString("DatImportSuccess", resourceCulture);
}
}
internal static string ImportingFile {
get {
return ResourceManager.GetString("ImportingFile", resourceCulture);
}
}
}

View File

@@ -186,4 +186,7 @@
<data name="AddingMedias" xml:space="preserve">
<value>Añadiendo medios...</value>
</data>
<data name="ImportingFile" xml:space="preserve">
<value>Importando fichero...</value>
</data>
</root>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root"
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
</xsd:schema>
@@ -195,4 +196,7 @@
<data name="DatImportSuccess" xml:space="preserve">
<value>Imported {0} machines with {1} ROMs.</value>
</data>
<data name="ImportingFile" xml:space="preserve">
<value>Importing file...</value>
</data>
</root>

View File

@@ -1,6 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0-beta.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aaru.Checksums.Native"/>
<PackageReference Include="DotNetZip"/>
<PackageReference Include="EFCore.BulkExtensions"/>
<PackageReference Include="Mono.Fuse.NETStandard"/>
@@ -18,6 +27,7 @@
<PackageReference Include="Roslynator.Formatting.Analyzers"/>
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer"/>
<PackageReference Include="Text.Analyzers"/>
<PackageReference Include="ZstdSharp.Port"/>
</ItemGroup>
<ItemGroup>
@@ -25,7 +35,7 @@
<ProjectReference Include="..\RomRepoMgr.Settings\RomRepoMgr.Settings.csproj"/>
<ProjectReference Include="..\SabreTools\SabreTools.DatFiles\SabreTools.DatFiles.csproj"/>
<ProjectReference Include="..\SabreTools\SabreTools.DatItems\SabreTools.DatItems.csproj"/>
<ProjectReference Include="..\SabreTools\SabreTools.DatTools\SabreTools.DatTools.csproj" />
<ProjectReference Include="..\SabreTools\SabreTools.DatTools\SabreTools.DatTools.csproj"/>
<ProjectReference Include="..\SabreTools\SabreTools.FileTypes\SabreTools.FileTypes.csproj"/>
</ItemGroup>

View File

@@ -32,7 +32,8 @@
using System.Collections.Generic;
using System.Threading;
using Aaru.Checksums;
using RomRepoMgr.Core.Checksums;
using Crc32Context = RomRepoMgr.Core.Checksums.Crc32Context;
namespace RomRepoMgr.Core.Workers;

View File

@@ -28,8 +28,11 @@ using System.Diagnostics;
using System.IO;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Resources;
using RomRepoMgr.Settings;
using SharpCompress.Compressors;
using SharpCompress.Compressors.LZMA;
using ZstdSharp;
using ZstdSharp.Unsafe;
using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
namespace RomRepoMgr.Core.Workers;
@@ -45,11 +48,32 @@ public sealed class Compression
public void CompressFile(string source, string destination)
{
var inFs = new FileStream(source, FileMode.Open, FileAccess.Read);
var outFs = new FileStream(destination, FileMode.CreateNew, FileAccess.Write);
Stream zStream = new LZipStream(outFs, CompressionMode.Compress);
var inFs = new FileStream(source, FileMode.Open, FileAccess.Read);
var outFs = new FileStream(destination, FileMode.CreateNew, FileAccess.Write);
var buffer = new byte[BUFFER_SIZE];
Stream zStream;
switch(Settings.Settings.Current.Compression)
{
case CompressionType.Zstd:
{
var zstdStream = new CompressionStream(outFs, 15);
zstdStream.SetParameter(ZSTD_cParameter.ZSTD_c_nbWorkers, Environment.ProcessorCount);
zStream = zstdStream;
break;
}
case CompressionType.None:
zStream = outFs;
break;
default:
zStream = new LZipStream(outFs, CompressionMode.Compress);
break;
}
byte[] buffer = new byte[BUFFER_SIZE];
SetProgressBounds?.Invoke(this,
new ProgressBoundsEventArgs
@@ -66,7 +90,7 @@ public sealed class Compression
Value = inFs.Position
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
zStream.Write(buffer, 0, buffer.Length);
}
@@ -79,7 +103,7 @@ public sealed class Compression
Maximum = inFs.Length
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
zStream.Write(buffer, 0, buffer.Length);
inFs.Close();
@@ -89,9 +113,19 @@ public sealed class Compression
public void DecompressFile(string source, string destination)
{
var inFs = new FileStream(source, FileMode.Open, FileAccess.Read);
var outFs = new FileStream(destination, FileMode.Create, FileAccess.Write);
Stream zStream = new LZipStream(inFs, CompressionMode.Decompress);
var inFs = new FileStream(source, FileMode.Open, FileAccess.Read);
var outFs = new FileStream(destination, FileMode.Create, FileAccess.Write);
Stream zStream;
if(Path.GetExtension(source) == ".zst")
zStream = new DecompressionStream(inFs);
else if(Path.GetExtension(source) == ".lz")
zStream = new LZipStream(inFs, CompressionMode.Decompress);
else if(string.IsNullOrWhiteSpace(Path.GetExtension(source)))
zStream = inFs;
else
throw new ArgumentException($"Invalid compression extension {Path.GetExtension(source)}");
zStream.CopyTo(outFs);

View File

@@ -30,9 +30,11 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using Aaru.Checksums;
using System.Threading;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using RomRepoMgr.Core.Checksums;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Core.Resources;
@@ -53,15 +55,18 @@ namespace RomRepoMgr.Core.Workers;
public sealed class DatImporter
{
readonly string _category;
readonly string _datFilesPath;
readonly string _datPath;
bool _aborted;
static readonly Lock DbLock = new();
readonly string _category;
readonly string _datFilesPath;
readonly string _datPath;
readonly ILoggerFactory _loggerFactory;
bool _aborted;
public DatImporter(string datPath, string category)
public DatImporter(string datPath, string category, ILoggerFactory loggerFactory)
{
_datPath = datPath;
_datFilesPath = Path.Combine(Settings.Settings.Current.RepositoryPath, "datfiles");
_datPath = datPath;
_datFilesPath = Path.Combine(Settings.Settings.Current.RepositoryPath, "datfiles");
_loggerFactory = loggerFactory;
if(!string.IsNullOrWhiteSpace(category)) _category = category;
}
@@ -70,7 +75,7 @@ public sealed class DatImporter
{
try
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, _loggerFactory);
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
@@ -132,8 +137,11 @@ public sealed class DatImporter
Category = _category
};
ctx.RomSets.Add(romSet);
ctx.SaveChanges();
lock(DbLock)
{
ctx.RomSets.Add(romSet);
ctx.SaveChanges();
}
SetMessage?.Invoke(this,
new MessageEventArgs
@@ -167,7 +175,7 @@ public sealed class DatImporter
Maximum = machineNames.Count
});
var position = 0;
int position = 0;
var machines = new Dictionary<string, Machine>();
foreach(string name in machineNames)
@@ -199,8 +207,11 @@ public sealed class DatImporter
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
ctx.BulkInsert(machines.Values.ToList(), b => b.SetOutputIdentity = true);
ctx.SaveChanges();
lock(DbLock)
{
ctx.BulkInsert(machines.Values.ToList(), b => b.SetOutputIdentity = true);
ctx.SaveChanges();
}
SetMessage?.Invoke(this,
new MessageEventArgs
@@ -212,29 +223,29 @@ public sealed class DatImporter
var disks = new List<Disk>();
var medias = new List<Media>();
var tmpRomCrc32Table = Guid.NewGuid().ToString();
var tmpRomMd5Table = Guid.NewGuid().ToString();
var tmpRomSha1Table = Guid.NewGuid().ToString();
var tmpRomSha256Table = Guid.NewGuid().ToString();
var tmpRomSha384Table = Guid.NewGuid().ToString();
var tmpRomSha512Table = Guid.NewGuid().ToString();
var tmpDiskMd5Table = Guid.NewGuid().ToString();
var tmpDiskSha1Table = Guid.NewGuid().ToString();
var tmpMediaMd5Table = Guid.NewGuid().ToString();
var tmpMediaSha1Table = Guid.NewGuid().ToString();
var tmpMediaSha256Table = Guid.NewGuid().ToString();
string tmpRomCrc32Table = Guid.NewGuid().ToString();
string tmpRomMd5Table = Guid.NewGuid().ToString();
string tmpRomSha1Table = Guid.NewGuid().ToString();
string tmpRomSha256Table = Guid.NewGuid().ToString();
string tmpRomSha384Table = Guid.NewGuid().ToString();
string tmpRomSha512Table = Guid.NewGuid().ToString();
string tmpDiskMd5Table = Guid.NewGuid().ToString();
string tmpDiskSha1Table = Guid.NewGuid().ToString();
string tmpMediaMd5Table = Guid.NewGuid().ToString();
string tmpMediaSha1Table = Guid.NewGuid().ToString();
string tmpMediaSha256Table = Guid.NewGuid().ToString();
var romsHaveCrc = false;
var romsHaveMd5 = false;
var romsHaveSha1 = false;
var romsHaveSha256 = false;
var romsHaveSha384 = false;
var romsHaveSha512 = false;
var disksHaveMd5 = false;
var disksHaveSha1 = false;
var mediasHaveMd5 = false;
var mediasHaveSha1 = false;
var mediasHaveSha256 = false;
bool romsHaveCrc = false;
bool romsHaveMd5 = false;
bool romsHaveSha1 = false;
bool romsHaveSha256 = false;
bool romsHaveSha384 = false;
bool romsHaveSha512 = false;
bool disksHaveMd5 = false;
bool disksHaveSha1 = false;
bool mediasHaveMd5 = false;
bool mediasHaveSha1 = false;
bool mediasHaveSha256 = false;
DbConnection dbConnection = ctx.Database.GetDbConnection();
dbConnection.Open();
@@ -248,358 +259,329 @@ public sealed class DatImporter
Maximum = datFile.Items.SortedKeys.Length
});
using(DbTransaction dbTransaction = dbConnection.BeginTransaction())
List<DbFile> pendingFilesByCrcList;
List<DbFile> pendingFilesByMd5List;
List<DbFile> pendingFilesBySha1List;
List<DbFile> pendingFilesBySha256List;
List<DbFile> pendingFilesBySha384List;
List<DbFile> pendingFilesBySha512List;
Dictionary<string, DbDisk> pendingDisksByMd5;
Dictionary<string, DbDisk> pendingDisksBySha1;
Dictionary<string, DbMedia> pendingMediasByMd5;
Dictionary<string, DbMedia> pendingMediasBySha1;
Dictionary<string, DbMedia> pendingMediasBySha256;
lock(DbLock)
{
DbCommand dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomCrc32Table}\" (\"Size\" INTEGER NOT NULL, \"Crc32\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomMd5Table}\" (\"Size\" INTEGER NOT NULL, \"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha1Table}\" (\"Size\" INTEGER NOT NULL, \"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha256Table}\" (\"Size\" INTEGER NOT NULL, \"Sha256\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha384Table}\" (\"Size\" INTEGER NOT NULL, \"Sha384\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha512Table}\" (\"Size\" INTEGER NOT NULL, \"Sha512\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpDiskMd5Table}\" (\"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpDiskSha1Table}\" (\"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaMd5Table}\" (\"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaSha1Table}\" (\"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaSha256Table}\" (\"Sha256\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
foreach(string key in datFile.Items.SortedKeys)
using(DbTransaction dbTransaction = dbConnection.BeginTransaction())
{
SetProgress?.Invoke(this,
new ProgressEventArgs
{
Value = position
});
DbCommand dbcc = dbConnection.CreateCommand();
foreach(DatItem item in datFile.GetItemsForBucket(key))
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomCrc32Table}\" (\"Size\" INTEGER NOT NULL, \"Crc32\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomMd5Table}\" (\"Size\" INTEGER NOT NULL, \"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha1Table}\" (\"Size\" INTEGER NOT NULL, \"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha256Table}\" (\"Size\" INTEGER NOT NULL, \"Sha256\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha384Table}\" (\"Size\" INTEGER NOT NULL, \"Sha384\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"CREATE TABLE \"{tmpRomSha512Table}\" (\"Size\" INTEGER NOT NULL, \"Sha512\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpDiskMd5Table}\" (\"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpDiskSha1Table}\" (\"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaMd5Table}\" (\"Md5\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaSha1Table}\" (\"Sha1\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
dbcc = dbConnection.CreateCommand();
dbcc.CommandText = $"CREATE TABLE \"{tmpMediaSha256Table}\" (\"Sha256\" TEXT NOT NULL);";
dbcc.ExecuteNonQuery();
foreach(string key in datFile.Items.SortedKeys)
{
switch(item)
SetProgress?.Invoke(this,
new ProgressEventArgs
{
Value = position
});
foreach(DatItem item in datFile.GetItemsForBucket(key))
{
case Rom rom:
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.CRCKey) != null)
{
dbcc = dbConnection.CreateCommand();
switch(item)
{
case Rom rom:
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.CRCKey) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomCrc32Table}\" (\"Size\", \"Crc32\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.CRCKey)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomCrc32Table}\" (\"Size\", \"Crc32\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.CRCKey)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveCrc = true;
}
romsHaveCrc = true;
}
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomMd5Table}\" (\"Size\", \"Md5\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.MD5Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomMd5Table}\" (\"Size\", \"Md5\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.MD5Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveMd5 = true;
}
romsHaveMd5 = true;
}
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha1Table}\" (\"Size\", \"Sha1\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA1Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha1Table}\" (\"Size\", \"Sha1\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA1Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveSha1 = true;
}
romsHaveSha1 = true;
}
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA256Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA256Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha256Table}\" (\"Size\", \"Sha256\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA256Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha256Table}\" (\"Size\", \"Sha256\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA256Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveSha256 = true;
}
romsHaveSha256 = true;
}
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA384Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA384Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha384Table}\" (\"Size\", \"Sha384\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA384Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha384Table}\" (\"Size\", \"Sha384\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA384Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveSha384 = true;
}
romsHaveSha384 = true;
}
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA512Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA512Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha512Table}\" (\"Size\", \"Sha512\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA512Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpRomSha512Table}\" (\"Size\", \"Sha512\") VALUES (\"{(ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey)}\", \"{rom.GetStringFieldValue(SabreTools.Models.Metadata.Rom.SHA512Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
romsHaveSha512 = true;
}
romsHaveSha512 = true;
}
roms.Add(rom);
roms.Add(rom);
continue;
case Disk disk:
if(disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
continue;
case Disk disk:
if(disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpDiskMd5Table}\" (\"Md5\") VALUES (\"{disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.MD5Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpDiskMd5Table}\" (\"Md5\") VALUES (\"{disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.MD5Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
disksHaveMd5 = true;
}
disksHaveMd5 = true;
}
if(disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpDiskSha1Table}\" (\"Sha1\") VALUES (\"{disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.SHA1Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpDiskSha1Table}\" (\"Sha1\") VALUES (\"{disk.GetStringFieldValue(SabreTools.Models.Metadata.Disk.SHA1Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
disksHaveSha1 = true;
}
disksHaveSha1 = true;
}
disks.Add(disk);
disks.Add(disk);
continue;
case Media media:
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
continue;
case Media media:
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.MD5Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaMd5Table}\" (\"Md5\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.MD5Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaMd5Table}\" (\"Md5\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.MD5Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
mediasHaveMd5 = true;
}
mediasHaveMd5 = true;
}
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA1Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaSha1Table}\" (\"Sha1\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA1Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaSha1Table}\" (\"Sha1\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA1Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
mediasHaveSha1 = true;
}
mediasHaveSha1 = true;
}
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA256Key) != null)
{
dbcc = dbConnection.CreateCommand();
if(media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA256Key) != null)
{
dbcc = dbConnection.CreateCommand();
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaSha256Table}\" (\"Sha256\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA256Key)}\");";
dbcc.CommandText =
$"INSERT INTO \"{tmpMediaSha256Table}\" (\"Sha256\") VALUES (\"{media.GetStringFieldValue(SabreTools.Models.Metadata.Media.SHA256Key)}\");";
dbcc.ExecuteNonQuery();
dbcc.ExecuteNonQuery();
mediasHaveSha256 = true;
}
mediasHaveSha256 = true;
}
medias.Add(media);
medias.Add(media);
continue;
continue;
}
}
position++;
}
position++;
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
dbTransaction.Commit();
}
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
pendingFilesByCrcList = romsHaveCrc
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomCrc32Table}] AS t WHERE f.Crc32 = t.Crc32 AND f.Size = t.Size")
.ToList()
: [];
dbTransaction.Commit();
pendingFilesByMd5List = romsHaveMd5
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomMd5Table}] AS t WHERE f.Md5 = t.Md5 AND f.Size = t.Size")
.ToList()
: [];
pendingFilesBySha1List = romsHaveSha1
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha1Table}] AS t WHERE f.Sha1 = t.Sha1 AND f.Size = t.Size")
.ToList()
: [];
pendingFilesBySha256List = romsHaveSha256
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha256Table}] AS t WHERE f.Sha256 = t.Sha256 AND f.Size = t.Size")
.ToList()
: [];
pendingFilesBySha384List = romsHaveSha384
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha384Table}] AS t WHERE f.Sha384 = t.Sha384 AND f.Size = t.Size")
.ToList()
: [];
pendingFilesBySha512List = romsHaveSha512
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha512Table}] AS t WHERE f.Sha512 = t.Sha512 AND f.Size = t.Size")
.ToList()
: [];
pendingDisksByMd5 = disksHaveMd5
? ctx.Disks
.FromSqlRaw($"SELECT DISTINCT f.* FROM Disks AS f, [{tmpDiskMd5Table}] AS t WHERE f.Md5 = t.Md5")
.ToDictionary(f => f.Md5)
: [];
pendingDisksBySha1 = disksHaveSha1
? ctx.Disks
.FromSqlRaw($"SELECT DISTINCT f.* FROM Disks AS f, [{tmpDiskSha1Table}] AS t WHERE f.Sha1 = t.Sha1")
.ToDictionary(f => f.Sha1)
: [];
pendingMediasByMd5 = mediasHaveMd5
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaMd5Table}] AS t WHERE f.Md5 = t.Md5")
.ToDictionary(f => f.Md5)
: [];
pendingMediasBySha1 = mediasHaveSha1
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaSha1Table}] AS t WHERE f.Sha1 = t.Sha1")
.ToDictionary(f => f.Sha1)
: [];
pendingMediasBySha256 = mediasHaveSha256
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaSha256Table}] AS t WHERE f.Sha256 = t.Sha256")
.ToDictionary(f => f.Sha256)
: [];
}
List<DbFile> pendingFilesByCrcList = romsHaveCrc
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomCrc32Table}] AS t WHERE f.Crc32 = t.Crc32 AND f.Size = t.Size")
.ToList()
: [];
List<DbFile> pendingFilesByMd5List = romsHaveMd5
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomMd5Table}] AS t WHERE f.Md5 = t.Md5 AND f.Size = t.Size")
.ToList()
: [];
List<DbFile> pendingFilesBySha1List =
romsHaveSha1
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha1Table}] AS t WHERE f.Sha1 = t.Sha1 AND f.Size = t.Size")
.ToList()
: [];
List<DbFile> pendingFilesBySha256List =
romsHaveSha256
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha256Table}] AS t WHERE f.Sha256 = t.Sha256 AND f.Size = t.Size")
.ToList()
: [];
List<DbFile> pendingFilesBySha384List =
romsHaveSha384
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha384Table}] AS t WHERE f.Sha384 = t.Sha384 AND f.Size = t.Size")
.ToList()
: [];
List<DbFile> pendingFilesBySha512List =
romsHaveSha512
? ctx.Files
.FromSqlRaw($"SELECT DISTINCT f.* FROM Files AS f, [{tmpRomSha512Table}] AS t WHERE f.Sha512 = t.Sha512 AND f.Size = t.Size")
.ToList()
: [];
Dictionary<string, DbDisk> pendingDisksByMd5 =
disksHaveMd5
? ctx.Disks
.FromSqlRaw($"SELECT DISTINCT f.* FROM Disks AS f, [{tmpDiskMd5Table}] AS t WHERE f.Md5 = t.Md5")
.ToDictionary(f => f.Md5)
: [];
Dictionary<string, DbDisk> pendingDisksBySha1 =
disksHaveSha1
? ctx.Disks
.FromSqlRaw($"SELECT DISTINCT f.* FROM Disks AS f, [{tmpDiskSha1Table}] AS t WHERE f.Sha1 = t.Sha1")
.ToDictionary(f => f.Sha1)
: [];
Dictionary<string, DbMedia> pendingMediasByMd5 =
mediasHaveMd5
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaMd5Table}] AS t WHERE f.Md5 = t.Md5")
.ToDictionary(f => f.Md5)
: [];
Dictionary<string, DbMedia> pendingMediasBySha1 =
mediasHaveSha1
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaSha1Table}] AS t WHERE f.Sha1 = t.Sha1")
.ToDictionary(f => f.Sha1)
: [];
Dictionary<string, DbMedia> pendingMediasBySha256 =
mediasHaveSha256
? ctx.Medias
.FromSqlRaw($"SELECT DISTINCT f.* FROM Medias AS f, [{tmpMediaSha256Table}] AS t WHERE f.Sha256 = t.Sha256")
.ToDictionary(f => f.Sha256)
: [];
var pendingFilesByCrc = new Dictionary<string, DbFile>();
var pendingFilesByMd5 = new Dictionary<string, DbFile>();
var pendingFilesBySha1 = new Dictionary<string, DbFile>();
var pendingFilesBySha256 = new Dictionary<string, DbFile>();
var pendingFilesBySha384 = new Dictionary<string, DbFile>();
var pendingFilesBySha512 = new Dictionary<string, DbFile>();
var pendingFiles = new List<DbFile>();
// This is because of hash collisions.
foreach(DbFile item in pendingFilesByCrcList)
{
if(pendingFilesByCrc.ContainsKey(item.Crc32))
pendingFiles.Add(item);
else
pendingFilesByCrc[item.Crc32] = item;
}
var pendingFiles = pendingFilesByCrcList.Where(item => !pendingFilesByCrc.TryAdd(item.Crc32, item))
.ToList();
foreach(DbFile item in pendingFilesByMd5List)
{
if(pendingFilesByMd5.ContainsKey(item.Md5))
pendingFiles.Add(item);
else
pendingFilesByMd5[item.Md5] = item;
}
pendingFiles.AddRange(pendingFilesByMd5List.Where(item => !pendingFilesByMd5.TryAdd(item.Md5, item)));
pendingFiles.AddRange(pendingFilesBySha1List.Where(item => !pendingFilesBySha1.TryAdd(item.Sha1, item)));
foreach(DbFile item in pendingFilesBySha1List)
{
if(pendingFilesBySha1.ContainsKey(item.Sha1))
pendingFiles.Add(item);
else
pendingFilesBySha1[item.Sha1] = item;
}
pendingFiles.AddRange(pendingFilesBySha256List.Where(item => !pendingFilesBySha256
.TryAdd(item.Sha256, item)));
foreach(DbFile item in pendingFilesBySha256List)
{
if(pendingFilesBySha256.ContainsKey(item.Sha256))
pendingFiles.Add(item);
else
pendingFilesBySha256[item.Sha256] = item;
}
pendingFiles.AddRange(pendingFilesBySha384List.Where(item => !pendingFilesBySha384
.TryAdd(item.Sha384, item)));
foreach(DbFile item in pendingFilesBySha384List)
{
if(pendingFilesBySha384.ContainsKey(item.Sha384))
pendingFiles.Add(item);
else
pendingFilesBySha384[item.Sha384] = item;
}
foreach(DbFile item in pendingFilesBySha512List)
{
if(pendingFilesBySha512.ContainsKey(item.Sha512))
pendingFiles.Add(item);
else
pendingFilesBySha512[item.Sha512] = item;
}
pendingFiles.AddRange(pendingFilesBySha512List.Where(item => !pendingFilesBySha512
.TryAdd(item.Sha512, item)));
// Clear some memory
pendingFilesByCrcList.Clear();
@@ -609,17 +591,20 @@ public sealed class DatImporter
pendingFilesBySha384List.Clear();
pendingFilesBySha512List.Clear();
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomCrc32Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha256Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha384Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha512Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpDiskMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpDiskSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaSha256Table}]");
lock(DbLock)
{
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomCrc32Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha256Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha384Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpRomSha512Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpDiskMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpDiskSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaMd5Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaSha1Table}]");
ctx.Database.ExecuteSqlRaw($"DROP TABLE [{tmpMediaSha256Table}]");
}
SetProgressBounds?.Invoke(this,
new ProgressBoundsEventArgs
@@ -641,7 +626,7 @@ public sealed class DatImporter
foreach(Rom rom in roms)
{
var hashCollision = false;
bool hashCollision = false;
SetProgress?.Invoke(this,
new ProgressEventArgs
@@ -663,7 +648,7 @@ public sealed class DatImporter
return;
}
var uSize = (ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey);
ulong uSize = (ulong)rom.GetInt64FieldValue(SabreTools.Models.Metadata.Rom.SizeKey);
DbFile file = null;
@@ -923,7 +908,10 @@ public sealed class DatImporter
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
ctx.BulkInsert(newFiles, b => b.SetOutputIdentity = true);
lock(DbLock)
{
ctx.BulkInsert(newFiles, b => b.SetOutputIdentity = true);
}
foreach(FileByMachine fbm in newFilesByMachine)
{
@@ -931,9 +919,12 @@ public sealed class DatImporter
fbm.MachineId = fbm.Machine.Id;
}
ctx.BulkInsert(newFilesByMachine);
lock(DbLock)
{
ctx.BulkInsert(newFilesByMachine);
ctx.SaveChanges();
ctx.SaveChanges();
}
pendingFilesBySha512.Clear();
pendingFilesBySha384.Clear();
@@ -1056,7 +1047,10 @@ public sealed class DatImporter
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
ctx.BulkInsert(newDisks, b => b.SetOutputIdentity = true);
lock(DbLock)
{
ctx.BulkInsert(newDisks, b => b.SetOutputIdentity = true);
}
foreach(DiskByMachine dbm in newDisksByMachine)
{
@@ -1064,9 +1058,12 @@ public sealed class DatImporter
dbm.MachineId = dbm.Machine.Id;
}
ctx.BulkInsert(newDisksByMachine);
lock(DbLock)
{
ctx.BulkInsert(newDisksByMachine);
ctx.SaveChanges();
ctx.SaveChanges();
}
pendingDisksBySha1.Clear();
pendingDisksByMd5.Clear();
@@ -1203,7 +1200,10 @@ public sealed class DatImporter
SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty);
ctx.BulkInsert(newMedias, b => b.SetOutputIdentity = true);
lock(DbLock)
{
ctx.BulkInsert(newMedias, b => b.SetOutputIdentity = true);
}
foreach(MediaByMachine mbm in newMediasByMachine)
{
@@ -1211,63 +1211,70 @@ public sealed class DatImporter
mbm.MachineId = mbm.Machine.Id;
}
ctx.BulkInsert(newMediasByMachine);
lock(DbLock)
{
ctx.BulkInsert(newMediasByMachine);
ctx.SaveChanges();
ctx.SaveChanges();
}
pendingMediasBySha256.Clear();
pendingMediasBySha1.Clear();
pendingMediasByMd5.Clear();
newMedias.Clear();
newMediasByMachine.Clear();
RomSetStat stats;
RomSetStat stats = ctx.RomSets.Where(r => r.Id == romSet.Id)
.Select(r => new RomSetStat
{
RomSetId = r.Id,
TotalMachines = r.Machines.Count,
CompleteMachines =
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count == 0 &&
m.Files.All(f => f.File.IsInRepo)) +
r.Machines.Count(m => m.Disks.Count > 0 &&
m.Files.Count == 0 &&
m.Disks.All(f => f.Disk.IsInRepo)) +
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count > 0 &&
m.Files.All(f => f.File.IsInRepo) &&
m.Disks.All(f => f.Disk.IsInRepo)),
IncompleteMachines =
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count == 0 &&
m.Files.Any(f => !f.File.IsInRepo)) +
r.Machines.Count(m => m.Disks.Count > 0 &&
m.Files.Count == 0 &&
m.Disks.Any(f => !f.Disk.IsInRepo)) +
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count > 0 &&
(m.Files.Any(f => !f.File.IsInRepo) ||
m.Disks.Any(f => !f.Disk.IsInRepo))),
TotalRoms =
r.Machines.Sum(m => m.Files.Count) +
r.Machines.Sum(m => m.Disks.Count) +
r.Machines.Sum(m => m.Medias.Count),
HaveRoms = r.Machines.Sum(m => m.Files.Count(f => f.File.IsInRepo)) +
r.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)) +
r.Machines.Sum(m => m.Medias.Count(f => f.Media.IsInRepo)),
MissRoms = r.Machines.Sum(m => m.Files.Count(f => !f.File.IsInRepo)) +
r.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) +
r.Machines.Sum(m => m.Medias.Count(f => !f.Media.IsInRepo))
})
.FirstOrDefault();
lock(DbLock)
{
stats = ctx.RomSets.Where(r => r.Id == romSet.Id)
.Select(r => new RomSetStat
{
RomSetId = r.Id,
TotalMachines = r.Machines.Count,
CompleteMachines =
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count == 0 &&
m.Files.All(f => f.File.IsInRepo)) +
r.Machines.Count(m => m.Disks.Count > 0 &&
m.Files.Count == 0 &&
m.Disks.All(f => f.Disk.IsInRepo)) +
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count > 0 &&
m.Files.All(f => f.File.IsInRepo) &&
m.Disks.All(f => f.Disk.IsInRepo)),
IncompleteMachines =
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count == 0 &&
m.Files.Any(f => !f.File.IsInRepo)) +
r.Machines.Count(m => m.Disks.Count > 0 &&
m.Files.Count == 0 &&
m.Disks.Any(f => !f.Disk.IsInRepo)) +
r.Machines.Count(m => m.Files.Count > 0 &&
m.Disks.Count > 0 &&
(m.Files.Any(f => !f.File.IsInRepo) ||
m.Disks.Any(f => !f.Disk.IsInRepo))),
TotalRoms =
r.Machines.Sum(m => m.Files.Count) +
r.Machines.Sum(m => m.Disks.Count) +
r.Machines.Sum(m => m.Medias.Count),
HaveRoms = r.Machines.Sum(m => m.Files.Count(f => f.File.IsInRepo)) +
r.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)) +
r.Machines.Sum(m => m.Medias.Count(f => f.Media.IsInRepo)),
MissRoms = r.Machines.Sum(m => m.Files.Count(f => !f.File.IsInRepo)) +
r.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) +
r.Machines.Sum(m => m.Medias.Count(f => !f.Media.IsInRepo))
})
.FirstOrDefault();
RomSetStat oldStats = ctx.RomSetStats.Find(stats.RomSetId);
RomSetStat oldStats = ctx.RomSetStats.Find(stats.RomSetId);
if(oldStats != null) ctx.Remove(oldStats);
if(oldStats != null) ctx.Remove(oldStats);
ctx.RomSetStats.Add(stats);
ctx.RomSetStats.Add(stats);
ctx.SaveChanges();
ctx.SaveChanges();
}
WorkFinished?.Invoke(this,
new MessageEventArgs

View File

@@ -6,16 +6,18 @@ using System.Text;
using System.Threading.Tasks;
using Ionic.Zip;
using Ionic.Zlib;
using Microsoft.Extensions.Logging;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Resources;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using SharpCompress.Compressors.LZMA;
using ZstdSharp;
using CompressionMode = SharpCompress.Compressors.CompressionMode;
namespace RomRepoMgr.Core.Workers;
public class FileExporter(long romSetId, string outPath)
public class FileExporter(long romSetId, string outPath, ILoggerFactory loggerFactory)
{
const long BUFFER_SIZE = 131072;
long _filePosition;
@@ -43,7 +45,7 @@ public class FileExporter(long romSetId, string outPath)
Message = Localization.RetrievingRomSetFromDatabase
});
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
RomSet romSet = ctx.RomSets.Find(romSetId);
@@ -108,7 +110,7 @@ public class FileExporter(long romSetId, string outPath)
Message = machine.Name
});
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath, loggerFactory);
string machineName = machine.Name;
@@ -154,10 +156,10 @@ public class FileExporter(long romSetId, string outPath)
if(media.Sha256 != null)
{
var sha256Bytes = new byte[32];
byte[] sha256Bytes = new byte[32];
string sha256 = media.Sha256;
for(var i = 0; i < 32; i++)
for(int i = 0; i < 32; i++)
{
if(sha256[i * 2] >= 0x30 && sha256[i * 2] <= 0x39)
sha256Bytes[i] = (byte)((sha256[i * 2] - 0x30) * 0x10);
@@ -189,10 +191,10 @@ public class FileExporter(long romSetId, string outPath)
if(media.Sha1 != null)
{
var sha1Bytes = new byte[20];
byte[] sha1Bytes = new byte[20];
string sha1 = media.Sha1;
for(var i = 0; i < 20; i++)
for(int i = 0; i < 20; i++)
{
if(sha1[i * 2] >= 0x30 && sha1[i * 2] <= 0x39)
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
@@ -224,10 +226,10 @@ public class FileExporter(long romSetId, string outPath)
if(media.Md5 != null)
{
var md5Bytes = new byte[16];
byte[] md5Bytes = new byte[16];
string md5 = media.Md5;
for(var i = 0; i < 16; i++)
for(int i = 0; i < 16; i++)
{
if(md5[i * 2] >= 0x30 && md5[i * 2] <= 0x39)
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);
@@ -285,7 +287,7 @@ public class FileExporter(long romSetId, string outPath)
Maximum = inFs.Length
});
var buffer = new byte[BUFFER_SIZE];
byte[] buffer = new byte[BUFFER_SIZE];
while(inFs.Position + BUFFER_SIZE <= inFs.Length)
{
@@ -295,7 +297,7 @@ public class FileExporter(long romSetId, string outPath)
Value = inFs.Position
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
outFs.Write(buffer, 0, buffer.Length);
}
@@ -307,7 +309,7 @@ public class FileExporter(long romSetId, string outPath)
Value = inFs.Position
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
outFs.Write(buffer, 0, buffer.Length);
inFs.Close();
@@ -358,10 +360,10 @@ public class FileExporter(long romSetId, string outPath)
if(disk.Sha1 != null)
{
var sha1Bytes = new byte[20];
byte[] sha1Bytes = new byte[20];
string sha1 = disk.Sha1;
for(var i = 0; i < 20; i++)
for(int i = 0; i < 20; i++)
{
if(sha1[i * 2] >= 0x30 && sha1[i * 2] <= 0x39)
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
@@ -393,10 +395,10 @@ public class FileExporter(long romSetId, string outPath)
if(disk.Md5 != null)
{
var md5Bytes = new byte[16];
byte[] md5Bytes = new byte[16];
string md5 = disk.Md5;
for(var i = 0; i < 16; i++)
for(int i = 0; i < 16; i++)
{
if(md5[i * 2] >= 0x30 && md5[i * 2] <= 0x39)
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);
@@ -452,7 +454,7 @@ public class FileExporter(long romSetId, string outPath)
Maximum = inFs.Length
});
var buffer = new byte[BUFFER_SIZE];
byte[] buffer = new byte[BUFFER_SIZE];
while(inFs.Position + BUFFER_SIZE <= inFs.Length)
{
@@ -462,7 +464,7 @@ public class FileExporter(long romSetId, string outPath)
Value = inFs.Position
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
outFs.Write(buffer, 0, buffer.Length);
}
@@ -474,7 +476,7 @@ public class FileExporter(long romSetId, string outPath)
Value = inFs.Position
});
inFs.EnsureRead(buffer, 0, buffer.Length);
inFs.ReadExactly(buffer, 0, buffer.Length);
outFs.Write(buffer, 0, buffer.Length);
inFs.Close();
@@ -557,10 +559,10 @@ public class FileExporter(long romSetId, string outPath)
// Special case for empty file, as it seems to crash when SharpCompress tries to unLZMA it.
if(file.Size == 0) return new MemoryStream();
var sha384Bytes = new byte[48];
byte[] sha384Bytes = new byte[48];
string sha384 = file.Sha384;
for(var i = 0; i < 48; i++)
for(int i = 0; i < 48; i++)
{
if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39)
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);
@@ -588,10 +590,43 @@ public class FileExporter(long romSetId, string outPath)
sha384B32[4].ToString(),
sha384B32 + ".lz");
if(!File.Exists(repoPath))
throw new ArgumentException(string.Format(Localization.CannotFindHashInRepository, file.Sha256));
FileStream inFs;
var inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read);
// Try ZSTD
if(!File.Exists(repoPath))
{
repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32 + ".zst");
if(!File.Exists(repoPath))
{
repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath,
"files",
sha384B32[0].ToString(),
sha384B32[1].ToString(),
sha384B32[2].ToString(),
sha384B32[3].ToString(),
sha384B32[4].ToString(),
sha384B32);
return !File.Exists(repoPath)
? throw new ArgumentException(string.Format(Localization.CannotFindHashInRepository,
file.Sha256))
: new FileStream(repoPath, FileMode.Open, FileAccess.Read);
}
inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read);
return new StreamWithLength(new DecompressionStream(inFs), (long)file.Size);
}
inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read);
return new StreamWithLength(new LZipStream(inFs, CompressionMode.Decompress), (long)file.Size);
}
@@ -611,11 +646,9 @@ public class FileExporter(long romSetId, string outPath)
Value = _filePosition
});
if(!_filesByMachine.TryGetValue(e.CurrentEntry.FileName, out FileByMachine fileByMachine))
{
if(!_filesByMachine.TryGetValue(e.CurrentEntry.FileName.Replace('/', '\\'), out fileByMachine))
throw new ArgumentException(Localization.CannotFindZipEntryInDictionary);
}
if(!_filesByMachine.TryGetValue(e.CurrentEntry.FileName, out FileByMachine fileByMachine) &&
!_filesByMachine.TryGetValue(e.CurrentEntry.FileName.Replace('/', '\\'), out fileByMachine))
throw new ArgumentException(Localization.CannotFindZipEntryInDictionary);
DbFile currentFile = fileByMachine.File;

File diff suppressed because it is too large Load Diff

View File

@@ -41,13 +41,13 @@ public sealed class Context(DbContextOptions options) : DbContext(options)
public DbSet<MediaByMachine> MediasByMachines { get; set; }
public DbSet<RomSetStat> RomSetStats { get; set; }
public static Context Create(string dbPath)
public static Context Create(string dbPath, ILoggerFactory loggerFactory)
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseLazyLoadingProxies()
#if DEBUG
.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()))
.UseLoggerFactory(loggerFactory)
#endif
.UseSqlite($"Data Source={dbPath}");

View File

@@ -24,10 +24,12 @@
*******************************************************************************/
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Logging;
namespace RomRepoMgr.Database;
public class ContextFactory : IDesignTimeDbContextFactory<Context>
{
public Context CreateDbContext(string[] args) => Context.Create("romrepo.db");
public Context CreateDbContext(string[] args) =>
Context.Create("romrepo.db", LoggerFactory.Create(builder => builder.AddConsole()));
}

View File

@@ -11,46 +11,32 @@ namespace RomRepoMgr.Database.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localization {
private static global::System.Resources.ResourceManager resourceMan;
private static System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
private static System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localization() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RomRepoMgr.Database.Resources.Localization", typeof(Localization).Assembly);
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RomRepoMgr.Database.Resources.Localization", typeof(Localization).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -59,9 +45,6 @@ namespace RomRepoMgr.Database.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Settings are not initialized!.
/// </summary>
internal static string Settings_not_initialized {
get {
return ResourceManager.GetString("Settings_not_initialized", resourceCulture);

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root"
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
</xsd:schema>

View File

@@ -1,10 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0-beta.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console"/>
<PackageReference Include="AsyncFixer"/>
<PackageReference Include="ErrorProne.NET.CoreAnalyzers"/>

View File

@@ -232,7 +232,7 @@ public static class DetectOS
/// <returns>Current operating system version</returns>
public static string GetVersion()
{
var environ = Environment.OSVersion.Version.ToString();
string environ = Environment.OSVersion.Version.ToString();
switch(GetRealPlatformID())
{

View File

@@ -11,46 +11,32 @@ namespace RomRepoMgr.Settings.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localization {
private static global::System.Resources.ResourceManager resourceMan;
private static System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
private static System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localization() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RomRepoMgr.Settings.Resources.Localization", typeof(Localization).Assembly);
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RomRepoMgr.Settings.Resources.Localization", typeof(Localization).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -59,9 +45,6 @@ namespace RomRepoMgr.Settings.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Unhandled exception calling uname: {0}.
/// </summary>
internal static string Unhandled_exception_uname {
get {
return ResourceManager.GetString("Unhandled_exception_uname", resourceCulture);

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root"
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
</xsd:schema>

View File

@@ -1,4 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0-beta.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry"/>

View File

@@ -33,12 +33,21 @@ using PlatformID = Aaru.CommonTypes.Interop.PlatformID;
namespace RomRepoMgr.Settings;
public enum CompressionType
{
Lzip = 0,
Zstd,
None
}
public sealed class SetSettings
{
public string DatabasePath { get; set; }
public string RepositoryPath { get; set; }
public string TemporaryFolder { get; set; }
public string UnArchiverPath { get; set; }
public string DatabasePath { get; set; }
public string RepositoryPath { get; set; }
public string TemporaryFolder { get; set; }
public string UnArchiverPath { get; set; }
public CompressionType Compression { get; set; }
public bool UseInternalDecompressor { get; set; }
}
/// <summary>Manages statistics</summary>
@@ -48,7 +57,7 @@ public static class Settings
const string XDG_CONFIG_HOME_RESOLVED = ".config";
/// <summary>Current statistics</summary>
public static SetSettings Current;
public static bool UnArUsable { get; set; }
public static bool CanDecompress { get; set; }
/// <summary>Loads settings</summary>
public static void LoadSettings()
@@ -105,6 +114,15 @@ public static class Settings
? ((NSString)obj).ToString()
: null;
Current.Compression = parsedPreferences.TryGetValue("Compression", out obj)
? (CompressionType)Enum.Parse(typeof(CompressionType),
((NSNumber)obj).ToString())
: CompressionType.Lzip;
Current.UseInternalDecompressor =
parsedPreferences.TryGetValue("UseInternalDecompressor", out obj) &&
((NSNumber)obj).ToBool();
prefsFs.Close();
}
else
@@ -146,10 +164,16 @@ public static class Settings
return;
}
Current.DatabasePath = key.GetValue("DatabasePath") as string;
Current.RepositoryPath = key.GetValue("RepositoryPath") as string;
Current.TemporaryFolder = key.GetValue("TemporaryFolder") as string;
Current.UnArchiverPath = key.GetValue("UnArchiverPath") as string;
Current.DatabasePath = key.GetValue("DatabasePath") as string;
Current.RepositoryPath = key.GetValue("RepositoryPath") as string;
Current.TemporaryFolder = key.GetValue("TemporaryFolder") as string;
Current.UnArchiverPath = key.GetValue("UnArchiverPath") as string;
Current.UseInternalDecompressor = key.GetValue("UseInternalDecompressor") as int? > 0;
if(key.GetValue("Compression") is int compression)
Current.Compression = (CompressionType)compression;
else
Current.Compression = CompressionType.Lzip;
}
break;
@@ -222,6 +246,12 @@ public static class Settings
},
{
"UnArchiverPath", Current.UnArchiverPath
},
{
"Compression", (NSNumber)(int)Current.Compression
},
{
"UseInternalDecompressor", new NSNumber(Current.UseInternalDecompressor ? 1 : 0)
}
};
@@ -252,10 +282,12 @@ public static class Settings
RegistryKey key = parentKey?.CreateSubKey("RomRepoMgr");
key?.SetValue("DatabasePath", Current.DatabasePath);
key?.SetValue("RepositoryPath", Current.RepositoryPath);
key?.SetValue("TemporaryFolder", Current.TemporaryFolder);
key?.SetValue("UnArchiverPath", Current.UnArchiverPath);
key?.SetValue("DatabasePath", Current.DatabasePath);
key?.SetValue("RepositoryPath", Current.RepositoryPath);
key?.SetValue("TemporaryFolder", Current.TemporaryFolder);
key?.SetValue("UnArchiverPath", Current.UnArchiverPath);
key?.SetValue("Compression", (int)Current.Compression);
key?.SetValue("UseInternalDecompressor", Current.UseInternalDecompressor ? 1 : 0);
}
break;
@@ -309,7 +341,8 @@ public static class Settings
{
DatabasePath = Path.Combine(dataPath, "romrepo.db"),
RepositoryPath = Path.Combine(dataPath, "repo"),
TemporaryFolder = Path.GetTempPath()
TemporaryFolder = Path.GetTempPath(),
Compression = CompressionType.Lzip
};
}
}

View File

@@ -26,12 +26,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
global.json = global.json
README.md = README.md
logo.png = logo.png
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.DatTools", "SabreTools\SabreTools.DatTools\SabreTools.DatTools.csproj", "{20CC9ED6-9F56-49B5-A265-DC246C424F27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Reports", "SabreTools\SabreTools.Reports\SabreTools.Reports.csproj", "{E73767A7-0A65-4F89-B149-A520874F7B32}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RomRepoMgr.Blazor", "RomRepoMgr.Blazor\RomRepoMgr.Blazor.csproj", "{30DA0637-76C5-43DE-8203-403AECF5F859}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -78,5 +83,9 @@ Global
{E73767A7-0A65-4F89-B149-A520874F7B32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E73767A7-0A65-4F89-B149-A520874F7B32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E73767A7-0A65-4F89-B149-A520874F7B32}.Release|Any CPU.Build.0 = Release|Any CPU
{30DA0637-76C5-43DE-8203-403AECF5F859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30DA0637-76C5-43DE-8203-403AECF5F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30DA0637-76C5-43DE-8203-403AECF5F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30DA0637-76C5-43DE-8203-403AECF5F859}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,18 +1,22 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xml:space="preserve">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Aaru/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=datfiles/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlclose/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=HKLM/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libdl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lsar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepombgrfs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepomgr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=setxattr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Umounted/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Winfsp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattrs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0020_007B_000A_0020_0020_0020_0020/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=datfiles/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlclose/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=HKLM/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libdl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lsar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepombgrfs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=romrepomgr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=setxattr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Umounted/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Winfsp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xattrs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
x:Key="/Default/UserDictionary/Words/=_0020_007B_000A_0020_0020_0020_0020/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

View File

@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using Avalonia.Media;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using RomRepoMgr.Core.EventArgs;
namespace RomRepoMgr.Models;
public partial class DatImporter : ObservableObject
{
[ObservableProperty]
bool _indeterminate;
[ObservableProperty]
double _maximum;
[ObservableProperty]
double _minimum;
[ObservableProperty]
double _progress;
[ObservableProperty]
Color _statusColor;
[ObservableProperty]
string _statusMessage;
public string Filename { get; internal init; }
public Task Task { get; set; }
public bool Running { get; private set; } = true;
internal void OnErrorOccurred(object sender, ErrorEventArgs e) => Dispatcher.UIThread.Post(() =>
{
StatusMessage = e.Message;
StatusColor = Colors.Red;
if(!Indeterminate) return;
Indeterminate = false;
Progress = 0;
});
internal void OnSetIndeterminateProgress(object sender, EventArgs e) =>
Dispatcher.UIThread.Post(() => Indeterminate = true);
internal void OnSetMessage(object sender, MessageEventArgs e) =>
Dispatcher.UIThread.Post(() => StatusMessage = e.Message);
internal void OnSetProgress(object sender, ProgressEventArgs e) =>
Dispatcher.UIThread.Post(() => Progress = e.Value);
internal void OnSetProgressBounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
{
Indeterminate = false;
Maximum = e.Maximum;
Minimum = e.Minimum;
});
internal void OnWorkFinished(object sender, MessageEventArgs e) => Dispatcher.UIThread.Post(() =>
{
Indeterminate = false;
Maximum = 1;
Minimum = 0;
Progress = 1;
StatusMessage = e.Message;
Running = false;
});
}

View File

@@ -0,0 +1,70 @@
using System;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using RomRepoMgr.Core.EventArgs;
namespace RomRepoMgr.Models;
public partial class RomImporter : ObservableObject
{
[ObservableProperty]
bool _indeterminate;
[ObservableProperty]
double _maximum;
[ObservableProperty]
double _minimum;
[ObservableProperty]
double _progress;
[ObservableProperty]
bool _progressVisible = true;
[ObservableProperty]
Color _statusColor;
[ObservableProperty]
string _statusMessage;
public string Filename { get; internal init; }
public bool Running { get; private set; } = true;
internal void OnSetIndeterminateProgress(object sender, EventArgs e)
{
Indeterminate = true;
}
internal void OnSetMessage(object sender, MessageEventArgs e)
{
StatusMessage = e.Message;
}
internal void OnSetProgress(object sender, ProgressEventArgs e)
{
Progress = e.Value;
}
internal void OnSetProgressBounds(object sender, ProgressBoundsEventArgs e)
{
Indeterminate = false;
Maximum = e.Maximum;
Minimum = e.Minimum;
}
internal void OnWorkFinished(object sender, MessageEventArgs e)
{
Indeterminate = false;
Maximum = 1;
Minimum = 0;
Progress = 1;
StatusMessage = e.Message;
Running = false;
ProgressVisible = false;
}
public void OnImportedRom(object sender, ImportedRomItemEventArgs e)
{
Indeterminate = false;
Maximum = 1;
Minimum = 0;
Progress = 1;
StatusMessage = e.Item.Status;
Running = false;
ProgressVisible = false;
}
}

View File

@@ -23,9 +23,10 @@
// Copyright © 2020-2024 Natalia Portillo
*******************************************************************************/
using System;
using Avalonia;
using Avalonia.Logging;
using Avalonia.ReactiveUI;
using Serilog;
namespace RomRepoMgr;
@@ -34,11 +35,39 @@ internal static class Program
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting up");
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
catch(Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace(LogEventLevel.Debug)
.UseReactiveUI();
#if DEBUG
.LogToSerilog(LogEventLevel.Debug);
#else
.LogToSerilog(LogEventLevel.Information);
#endif
}

View File

@@ -764,5 +764,29 @@ namespace RomRepoMgr.Resources {
return ResourceManager.GetString("NativeMenuQuitText", resourceCulture);
}
}
public static string ProgressLabel {
get {
return ResourceManager.GetString("ProgressLabel", resourceCulture);
}
}
public static string CompressionType {
get {
return ResourceManager.GetString("CompressionType", resourceCulture);
}
}
public static string UseInternalDecompressorLabel {
get {
return ResourceManager.GetString("UseInternalDecompressorLabel", resourceCulture);
}
}
public static string ProcessingArchive {
get {
return ResourceManager.GetString("ProcessingArchive", resourceCulture);
}
}
}
}

View File

@@ -378,4 +378,16 @@ Tardará mucho tiempo...</value>
<data name="NativeMenuQuitText" xml:space="preserve">
<value>_Salir</value>
</data>
<data name="ProgressLabel" xml:space="preserve">
<value>Progreso</value>
</data>
<data name="CompressionType" xml:space="preserve">
<value>Compresión</value>
</data>
<data name="UseInternalDecompressorLabel" xml:space="preserve">
<value>Usar decompresor interno (soporta menos formatos)</value>
</data>
<data name="ProcessingArchive" xml:space="preserve">
<value>Procesando archivo: {0}</value>
</data>
</root>

View File

@@ -386,4 +386,16 @@ This will take a long time...</value>
<data name="NativeMenuQuitText" xml:space="preserve">
<value>_Quit</value>
</data>
<data name="ProgressLabel" xml:space="preserve">
<value>Progress</value>
</data>
<data name="CompressionType" xml:space="preserve">
<value>Compression</value>
</data>
<data name="UseInternalDecompressorLabel" xml:space="preserve">
<value>Use internal decompressor (supports less formats)</value>
</data>
<data name="ProcessingArchive" xml:space="preserve">
<value>Processing archive: {0}</value>
</data>
</root>

View File

@@ -1,6 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>
<PropertyGroup>
<Version>1.0.0-beta.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta.1</InformationalVersion>
<Copyright>© $([System.DateTime]::Now.Year) Natalia Portillo</Copyright>
<ApplicationIcon>Assets/romrepomgr.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
@@ -19,9 +31,9 @@
<PackageReference Include="Avalonia.Controls.DataGrid"/>
<PackageReference Include="Avalonia.Desktop"/>
<PackageReference Include="Avalonia.Diagnostics"/>
<PackageReference Include="Avalonia.ReactiveUI"/>
<PackageReference Include="Avalonia.Svg.Skia"/>
<PackageReference Include="Avalonia.Themes.Fluent"/>
<PackageReference Include="CommunityToolkit.Mvvm"/>
<PackageReference Include="MessageBox.Avalonia"/>
<PackageReference Include="AsyncFixer"/>
<PackageReference Include="ErrorProne.NET.CoreAnalyzers"/>
@@ -31,9 +43,12 @@
<PackageReference Include="Roslynator.Analyzers"/>
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers"/>
<PackageReference Include="Roslynator.Formatting.Analyzers"/>
<PackageReference Include="SkiaSharp.NativeAssets.Linux"/>
<PackageReference Include="Serilog"/>
<PackageReference Include="Serilog.Extensions.Logging"/>
<PackageReference Include="Serilog.Sinks.Console"/>
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer"/>
<PackageReference Include="Text.Analyzers"/>
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Condition="$(RuntimeIdentifier.StartsWith('linux'))"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RomRepoMgr.Database\RomRepoMgr.Database.csproj"/>
@@ -46,4 +61,39 @@
<LastGenOutput>Localization.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Target Name="CreateMacAppBundle" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'">
<!-- Create Directory Structure -->
<MakeDir Directories="
$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS;
$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Resources" />
<!-- Copy Executable -->
<Copy SourceFiles="$(PublishDir)RomRepoMgr" DestinationFiles="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS/RomRepoMgr" />
<!-- Copy .dylib files -->
<ItemGroup>
<DylibFiles Include="$(PublishDir)**\*.dylib" />
</ItemGroup>
<Copy SourceFiles="@(DylibFiles)" DestinationFolder="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/MacOS\%(RecursiveDir)" />
<!-- Copy Icon (optional) -->
<Copy SourceFiles="Assets/RomRepoMgr.icns" DestinationFiles="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Resources/RomRepoMgr.icns" />
<!-- Generate Info.plist -->
<WriteLinesToFile File="$(PublishDir)MacPackage/RomRepoMgr.app/Contents/Info.plist" Lines="
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;dict&gt;
&lt;key&gt;CFBundleName&lt;/key&gt;&lt;string&gt;RomRepoMgr&lt;/string&gt;
&lt;key&gt;CFBundleIdentifier&lt;/key&gt;&lt;string&gt;es.natportillo.romrepomgr&lt;/string&gt;
&lt;key&gt;CFBundleVersion&lt;/key&gt;&lt;string&gt;1.0&lt;/string&gt;
&lt;key&gt;CFBundleExecutable&lt;/key&gt;&lt;string&gt;RomRepoMgr&lt;/string&gt;
&lt;key&gt;CFBundleIconFile&lt;/key&gt;&lt;string&gt;RomRepoMgr.icns&lt;/string&gt;
&lt;key&gt;NSHumanReadableCopyright&lt;/key&gt;&lt;string&gt;© 2025 Nat Portillo.&lt;/string&gt;
&lt;/dict&gt;
&lt;/plist&gt;" Overwrite="true" />
</Target>
</Project>

53
RomRepoMgr/SerilogSink.cs Normal file
View File

@@ -0,0 +1,53 @@
#nullable enable
using System.Collections.Generic;
using Avalonia.Logging;
namespace RomRepoMgr;
public class SerilogSink(LogEventLevel minimumLevel, IList<string>? areas = null) : ILogSink
{
private readonly IList<string>? _areas = areas?.Count > 0 ? areas : null;
public bool IsEnabled(LogEventLevel level, string area) =>
level >= minimumLevel && (_areas?.Contains(area) ?? true);
public void Log(LogEventLevel level, string area, object? source, string messageTemplate)
{
if(IsEnabled(level, area))
{
Serilog.Log.Write(LogLevelToSerilogLevel(level),
"[{Area} {Source}] {MessageTemplate}",
area,
source,
messageTemplate);
}
}
public void Log(LogEventLevel level, string area, object? source, string messageTemplate,
params object?[] propertyValues)
{
if(IsEnabled(level, area))
{
Serilog.Log.Write(LogLevelToSerilogLevel(level),
"[{Area} {Source}] {MessageTemplate}",
propertyValues,
area,
source,
messageTemplate);
}
}
private static Serilog.Events.LogEventLevel LogLevelToSerilogLevel(LogEventLevel level)
{
return level switch
{
LogEventLevel.Verbose => Serilog.Events.LogEventLevel.Verbose,
LogEventLevel.Debug => Serilog.Events.LogEventLevel.Debug,
LogEventLevel.Information => Serilog.Events.LogEventLevel.Information,
LogEventLevel.Warning => Serilog.Events.LogEventLevel.Warning,
LogEventLevel.Error => Serilog.Events.LogEventLevel.Error,
LogEventLevel.Fatal => Serilog.Events.LogEventLevel.Fatal,
_ => Serilog.Events.LogEventLevel.Verbose
};
}
}

View File

@@ -0,0 +1,15 @@
using Avalonia;
using Avalonia.Logging;
namespace RomRepoMgr;
public static class SerilogSinkExtensions
{
public static AppBuilder LogToSerilog(this AppBuilder builder, LogEventLevel level = LogEventLevel.Warning,
params string[] areas)
{
Logger.Sink = new SerilogSink(level, areas);
return builder;
}
}

View File

@@ -26,20 +26,22 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Views;
namespace RomRepoMgr.ViewModels;
public sealed class AboutViewModel : ViewModelBase
public sealed partial class AboutViewModel : ViewModelBase
{
readonly About _view;
string _versionText;
[ObservableProperty]
string _versionText;
public AboutViewModel()
{
@@ -57,26 +59,20 @@ public sealed class AboutViewModel : ViewModelBase
public string SuiteName => "ROM Repository Manager";
public string Copyright => "© 2020-2024 Natalia Portillo";
public string Website => "https://www.claunia.com";
public ReactiveCommand<Unit, Unit> WebsiteCommand { get; private set; }
public ReactiveCommand<Unit, Unit> LicenseCommand { get; private set; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; private set; }
public ICommand WebsiteCommand { get; private set; }
public ICommand LicenseCommand { get; private set; }
public ICommand CloseCommand { get; private set; }
public ObservableCollection<AssemblyModel> Assemblies { get; private set; }
public string VersionText
{
get => _versionText;
private set => this.RaiseAndSetIfChanged(ref _versionText, value);
}
void LoadData()
{
VersionText =
(Attribute.GetCustomAttribute(typeof(App).Assembly, typeof(AssemblyInformationalVersionAttribute)) as
AssemblyInformationalVersionAttribute)?.InformationalVersion;
WebsiteCommand = ReactiveCommand.Create(ExecuteWebsiteCommand);
LicenseCommand = ReactiveCommand.Create(ExecuteLicenseCommand);
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
WebsiteCommand = new RelayCommand(ExecuteWebsiteCommand);
LicenseCommand = new RelayCommand(ExecuteLicenseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
Assemblies = [];

View File

@@ -24,18 +24,21 @@
*******************************************************************************/
using System;
using System.Reactive;
using System.Threading.Tasks;
using ReactiveUI;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public class EditDatViewModel : ViewModelBase
public partial class EditDatViewModel : ViewModelBase
{
readonly RomSetModel _romSet;
readonly EditDat _view;
@@ -45,9 +48,10 @@ public class EditDatViewModel : ViewModelBase
string _date;
string _description;
string _homepage;
bool _modified;
string _name;
string _version;
[ObservableProperty]
bool _modified;
string _name;
string _version;
// Mock
public EditDatViewModel()
@@ -96,26 +100,20 @@ public class EditDatViewModel : ViewModelBase
_date = romSet.Date;
_description = romSet.Description;
_homepage = romSet.Homepage;
SaveCommand = ReactiveCommand.CreateFromTask(ExecuteSaveCommandAsync);
CancelCommand = ReactiveCommand.Create(ExecuteCloseCommand);
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
SaveCommand = new AsyncRelayCommand(ExecuteSaveCommandAsync);
CancelCommand = new RelayCommand(ExecuteCloseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
}
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<Unit, Unit> CancelCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public long TotalMachines => _romSet.TotalMachines;
public long CompleteMachines => _romSet.CompleteMachines;
public long IncompleteMachines => _romSet.IncompleteMachines;
public long TotalRoms => _romSet.TotalRoms;
public long HaveRoms => _romSet.HaveRoms;
public long MissRoms => _romSet.MissRoms;
public bool Modified
{
get => _modified;
set => this.RaiseAndSetIfChanged(ref _modified, value);
}
public ICommand SaveCommand { get; }
public ICommand CancelCommand { get; }
public ICommand CloseCommand { get; }
public long TotalMachines => _romSet.TotalMachines;
public long CompleteMachines => _romSet.CompleteMachines;
public long IncompleteMachines => _romSet.IncompleteMachines;
public long TotalRoms => _romSet.TotalRoms;
public long HaveRoms => _romSet.HaveRoms;
public long MissRoms => _romSet.MissRoms;
public string Name
{
@@ -124,7 +122,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _name) Modified = true;
this.RaiseAndSetIfChanged(ref _name, value);
SetProperty(ref _name, value);
}
}
@@ -135,7 +133,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _version) Modified = true;
this.RaiseAndSetIfChanged(ref _version, value);
SetProperty(ref _version, value);
}
}
@@ -146,7 +144,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _author) Modified = true;
this.RaiseAndSetIfChanged(ref _author, value);
SetProperty(ref _author, value);
}
}
@@ -157,7 +155,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _comment) Modified = true;
this.RaiseAndSetIfChanged(ref _comment, value);
SetProperty(ref _comment, value);
}
}
@@ -168,7 +166,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _category) Modified = true;
this.RaiseAndSetIfChanged(ref _category, value);
SetProperty(ref _category, value);
}
}
@@ -179,7 +177,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _date) Modified = true;
this.RaiseAndSetIfChanged(ref _date, value);
SetProperty(ref _date, value);
}
}
@@ -190,7 +188,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _description) Modified = true;
this.RaiseAndSetIfChanged(ref _description, value);
SetProperty(ref _description, value);
}
}
@@ -201,7 +199,7 @@ public class EditDatViewModel : ViewModelBase
{
if(value != _homepage) Modified = true;
this.RaiseAndSetIfChanged(ref _homepage, value);
SetProperty(ref _homepage, value);
}
}
@@ -211,7 +209,8 @@ public class EditDatViewModel : ViewModelBase
async Task ExecuteSaveCommandAsync()
{
await using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
await using var ctx =
Context.Create(Settings.Settings.Current.DatabasePath, new SerilogLoggerFactory(Log.Logger));
RomSet romSetDb = await ctx.RomSets.FindAsync(_romSet.Id);

View File

@@ -24,10 +24,11 @@
*******************************************************************************/
using System.IO;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
@@ -37,17 +38,22 @@ using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
namespace RomRepoMgr.ViewModels;
public sealed class ExportDatViewModel : ViewModelBase
public sealed partial class ExportDatViewModel : ViewModelBase
{
readonly string _datHash;
readonly string _outPath;
readonly ExportDat _view;
readonly Compression _worker;
bool _canClose;
string _errorMessage;
bool _errorVisible;
bool _progressVisible;
string _statusMessage;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
string _errorMessage;
[ObservableProperty]
bool _errorVisible;
[ObservableProperty]
bool _progressVisible;
[ObservableProperty]
string _statusMessage;
// Mock
public ExportDatViewModel() {}
@@ -57,7 +63,7 @@ public sealed class ExportDatViewModel : ViewModelBase
_view = view;
_datHash = datHash;
_outPath = outPath;
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
ProgressVisible = false;
ErrorVisible = false;
_worker = new Compression();
@@ -65,37 +71,7 @@ public sealed class ExportDatViewModel : ViewModelBase
_worker.FailedWithText += OnWorkerOnFailedWithText;
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public bool ProgressVisible
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
}
public bool ErrorVisible
{
get => _errorVisible;
set => this.RaiseAndSetIfChanged(ref _errorVisible, value);
}
public string ErrorMessage
{
get => _errorMessage;
set => this.RaiseAndSetIfChanged(ref _errorMessage, value);
}
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ICommand CloseCommand { get; }
void OnWorkerOnFinishedWithText(object sender, MessageEventArgs args) => Dispatcher.UIThread.Post(() =>
{
@@ -119,9 +95,9 @@ public sealed class ExportDatViewModel : ViewModelBase
ProgressVisible = true;
StatusMessage = Localization.DecompressingDat;
var sha384Bytes = new byte[48];
byte[] sha384Bytes = new byte[48];
for(var i = 0; i < 48; i++)
for(int i = 0; i < 48; i++)
{
if(_datHash[i * 2] >= 0x30 && _datHash[i * 2] <= 0x39)
sha384Bytes[i] = (byte)((_datHash[i * 2] - 0x30) * 0x10);

View File

@@ -24,39 +24,61 @@
*******************************************************************************/
using System;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class ExportRomsViewModel : ViewModelBase
public sealed partial class ExportRomsViewModel : ViewModelBase
{
readonly long _romSetId;
readonly ExportRoms _view;
bool _canClose;
bool _progress2IsIndeterminate;
double _progress2Maximum;
double _progress2Minimum;
double _progress2Value;
bool _progress2Visible;
bool _progress3IsIndeterminate;
double _progress3Maximum;
double _progress3Minimum;
double _progress3Value;
bool _progress3Visible;
bool _progressIsIndeterminate;
double _progressMaximum;
double _progressMinimum;
double _progressValue;
bool _progressVisible;
string _status2Message;
string _status3Message;
string _statusMessage;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
bool _progress2IsIndeterminate;
[ObservableProperty]
double _progress2Maximum;
[ObservableProperty]
double _progress2Minimum;
[ObservableProperty]
double _progress2Value;
[ObservableProperty]
bool _progress2Visible;
[ObservableProperty]
bool _progress3IsIndeterminate;
[ObservableProperty]
double _progress3Maximum;
[ObservableProperty]
double _progress3Minimum;
[ObservableProperty]
double _progress3Value;
[ObservableProperty]
bool _progress3Visible;
[ObservableProperty]
bool _progressIsIndeterminate;
[ObservableProperty]
double _progressMaximum;
[ObservableProperty]
double _progressMinimum;
[ObservableProperty]
double _progressValue;
[ObservableProperty]
bool _progressVisible;
[ObservableProperty]
string _status2Message;
[ObservableProperty]
string _status3Message;
[ObservableProperty]
string _statusMessage;
// Mock
public ExportRomsViewModel()
@@ -71,127 +93,12 @@ public sealed class ExportRomsViewModel : ViewModelBase
_view = view;
_romSetId = romSetId;
FolderPath = folderPath;
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
CanClose = false;
}
public string FolderPath { get; }
public bool ProgressVisible
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public double ProgressMinimum
{
get => _progressMinimum;
set => this.RaiseAndSetIfChanged(ref _progressMinimum, value);
}
public double ProgressMaximum
{
get => _progressMaximum;
set => this.RaiseAndSetIfChanged(ref _progressMaximum, value);
}
public double ProgressValue
{
get => _progressValue;
set => this.RaiseAndSetIfChanged(ref _progressValue, value);
}
public bool ProgressIsIndeterminate
{
get => _progressIsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progressIsIndeterminate, value);
}
public bool Progress2Visible
{
get => _progress2Visible;
set => this.RaiseAndSetIfChanged(ref _progress2Visible, value);
}
public string Status2Message
{
get => _status2Message;
set => this.RaiseAndSetIfChanged(ref _status2Message, value);
}
public double Progress2Minimum
{
get => _progress2Minimum;
set => this.RaiseAndSetIfChanged(ref _progress2Minimum, value);
}
public double Progress2Maximum
{
get => _progress2Maximum;
set => this.RaiseAndSetIfChanged(ref _progress2Maximum, value);
}
public double Progress2Value
{
get => _progress2Value;
set => this.RaiseAndSetIfChanged(ref _progress2Value, value);
}
public bool Progress2IsIndeterminate
{
get => _progress2IsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progress2IsIndeterminate, value);
}
public bool Progress3Visible
{
get => _progress3Visible;
set => this.RaiseAndSetIfChanged(ref _progress3Visible, value);
}
public string Status3Message
{
get => _status3Message;
set => this.RaiseAndSetIfChanged(ref _status3Message, value);
}
public double Progress3Minimum
{
get => _progress3Minimum;
set => this.RaiseAndSetIfChanged(ref _progress3Minimum, value);
}
public double Progress3Maximum
{
get => _progress3Maximum;
set => this.RaiseAndSetIfChanged(ref _progress3Maximum, value);
}
public double Progress3Value
{
get => _progress3Value;
set => this.RaiseAndSetIfChanged(ref _progress3Value, value);
}
public bool Progress3IsIndeterminate
{
get => _progress3IsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progress3IsIndeterminate, value);
}
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public string FolderPath { get; }
public ICommand CloseCommand { get; }
void ExecuteCloseCommand() => _view.Close();
@@ -249,7 +156,7 @@ public sealed class ExportRomsViewModel : ViewModelBase
public void OnOpened()
{
var worker = new FileExporter(_romSetId, FolderPath);
var worker = new FileExporter(_romSetId, FolderPath, new SerilogLoggerFactory(Log.Logger));
worker.SetMessage += OnWorkerOnSetMessage;
worker.SetProgress += OnWorkerOnSetProgress;
worker.SetProgressBounds += OnWorkerOnSetProgressBounds;

View File

@@ -1,96 +1,75 @@
/******************************************************************************
// RomRepoMgr - ROM repository manager
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2020-2024 Natalia Portillo
*******************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Models;
using RomRepoMgr.Resources;
using RomRepoMgr.Views;
using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class ImportDatFolderViewModel : ViewModelBase
public sealed partial class ImportDatFolderViewModel : ViewModelBase
{
readonly ImportDatFolder _view;
bool _allFilesChecked;
bool _canClose;
bool _canStart;
string _category;
string[] _datFiles;
bool _isImporting;
bool _isReady;
int _listPosition;
bool _progress2IsIndeterminate;
double _progress2Maximum;
double _progress2Minimum;
double _progress2Value;
bool _progress2Visible;
bool _progressIsIndeterminate;
double _progressMaximum;
double _progressMinimum;
double _progressValue;
bool _progressVisible;
bool _recursiveChecked;
string _status2Message;
string _statusMessage;
readonly Stopwatch _stopwatch = new();
bool _allFilesChecked;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
bool _canStart;
[ObservableProperty]
string _category;
string[] _datFiles;
[ObservableProperty]
string _folderPath;
[ObservableProperty]
bool _isImporting;
[ObservableProperty]
bool _isReady;
int _listPosition;
[ObservableProperty]
bool _progressIsIndeterminate;
[ObservableProperty]
double _progressMaximum;
[ObservableProperty]
double _progressMinimum;
[ObservableProperty]
double _progressValue;
[ObservableProperty]
bool _progressVisible;
bool _recursiveChecked;
[ObservableProperty]
string _statusMessage;
int _workers;
// Mock
public ImportDatFolderViewModel()
{
#pragma warning disable PH2080
FolderPath = "C:\\ROMs";
#pragma warning restore PH2080
CanClose = true;
IsReady = true;
SelectFolderCommand = new AsyncRelayCommand(SelectFolderAsync);
CloseCommand = new RelayCommand(Close);
StartCommand = new RelayCommand(Start);
}
public ImportDatFolderViewModel(ImportDatFolder view, string folderPath)
{
_view = view;
FolderPath = folderPath;
_allFilesChecked = false;
_recursiveChecked = true;
ImportResults = [];
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
StartCommand = ReactiveCommand.Create(ExecuteStartCommand);
}
public string FolderPath { get; }
public ICommand SelectFolderCommand { get; }
public Window View { get; init; }
public bool AllFilesChecked
{
get => _allFilesChecked;
set
{
this.RaiseAndSetIfChanged(ref _allFilesChecked, value);
SetProperty(ref _allFilesChecked, value);
RefreshFiles();
}
}
@@ -100,120 +79,122 @@ public sealed class ImportDatFolderViewModel : ViewModelBase
get => _recursiveChecked;
set
{
this.RaiseAndSetIfChanged(ref _recursiveChecked, value);
SetProperty(ref _recursiveChecked, value);
RefreshFiles();
}
}
public bool IsReady
public ICommand CloseCommand { get; }
public ICommand StartCommand { get; }
public ObservableCollection<DatImporter> Importers { get; } = [];
void Start()
{
get => _isReady;
set => this.RaiseAndSetIfChanged(ref _isReady, value);
_listPosition = 0;
ProgressMinimum = 0;
ProgressMaximum = _datFiles.Length;
ProgressValue = 0;
ProgressIsIndeterminate = false;
ProgressVisible = true;
CanClose = false;
CanStart = false;
IsReady = false;
IsImporting = true;
_workers = 0;
_stopwatch.Restart();
Import();
}
public bool ProgressVisible
void Import()
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
Dispatcher.UIThread.Post(() =>
{
if(_listPosition >= _datFiles.Length)
{
if(_workers != 0) return;
ProgressVisible = false;
StatusMessage = Localization.Finished;
CanClose = true;
CanStart = false;
IsReady = true;
_stopwatch.Stop();
return;
}
StatusMessage = string.Format(Localization.ImportingItem, Path.GetFileName(_datFiles[_listPosition]));
ProgressValue = _listPosition;
var model = new DatImporter
{
Filename = Path.GetFileName(_datFiles[_listPosition]),
Minimum = 0,
Maximum = _datFiles.Length,
Progress = 0,
Indeterminate = false
};
var worker =
new Core.Workers.DatImporter(_datFiles[_listPosition], Category, new SerilogLoggerFactory(Log.Logger));
worker.ErrorOccurred += model.OnErrorOccurred;
worker.SetIndeterminateProgress += model.OnSetIndeterminateProgress;
worker.SetMessage += model.OnSetMessage;
worker.SetProgress += model.OnSetProgress;
worker.SetProgressBounds += model.OnSetProgressBounds;
worker.WorkFinished += model.OnWorkFinished;
worker.RomSetAdded += RomSetAdded;
worker.WorkFinished += (_, _) =>
{
_workers--;
if(_workers < Environment.ProcessorCount) Import();
};
worker.ErrorOccurred += (_, _) =>
{
_workers--;
if(_workers < Environment.ProcessorCount) Import();
};
Importers.Add(model);
model.Task = Task.Run(worker.Import);
_workers++;
_listPosition++;
if(_workers < Environment.ProcessorCount) Import();
});
}
public string StatusMessage
public event EventHandler<RomSetEventArgs> RomSetAdded;
void Close()
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
View.Close();
}
public double ProgressMinimum
async Task SelectFolderAsync()
{
get => _progressMinimum;
set => this.RaiseAndSetIfChanged(ref _progressMinimum, value);
IReadOnlyList<IStorageFolder> result =
await View.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = Localization.ImportDatFolderDialogTitle
});
if(result.Count < 1) return;
FolderPath = result[0].TryGetLocalPath() ?? string.Empty;
RecursiveChecked = true;
AllFilesChecked = false;
RefreshFiles();
}
public double ProgressMaximum
{
get => _progressMaximum;
set => this.RaiseAndSetIfChanged(ref _progressMaximum, value);
}
public double ProgressValue
{
get => _progressValue;
set => this.RaiseAndSetIfChanged(ref _progressValue, value);
}
public bool ProgressIsIndeterminate
{
get => _progressIsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progressIsIndeterminate, value);
}
public bool Progress2Visible
{
get => _progress2Visible;
set => this.RaiseAndSetIfChanged(ref _progress2Visible, value);
}
public string Status2Message
{
get => _status2Message;
set => this.RaiseAndSetIfChanged(ref _status2Message, value);
}
public double Progress2Minimum
{
get => _progress2Minimum;
set => this.RaiseAndSetIfChanged(ref _progress2Minimum, value);
}
public double Progress2Maximum
{
get => _progress2Maximum;
set => this.RaiseAndSetIfChanged(ref _progress2Maximum, value);
}
public double Progress2Value
{
get => _progress2Value;
set => this.RaiseAndSetIfChanged(ref _progress2Value, value);
}
public bool Progress2IsIndeterminate
{
get => _progress2IsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progress2IsIndeterminate, value);
}
public bool IsImporting
{
get => _isImporting;
set => this.RaiseAndSetIfChanged(ref _isImporting, value);
}
public string Category
{
get => _category;
set => this.RaiseAndSetIfChanged(ref _category, value);
}
public ObservableCollection<ImportDatFolderItem> ImportResults { get; }
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public bool CanStart
{
get => _canStart;
set => this.RaiseAndSetIfChanged(ref _canStart, value);
}
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ReactiveCommand<Unit, Unit> StartCommand { get; }
internal void OnOpened() => RefreshFiles();
void RefreshFiles()
{
_ = Task.Run(() =>
@@ -222,7 +203,6 @@ public sealed class ImportDatFolderViewModel : ViewModelBase
{
IsReady = false;
ProgressVisible = true;
Progress2Visible = false;
ProgressIsIndeterminate = true;
StatusMessage = Localization.SearchingForFiles;
});
@@ -264,99 +244,4 @@ public sealed class ImportDatFolderViewModel : ViewModelBase
});
});
}
void ExecuteCloseCommand() => _view.Close();
void ExecuteStartCommand()
{
_listPosition = 0;
ProgressMinimum = 0;
ProgressMaximum = _datFiles.Length;
ProgressValue = 0;
ProgressIsIndeterminate = false;
ProgressVisible = true;
Progress2Visible = true;
CanClose = false;
CanStart = false;
IsReady = false;
IsImporting = true;
Import();
}
void Import()
{
if(_listPosition >= _datFiles.Length)
{
Progress2Visible = false;
ProgressVisible = false;
StatusMessage = Localization.Finished;
CanClose = true;
CanStart = false;
IsReady = true;
return;
}
StatusMessage = string.Format(Localization.ImportingItem, Path.GetFileName(_datFiles[_listPosition]));
ProgressValue = _listPosition;
var worker = new DatImporter(_datFiles[_listPosition], Category);
worker.ErrorOccurred += OnWorkerOnErrorOccurred;
worker.SetIndeterminateProgress += OnWorkerOnSetIndeterminateProgress;
worker.SetMessage += OnWorkerOnSetMessage;
worker.SetProgress += OnWorkerOnSetProgress;
worker.SetProgressBounds += OnWorkerOnSetProgressBounds;
worker.WorkFinished += OnWorkerOnWorkFinished;
worker.RomSetAdded += RomSetAdded;
_ = Task.Run(worker.Import);
}
void OnWorkerOnWorkFinished(object sender, MessageEventArgs args) => Dispatcher.UIThread.Post(() =>
{
ImportResults.Add(new ImportDatFolderItem
{
Filename = Path.GetFileName(_datFiles[_listPosition]),
Status = args.Message
});
_listPosition++;
Import();
});
void OnWorkerOnSetProgressBounds(object sender, ProgressBoundsEventArgs args) => Dispatcher.UIThread.Post(() =>
{
Progress2IsIndeterminate = false;
Progress2Maximum = args.Maximum;
Progress2Minimum = args.Minimum;
});
void OnWorkerOnSetProgress(object sender, ProgressEventArgs args) =>
Dispatcher.UIThread.Post(() => Progress2Value = args.Value);
void OnWorkerOnSetMessage(object sender, MessageEventArgs args) =>
Dispatcher.UIThread.Post(() => Status2Message = args.Message);
void OnWorkerOnSetIndeterminateProgress(object sender, EventArgs args) =>
Dispatcher.UIThread.Post(() => Progress2IsIndeterminate = true);
void OnWorkerOnErrorOccurred(object sender, ErrorEventArgs args) => Dispatcher.UIThread.Post(() =>
{
ImportResults.Add(new ImportDatFolderItem
{
Filename = Path.GetFileName(_datFiles[_listPosition]),
Status = args.Message
});
_listPosition++;
Import();
});
public event EventHandler<RomSetEventArgs> RomSetAdded;
}
public sealed class ImportDatFolderItem
{
public string Filename { get; set; }
public string Status { get; set; }
}

View File

@@ -24,29 +24,41 @@
*******************************************************************************/
using System;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class ImportDatViewModel : ViewModelBase
public sealed partial class ImportDatViewModel : ViewModelBase
{
readonly ImportDat _view;
readonly DatImporter _worker;
bool _canClose;
double _currentValue;
string _errorMessage;
bool _errorVisible;
bool _indeterminateProgress;
double _maximumValue;
double _minimumValue;
bool _progressVisible;
string _statusMessage;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
double _currentValue;
[ObservableProperty]
string _errorMessage;
[ObservableProperty]
bool _errorVisible;
[ObservableProperty]
bool _indeterminateProgress;
[ObservableProperty]
double _maximumValue;
[ObservableProperty]
double _minimumValue;
[ObservableProperty]
bool _progressVisible;
[ObservableProperty]
string _statusMessage;
// Mock
public ImportDatViewModel() {}
@@ -54,11 +66,11 @@ public sealed class ImportDatViewModel : ViewModelBase
public ImportDatViewModel(ImportDat view, string datPath)
{
_view = view;
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
IndeterminateProgress = true;
ProgressVisible = false;
ErrorVisible = false;
_worker = new DatImporter(datPath, null);
_worker = new DatImporter(datPath, null, new SerilogLoggerFactory(Log.Logger));
_worker.ErrorOccurred += OnWorkerOnErrorOccurred;
_worker.SetIndeterminateProgress += OnWorkerOnSetIndeterminateProgress;
_worker.SetMessage += OnWorkerOnSetMessage;
@@ -67,61 +79,7 @@ public sealed class ImportDatViewModel : ViewModelBase
_worker.WorkFinished += OnWorkerOnWorkFinished;
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public bool IndeterminateProgress
{
get => _indeterminateProgress;
set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value);
}
public double MaximumValue
{
get => _maximumValue;
set => this.RaiseAndSetIfChanged(ref _maximumValue, value);
}
public double MinimumValue
{
get => _minimumValue;
set => this.RaiseAndSetIfChanged(ref _minimumValue, value);
}
public double CurrentValue
{
get => _currentValue;
set => this.RaiseAndSetIfChanged(ref _currentValue, value);
}
public bool ProgressVisible
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
}
public bool ErrorVisible
{
get => _errorVisible;
set => this.RaiseAndSetIfChanged(ref _errorVisible, value);
}
public string ErrorMessage
{
get => _errorMessage;
set => this.RaiseAndSetIfChanged(ref _errorMessage, value);
}
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ICommand CloseCommand { get; }
void OnWorkerOnWorkFinished(object sender, MessageEventArgs args) => Dispatcher.UIThread.Post(() =>
{

View File

@@ -1,110 +1,105 @@
/******************************************************************************
// RomRepoMgr - ROM repository manager
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2020-2024 Natalia Portillo
*******************************************************************************/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using RomRepoMgr.Models;
using RomRepoMgr.Resources;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
using SharpCompress.Readers;
namespace RomRepoMgr.ViewModels;
public sealed class ImportRomFolderViewModel : ViewModelBase
public sealed partial class ImportRomFolderViewModel : ViewModelBase
{
readonly ImportRomFolder _view;
bool _canClose;
bool _canStart;
bool _isImporting;
bool _isReady;
bool _knownOnlyChecked;
bool _progress2IsIndeterminate;
double _progress2Maximum;
double _progress2Minimum;
double _progress2Value;
bool _progress2Visible;
bool _progressIsIndeterminate;
double _progressMaximum;
double _progressMinimum;
double _progressValue;
bool _progressVisible;
bool _recurseArchivesChecked;
bool _removeFilesChecked;
bool _removeFilesEnabled;
string _status2Message;
string _statusMessage;
readonly Context _ctx =
Context.Create(Settings.Settings.Current.DatabasePath, new SerilogLoggerFactory(Log.Logger));
readonly Stopwatch _mainStopwatch = new();
readonly ConcurrentBag<DbDisk> _newDisks = [];
readonly ConcurrentBag<DbFile> _newFiles = [];
readonly ConcurrentBag<DbMedia> _newMedias = [];
readonly Stopwatch _stopwatch = new();
[ObservableProperty]
bool _canChoose;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
bool _canStart;
[ObservableProperty]
string _folderPath;
[ObservableProperty]
bool _isImporting;
[ObservableProperty]
bool _isReady;
[ObservableProperty]
bool _knownOnlyChecked;
int _listPosition;
[ObservableProperty]
bool _progress2IsIndeterminate;
[ObservableProperty]
double _progress2Maximum;
[ObservableProperty]
double _progress2Minimum;
[ObservableProperty]
double _progress2Value;
[ObservableProperty]
bool _progress2Visible;
[ObservableProperty]
bool _progressIsIndeterminate;
[ObservableProperty]
double _progressMaximum;
[ObservableProperty]
double _progressMinimum;
[ObservableProperty]
double _progressValue;
[ObservableProperty]
bool _progressVisible;
bool _recurseArchivesChecked;
[ObservableProperty]
bool _removeFilesChecked;
[ObservableProperty]
bool _removeFilesEnabled;
FileImporter _rootImporter;
[ObservableProperty]
string _statusMessage;
[ObservableProperty]
string _statusMessage2;
[ObservableProperty]
bool _statusMessage2Visible;
// Mock
public ImportRomFolderViewModel()
{
#pragma warning disable PH2080
FolderPath = "C:\\ROMs";
#pragma warning restore PH2080
SelectFolderCommand = new AsyncRelayCommand(SelectFolderAsync);
CloseCommand = new RelayCommand(Close);
StartCommand = new RelayCommand(Start);
CanClose = true;
RemoveFilesChecked = false;
KnownOnlyChecked = true;
RecurseArchivesChecked = Settings.Settings.CanDecompress;
RemoveFilesEnabled = false;
CanChoose = true;
}
public ImportRomFolderViewModel(ImportRomFolder view, string folderPath)
{
_view = view;
FolderPath = folderPath;
_removeFilesChecked = false;
_knownOnlyChecked = true;
_recurseArchivesChecked = Settings.Settings.UnArUsable;
ImportResults = [];
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
StartCommand = ReactiveCommand.Create(ExecuteStartCommand);
IsReady = true;
CanStart = true;
CanClose = true;
_removeFilesEnabled = false;
}
public ICommand SelectFolderCommand { get; }
public ICommand CloseCommand { get; }
public ICommand StartCommand { get; }
public Window View { get; init; }
public string FolderPath { get; }
public bool RecurseArchivesEnabled => Settings.Settings.UnArUsable;
public bool RemoveFilesChecked
{
get => _removeFilesChecked;
set => this.RaiseAndSetIfChanged(ref _removeFilesChecked, value);
}
public bool KnownOnlyChecked
{
get => _knownOnlyChecked;
set => this.RaiseAndSetIfChanged(ref _knownOnlyChecked, value);
}
public bool RemoveFilesEnabled
{
get => _removeFilesEnabled;
set => this.RaiseAndSetIfChanged(ref _removeFilesEnabled, value);
}
public bool RecurseArchivesEnabled => Settings.Settings.CanDecompress;
public bool RecurseArchivesChecked
{
@@ -114,177 +109,382 @@ public sealed class ImportRomFolderViewModel : ViewModelBase
if(value) RemoveFilesChecked = false;
RemoveFilesEnabled = !value;
this.RaiseAndSetIfChanged(ref _recurseArchivesChecked, value);
SetProperty(ref _recurseArchivesChecked, value);
}
}
public bool IsReady
public ObservableCollection<RomImporter> Importers { get; } = [];
void Start()
{
get => _isReady;
set => this.RaiseAndSetIfChanged(ref _isReady, value);
_rootImporter = new FileImporter(_ctx, _newFiles, _newDisks, _newMedias, KnownOnlyChecked, RemoveFilesChecked);
_rootImporter.SetMessage += SetMessage;
_rootImporter.SetIndeterminateProgress += SetIndeterminateProgress;
_rootImporter.SetProgress += SetProgress;
_rootImporter.SetProgressBounds += SetProgressBounds;
_rootImporter.Finished += EnumeratingFilesFinished;
ProgressIsIndeterminate = true;
ProgressVisible = true;
CanClose = false;
CanStart = false;
IsImporting = true;
IsReady = false;
CanChoose = false;
_mainStopwatch.Start();
_ = Task.Run(() => _rootImporter.FindFiles(FolderPath));
}
public bool ProgressVisible
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public double ProgressMinimum
{
get => _progressMinimum;
set => this.RaiseAndSetIfChanged(ref _progressMinimum, value);
}
public double ProgressMaximum
{
get => _progressMaximum;
set => this.RaiseAndSetIfChanged(ref _progressMaximum, value);
}
public double ProgressValue
{
get => _progressValue;
set => this.RaiseAndSetIfChanged(ref _progressValue, value);
}
public bool ProgressIsIndeterminate
{
get => _progressIsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progressIsIndeterminate, value);
}
public bool Progress2Visible
{
get => _progress2Visible;
set => this.RaiseAndSetIfChanged(ref _progress2Visible, value);
}
public string Status2Message
{
get => _status2Message;
set => this.RaiseAndSetIfChanged(ref _status2Message, value);
}
public double Progress2Minimum
{
get => _progress2Minimum;
set => this.RaiseAndSetIfChanged(ref _progress2Minimum, value);
}
public double Progress2Maximum
{
get => _progress2Maximum;
set => this.RaiseAndSetIfChanged(ref _progress2Maximum, value);
}
public double Progress2Value
{
get => _progress2Value;
set => this.RaiseAndSetIfChanged(ref _progress2Value, value);
}
public bool Progress2IsIndeterminate
{
get => _progress2IsIndeterminate;
set => this.RaiseAndSetIfChanged(ref _progress2IsIndeterminate, value);
}
public bool IsImporting
{
get => _isImporting;
set => this.RaiseAndSetIfChanged(ref _isImporting, value);
}
public ObservableCollection<ImportRomItem> ImportResults { get; }
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public bool CanStart
{
get => _canStart;
set => this.RaiseAndSetIfChanged(ref _canStart, value);
}
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ReactiveCommand<Unit, Unit> StartCommand { get; }
void ExecuteCloseCommand() => _view.Close();
void ExecuteStartCommand()
{
IsReady = false;
ProgressVisible = true;
IsImporting = true;
CanStart = false;
CanClose = false;
Progress2Visible = true;
var worker = new FileImporter(KnownOnlyChecked, RemoveFilesChecked);
worker.SetIndeterminateProgress += OnWorkerOnSetIndeterminateProgress;
worker.SetMessage += OnWorkerOnSetMessage;
worker.SetProgress += OnWorkerOnSetProgress;
worker.SetProgressBounds += OnWorkerOnSetProgressBounds;
worker.SetIndeterminateProgress2 += OnWorkerOnSetIndeterminateProgress2;
worker.SetMessage2 += OnWorkerOnSetMessage2;
worker.SetProgress2 += OnWorkerOnSetProgress2;
worker.SetProgressBounds2 += OnWorkerOnSetProgressBounds2;
worker.Finished += OnWorkerOnFinished;
worker.ImportedRom += OnWorkerOnImportedRom;
_ = Task.Run(() => worker.ProcessPath(FolderPath, true, RecurseArchivesChecked));
}
void OnWorkerOnImportedRom(object sender, ImportedRomItemEventArgs args) =>
Dispatcher.UIThread.Post(() => ImportResults.Add(args.Item));
void OnWorkerOnFinished(object sender, EventArgs args) => Dispatcher.UIThread.Post(() =>
{
ProgressVisible = false;
StatusMessage = Localization.Finished;
CanClose = true;
Progress2Visible = false;
});
void OnWorkerOnSetProgressBounds(object sender, ProgressBoundsEventArgs args) => Dispatcher.UIThread.Post(() =>
void SetProgressBounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
{
ProgressIsIndeterminate = false;
ProgressMaximum = args.Maximum;
ProgressMinimum = args.Minimum;
ProgressMaximum = e.Maximum;
ProgressMinimum = e.Minimum;
});
void OnWorkerOnSetProgress(object sender, ProgressEventArgs args) =>
Dispatcher.UIThread.Post(() => ProgressValue = args.Value);
void SetProgress(object sender, ProgressEventArgs e)
{
Dispatcher.UIThread.Post(() => ProgressValue = e.Value);
}
void OnWorkerOnSetMessage(object sender, MessageEventArgs args) =>
Dispatcher.UIThread.Post(() => StatusMessage = args.Message);
void OnWorkerOnSetIndeterminateProgress(object sender, EventArgs args) =>
void SetIndeterminateProgress(object sender, EventArgs e)
{
Dispatcher.UIThread.Post(() => ProgressIsIndeterminate = true);
}
void OnWorkerOnSetProgressBounds2(object sender, ProgressBoundsEventArgs args) => Dispatcher.UIThread.Post(() =>
void SetProgress2Bounds(object sender, ProgressBoundsEventArgs e) => Dispatcher.UIThread.Post(() =>
{
Progress2IsIndeterminate = false;
Progress2Maximum = args.Maximum;
Progress2Minimum = args.Minimum;
Progress2Maximum = e.Maximum;
Progress2Minimum = e.Minimum;
});
void OnWorkerOnSetProgress2(object sender, ProgressEventArgs args) =>
Dispatcher.UIThread.Post(() => Progress2Value = args.Value);
void SetProgress2(object sender, ProgressEventArgs e)
{
Dispatcher.UIThread.Post(() => Progress2Value = e.Value);
}
void OnWorkerOnSetMessage2(object sender, MessageEventArgs args) =>
Dispatcher.UIThread.Post(() => Status2Message = args.Message);
void OnWorkerOnSetIndeterminateProgress2(object sender, EventArgs args) =>
void SetIndeterminateProgress2(object sender, EventArgs e)
{
Dispatcher.UIThread.Post(() => Progress2IsIndeterminate = true);
}
void EnumeratingFilesFinished(object sender, EventArgs e)
{
_rootImporter.Finished -= EnumeratingFilesFinished;
if(RecurseArchivesChecked)
{
Progress2Visible = true;
StatusMessage2Visible = true;
_rootImporter.SetMessage2 += SetMessage2;
_rootImporter.SetIndeterminateProgress2 += SetIndeterminateProgress2;
_rootImporter.SetProgress2 += SetProgress2;
_rootImporter.SetProgressBounds2 += SetProgress2Bounds;
_rootImporter.Finished += CheckArchivesFinished;
_ = Task.Run(() =>
{
_stopwatch.Restart();
if(Settings.Settings.Current.UseInternalDecompressor)
_rootImporter.SeparateFilesAndArchivesManaged();
else
_rootImporter.SeparateFilesAndArchives();
});
}
else
ProcessFiles();
}
void ProcessFiles()
{
_listPosition = 0;
ProgressMinimum = 0;
ProgressMaximum = _rootImporter.Files.Count;
ProgressValue = 0;
ProgressIsIndeterminate = false;
ProgressVisible = true;
CanClose = false;
CanStart = false;
IsReady = false;
IsImporting = true;
_stopwatch.Restart();
Parallel.ForEach(_rootImporter.Files,
file =>
{
Dispatcher.UIThread.Post(() =>
{
StatusMessage = string.Format(Localization.ImportingItem, Path.GetFileName(file));
ProgressValue = _listPosition;
});
var model = new RomImporter
{
Filename = Path.GetFileName(file),
Indeterminate = true
};
var worker = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
worker.SetMessage2 += model.OnSetMessage;
worker.SetProgress2 += model.OnSetProgress;
worker.SetProgressBounds2 += model.OnSetProgressBounds;
worker.ImportedRom += model.OnImportedRom;
worker.WorkFinished += model.OnWorkFinished;
Dispatcher.UIThread.Post(() => Importers.Add(model));
worker.ImportFile(file);
Interlocked.Increment(ref _listPosition);
});
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to process files", _stopwatch.Elapsed.TotalSeconds);
_rootImporter.SaveChanges();
_rootImporter.UpdateRomStats();
_listPosition = 0;
ProgressMinimum = 0;
ProgressMaximum = 1;
ProgressValue = 0;
ProgressIsIndeterminate = false;
ProgressVisible = false;
CanClose = true;
CanStart = false;
IsReady = false;
IsImporting = false;
StatusMessage = Localization.Finished;
_mainStopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to import ROMs", _mainStopwatch.Elapsed.TotalSeconds);
}
void ProcessArchives()
{
// For each archive
ProgressMaximum = _rootImporter.Archives.Count;
ProgressMinimum = 0;
ProgressValue = 0;
ProgressIsIndeterminate = false;
Progress2Visible = false;
StatusMessage2Visible = false;
_listPosition = 0;
_stopwatch.Restart();
Parallel.ForEach(_rootImporter.Archives,
archive =>
{
Dispatcher.UIThread.Post(() =>
{
StatusMessage = "Processing archive: " + Path.GetFileName(archive);
ProgressValue = _listPosition;
});
// Create FileImporter
var archiveImporter = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
// Extract archive
bool ret = Settings.Settings.Current.UseInternalDecompressor
? archiveImporter.ExtractArchiveManaged(archive)
: archiveImporter.ExtractArchive(archive);
if(!ret) return;
// Process files in archive
foreach(string file in archiveImporter.Files)
{
var model = new RomImporter
{
Filename = Path.GetFileName(file),
Indeterminate = true
};
var worker = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
worker.SetMessage2 += model.OnSetMessage;
worker.SetProgress2 += model.OnSetProgress;
worker.SetProgressBounds2 += model.OnSetProgressBounds;
worker.ImportedRom += model.OnImportedRom;
worker.WorkFinished += model.OnWorkFinished;
Dispatcher.UIThread.Post(() => Importers.Add(model));
worker.ImportFile(file);
worker.Files.Clear();
}
// Remove temporary files
archiveImporter.CleanupExtractedArchive();
Interlocked.Increment(ref _listPosition);
});
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
Progress2Visible = false;
StatusMessage2Visible = false;
ProcessFiles();
}
void ProcessArchivesManaged()
{
// For each archive
ProgressMaximum = _rootImporter.Archives.Count;
ProgressMinimum = 0;
ProgressValue = 0;
ProgressIsIndeterminate = false;
Progress2Visible = false;
StatusMessage2Visible = false;
_listPosition = 0;
_stopwatch.Restart();
Parallel.ForEach(_rootImporter.Archives,
archive =>
{
Dispatcher.UIThread.Post(() =>
{
StatusMessage =
string.Format(Localization.ProcessingArchive, Path.GetFileName(archive));
ProgressValue = _listPosition;
});
// Create FileImporter
var archiveImporter = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
// Open archive
try
{
using var fs = new FileStream(archive, FileMode.Open, FileAccess.Read);
using IReader reader = ReaderFactory.Open(fs);
// Process files in archive
while(reader.MoveToNextEntry())
{
if(reader.Entry.IsDirectory) continue;
if(reader.Entry.Crc == 0 && KnownOnlyChecked) continue;
if(!archiveImporter.IsCrcInDb(reader.Entry.Crc) && KnownOnlyChecked) continue;
var model = new RomImporter
{
Filename = Path.GetFileName(reader.Entry.Key),
Indeterminate = true
};
var worker = new FileImporter(_ctx,
_newFiles,
_newDisks,
_newMedias,
KnownOnlyChecked,
RemoveFilesChecked);
worker.SetIndeterminateProgress2 += model.OnSetIndeterminateProgress;
worker.SetMessage2 += model.OnSetMessage;
worker.SetProgress2 += model.OnSetProgress;
worker.SetProgressBounds2 += model.OnSetProgressBounds;
worker.ImportedRom += model.OnImportedRom;
worker.WorkFinished += model.OnWorkFinished;
Dispatcher.UIThread.Post(() => Importers.Add(model));
worker.ImportAndHashRom(reader.OpenEntryStream(),
reader.Entry.Key,
Path.Combine(Settings.Settings.Current.RepositoryPath,
Path.GetFileName(Path.GetTempFileName())),
reader.Entry.Size);
}
}
catch(InvalidOperationException) {}
finally
{
Interlocked.Increment(ref _listPosition);
}
});
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to process archives", _stopwatch.Elapsed.TotalSeconds);
Progress2Visible = false;
StatusMessage2Visible = false;
ProcessFiles();
}
void CheckArchivesFinished(object sender, EventArgs e)
{
_stopwatch.Stop();
Log.Debug("Took {TotalSeconds} seconds to check archives", _stopwatch.Elapsed.TotalSeconds);
Progress2Visible = false;
StatusMessage2Visible = false;
_rootImporter.Finished -= CheckArchivesFinished;
if(Settings.Settings.Current.UseInternalDecompressor)
ProcessArchivesManaged();
else
ProcessArchives();
}
void SetMessage(object sender, MessageEventArgs e)
{
Dispatcher.UIThread.Post(() => StatusMessage = e.Message);
}
void SetMessage2(object sender, MessageEventArgs e)
{
Dispatcher.UIThread.Post(() => StatusMessage2 = e.Message);
}
void Close() => View.Close();
async Task SelectFolderAsync()
{
IReadOnlyList<IStorageFolder> result =
await View.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = Localization.ImportRomsFolderDialogTitle
});
if(result.Count < 1) return;
FolderPath = result[0].TryGetLocalPath() ?? string.Empty;
IsReady = true;
CanStart = true;
CanClose = true;
}
}

View File

@@ -28,29 +28,34 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Filesystem;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Resources;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public class MainWindowViewModel : ViewModelBase
public sealed partial class MainWindowViewModel : ViewModelBase
{
readonly MainWindow _view;
RomSetModel _selectedRomSet;
Vfs _vfs;
[ObservableProperty]
RomSetModel _selectedRomSet;
[ObservableProperty]
Vfs _vfs;
// Mock
public MainWindowViewModel() {}
@@ -58,19 +63,19 @@ public class MainWindowViewModel : ViewModelBase
public MainWindowViewModel(MainWindow view, List<RomSetModel> romSets)
{
_view = view;
ExitCommand = ReactiveCommand.Create(ExecuteExitCommand);
SettingsCommand = ReactiveCommand.CreateFromTask(ExecuteSettingsCommandAsync);
AboutCommand = ReactiveCommand.Create(ExecuteAboutCommand);
ImportDatCommand = ReactiveCommand.CreateFromTask(ExecuteImportDatCommandAsync);
ImportDatFolderCommand = ReactiveCommand.CreateFromTask(ExecuteImportDatFolderCommandAsync);
ImportRomFolderCommand = ReactiveCommand.CreateFromTask(ExecuteImportRomFolderCommandAsync);
DeleteRomSetCommand = ReactiveCommand.CreateFromTask(ExecuteDeleteRomSetCommandAsync);
EditRomSetCommand = ReactiveCommand.Create(ExecuteEditRomSetCommand);
ExportDatCommand = ReactiveCommand.CreateFromTask(ExecuteExportDatCommandAsync);
ExportRomsCommand = ReactiveCommand.CreateFromTask(ExecuteExportRomsCommandAsync);
MountCommand = ReactiveCommand.CreateFromTask(ExecuteMountCommandAsync);
UmountCommand = ReactiveCommand.Create(ExecuteUmountCommand);
UpdateStatsCommand = ReactiveCommand.CreateFromTask(ExecuteUpdateStatsCommandAsync);
ExitCommand = new RelayCommand(ExecuteExitCommand);
SettingsCommand = new AsyncRelayCommand(ExecuteSettingsCommandAsync);
AboutCommand = new RelayCommand(ExecuteAboutCommand);
ImportDatCommand = new AsyncRelayCommand(ExecuteImportDatCommandAsync);
ImportDatFolderCommand = new AsyncRelayCommand(ExecuteImportDatFolderCommandAsync);
ImportRomFolderCommand = new AsyncRelayCommand(ExecuteImportRomFolderCommandAsync);
DeleteRomSetCommand = new AsyncRelayCommand(ExecuteDeleteRomSetCommandAsync);
EditRomSetCommand = new RelayCommand(ExecuteEditRomSetCommand);
ExportDatCommand = new AsyncRelayCommand(ExecuteExportDatCommandAsync);
ExportRomsCommand = new AsyncRelayCommand(ExecuteExportRomsCommandAsync);
MountCommand = new AsyncRelayCommand(ExecuteMountCommandAsync);
UmountCommand = new RelayCommand(ExecuteUmountCommand);
UpdateStatsCommand = new AsyncRelayCommand(ExecuteUpdateStatsCommandAsync);
RomSets = new ObservableCollection<RomSetModel>(romSets);
}
@@ -81,31 +86,19 @@ public class MainWindowViewModel : ViewModelBase
NativeMenu.GetIsNativeMenuExported((Application.Current.ApplicationLifetime as
IClassicDesktopStyleApplicationLifetime)?.MainWindow);
public ReactiveCommand<Unit, Unit> AboutCommand { get; }
public ReactiveCommand<Unit, Unit> ExitCommand { get; }
public ReactiveCommand<Unit, Unit> SettingsCommand { get; }
public ReactiveCommand<Unit, Unit> ImportDatCommand { get; }
public ReactiveCommand<Unit, Unit> ImportDatFolderCommand { get; }
public ReactiveCommand<Unit, Unit> ImportRomFolderCommand { get; }
public ReactiveCommand<Unit, Unit> DeleteRomSetCommand { get; }
public ReactiveCommand<Unit, Unit> EditRomSetCommand { get; }
public ReactiveCommand<Unit, Unit> ExportDatCommand { get; }
public ReactiveCommand<Unit, Unit> ExportRomsCommand { get; }
public ReactiveCommand<Unit, Unit> MountCommand { get; }
public ReactiveCommand<Unit, Unit> UmountCommand { get; }
public ReactiveCommand<Unit, Unit> UpdateStatsCommand { get; }
public Vfs Vfs
{
get => _vfs;
set => this.RaiseAndSetIfChanged(ref _vfs, value);
}
public RomSetModel SelectedRomSet
{
get => _selectedRomSet;
set => this.RaiseAndSetIfChanged(ref _selectedRomSet, value);
}
public ICommand AboutCommand { get; }
public ICommand ExitCommand { get; }
public ICommand SettingsCommand { get; }
public ICommand ImportDatCommand { get; }
public ICommand ImportDatFolderCommand { get; }
public ICommand ImportRomFolderCommand { get; }
public ICommand DeleteRomSetCommand { get; }
public ICommand EditRomSetCommand { get; }
public ICommand ExportDatCommand { get; }
public ICommand ExportRomsCommand { get; }
public ICommand MountCommand { get; }
public ICommand UmountCommand { get; }
public ICommand UpdateStatsCommand { get; }
internal Task ExecuteSettingsCommandAsync()
{
@@ -156,34 +149,29 @@ public class MainWindowViewModel : ViewModelBase
async Task ExecuteImportDatFolderCommandAsync()
{
IReadOnlyList<IStorageFolder> result =
await _view.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = Localization.ImportDatFolderDialogTitle
});
var dialog = new ImportDatFolder();
if(result.Count < 1) return;
var viewModel = new ImportDatFolderViewModel
{
View = dialog
};
var dialog = new ImportDatFolder();
var importDatFolderViewModel = new ImportDatFolderViewModel(dialog, result[0].Path.LocalPath);
importDatFolderViewModel.RomSetAdded += ImportDatViewModelOnRomSetAdded;
dialog.DataContext = importDatFolderViewModel;
_ = dialog.ShowDialog(_view);
viewModel.RomSetAdded += ImportDatViewModelOnRomSetAdded;
dialog.DataContext = viewModel;
_ = dialog.ShowDialog(_view);
}
async Task ExecuteImportRomFolderCommandAsync()
{
IReadOnlyList<IStorageFolder> result =
await _view.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = Localization.ImportRomsFolderDialogTitle
});
var dialog = new ImportRomFolder();
if(result.Count < 1) return;
var viewModel = new ImportRomFolderViewModel
{
View = dialog
};
var dialog = new ImportRomFolder();
var importRomFolderViewModel = new ImportRomFolderViewModel(dialog, result[0].Path.LocalPath);
dialog.DataContext = importRomFolderViewModel;
dialog.DataContext = viewModel;
_ = dialog.ShowDialog(_view);
}
@@ -280,12 +268,14 @@ public class MainWindowViewModel : ViewModelBase
try
{
Vfs = new Vfs();
Vfs = new Vfs(new SerilogLoggerFactory(Log.Logger));
Vfs.Umounted += VfsOnUmounted;
Vfs.MountTo(result[0].Path.LocalPath);
}
catch(Exception)
catch(Exception ex)
{
Log.Error(ex, "Error mounting VFS");
if(Debugger.IsAttached) throw;
Vfs = null;

View File

@@ -26,20 +26,23 @@
using System.IO;
using System.Threading.Tasks;
using Avalonia.Threading;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using RomRepoMgr.Core;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using RomRepoMgr.Resources;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class RemoveDatViewModel : ViewModelBase
public sealed partial class RemoveDatViewModel : ViewModelBase
{
readonly long _romSetId;
readonly RemoveDat _view;
string _statusMessage;
[ObservableProperty]
string _statusMessage;
// Mock
public RemoveDatViewModel() {}
@@ -50,17 +53,12 @@ public sealed class RemoveDatViewModel : ViewModelBase
_romSetId = romSetId;
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
internal void OnOpened()
{
_ = Task.Run(() =>
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath,
new SerilogLoggerFactory(Log.Logger));
Dispatcher.UIThread.Post(() => StatusMessage = Localization.RetrievingRomSetFromDatabase);
@@ -78,10 +76,10 @@ public sealed class RemoveDatViewModel : ViewModelBase
Dispatcher.UIThread.Post(() => StatusMessage = Localization.RemovingDatFileFromRepo);
var sha384Bytes = new byte[48];
byte[] sha384Bytes = new byte[48];
string sha384 = romSet.Sha384;
for(var i = 0; i < 48; i++)
for(int i = 0; i < 48; i++)
{
if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39)
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);

View File

@@ -26,35 +26,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reactive;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Database;
using RomRepoMgr.Resources;
using RomRepoMgr.Settings;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
using ErrorEventArgs = RomRepoMgr.Core.EventArgs.ErrorEventArgs;
namespace RomRepoMgr.ViewModels;
public sealed class SettingsViewModel : ViewModelBase
public sealed partial class SettingsViewModel : ViewModelBase
{
readonly SettingsDialog _view;
bool _databaseChanged;
string _databasePath;
bool _repositoryChanged;
string _repositoryPath;
bool _temporaryChanged;
string _temporaryPath;
bool _unArChanged;
string _unArPath;
string _unArVersion;
CompressionType _compression;
bool _compressionChanged;
bool _databaseChanged;
string _databasePath;
bool _repositoryChanged;
string _repositoryPath;
bool _temporaryChanged;
string _temporaryPath;
bool _unArChanged;
[ObservableProperty]
string _unArPath;
[ObservableProperty]
string _unArVersion;
bool _useInternalDecompressor;
bool _useInternalDecompressorChanged;
// Mock
public SettingsViewModel() {}
@@ -67,34 +79,39 @@ public sealed class SettingsViewModel : ViewModelBase
_temporaryChanged = false;
_unArChanged = false;
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
UnArCommand = ReactiveCommand.CreateFromTask(ExecuteUnArCommandAsync);
TemporaryCommand = ReactiveCommand.CreateFromTask(ExecuteTemporaryCommandAsync);
RepositoryCommand = ReactiveCommand.CreateFromTask(ExecuteRepositoryCommandAsync);
DatabaseCommand = ReactiveCommand.CreateFromTask(ExecuteDatabaseCommandAsync);
SaveCommand = ReactiveCommand.Create(ExecuteSaveCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
UnArCommand = new AsyncRelayCommand(ExecuteUnArCommandAsync);
TemporaryCommand = new AsyncRelayCommand(ExecuteTemporaryCommandAsync);
RepositoryCommand = new AsyncRelayCommand(ExecuteRepositoryCommandAsync);
DatabaseCommand = new AsyncRelayCommand(ExecuteDatabaseCommandAsync);
SaveCommand = new RelayCommand(ExecuteSaveCommand);
DatabasePath = Settings.Settings.Current.DatabasePath;
RepositoryPath = Settings.Settings.Current.RepositoryPath;
TemporaryPath = Settings.Settings.Current.TemporaryFolder;
UnArPath = Settings.Settings.Current.UnArchiverPath;
DatabasePath = Settings.Settings.Current.DatabasePath;
RepositoryPath = Settings.Settings.Current.RepositoryPath;
TemporaryPath = Settings.Settings.Current.TemporaryFolder;
UnArPath = Settings.Settings.Current.UnArchiverPath;
Compression = Settings.Settings.Current.Compression;
UseInternalDecompressor = Settings.Settings.Current.UseInternalDecompressor;
if(!string.IsNullOrWhiteSpace(UnArPath)) CheckUnAr();
}
public ReactiveCommand<Unit, Unit> UnArCommand { get; }
public ReactiveCommand<Unit, Unit> TemporaryCommand { get; }
public ReactiveCommand<Unit, Unit> RepositoryCommand { get; }
public ReactiveCommand<Unit, Unit> DatabaseCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public List<CompressionType> CompressionTypes { get; } =
Enum.GetValues(typeof(CompressionType)).Cast<CompressionType>().ToList();
public ICommand UnArCommand { get; }
public ICommand TemporaryCommand { get; }
public ICommand RepositoryCommand { get; }
public ICommand DatabaseCommand { get; }
public ICommand CloseCommand { get; }
public ICommand SaveCommand { get; }
public string DatabasePath
{
get => _databasePath;
set
{
this.RaiseAndSetIfChanged(ref _databasePath, value);
SetProperty(ref _databasePath, value);
_databaseChanged = true;
}
}
@@ -104,7 +121,7 @@ public sealed class SettingsViewModel : ViewModelBase
get => _repositoryPath;
set
{
this.RaiseAndSetIfChanged(ref _repositoryPath, value);
SetProperty(ref _repositoryPath, value);
// TODO: Refresh repository existing files
_repositoryChanged = true;
@@ -116,21 +133,29 @@ public sealed class SettingsViewModel : ViewModelBase
get => _temporaryPath;
set
{
this.RaiseAndSetIfChanged(ref _temporaryPath, value);
SetProperty(ref _temporaryPath, value);
_temporaryChanged = true;
}
}
public string UnArPath
public CompressionType Compression
{
get => _unArPath;
set => this.RaiseAndSetIfChanged(ref _unArPath, value);
get => _compression;
set
{
SetProperty(ref _compression, value);
_compressionChanged = true;
}
}
public string UnArVersion
public bool UseInternalDecompressor
{
get => _unArVersion;
set => this.RaiseAndSetIfChanged(ref _unArVersion, value);
get => _useInternalDecompressor;
set
{
SetProperty(ref _useInternalDecompressor, value);
_useInternalDecompressorChanged = true;
}
}
void CheckUnAr()
@@ -145,11 +170,14 @@ public sealed class SettingsViewModel : ViewModelBase
void CheckUnArFailed(object sender, ErrorEventArgs args)
{
UnArVersion = "";
UnArPath = "";
Dispatcher.UIThread.Post(() =>
{
UnArVersion = "";
UnArPath = "";
_ = MessageBoxManager.GetMessageBoxStandard(Localization.Error, args.Message, ButtonEnum.Ok, Icon.Error)
.ShowWindowDialogAsync(_view);
_ = MessageBoxManager.GetMessageBoxStandard(Localization.Error, args.Message, ButtonEnum.Ok, Icon.Error)
.ShowWindowDialogAsync(_view);
});
}
void CheckUnArFinished(object sender, MessageEventArgs args) => Dispatcher.UIThread.Post(() =>
@@ -233,10 +261,10 @@ public sealed class SettingsViewModel : ViewModelBase
{
try
{
var ctx = Context.Create(result);
var ctx = Context.Create(result, new SerilogLoggerFactory(Log.Logger));
await ctx.Database.MigrateAsync();
}
catch(Exception)
catch
{
btnResult = await MessageBoxManager
.GetMessageBoxStandard(Localization.DatabaseFileUnusableMsgBoxTitle,
@@ -251,7 +279,7 @@ public sealed class SettingsViewModel : ViewModelBase
{
File.Delete(result);
}
catch(Exception)
catch
{
await MessageBoxManager
.GetMessageBoxStandard(Localization.DatabaseFileCannotDeleteTitle,
@@ -260,9 +288,13 @@ public sealed class SettingsViewModel : ViewModelBase
Icon.Error)
.ShowWindowDialogAsync(_view);
#pragma warning disable ERP022
return;
#pragma warning restore ERP022
}
#pragma warning disable ERP022
}
#pragma warning restore ERP022
}
else
{
@@ -279,7 +311,7 @@ public sealed class SettingsViewModel : ViewModelBase
{
File.Delete(result);
}
catch(Exception)
catch
{
await MessageBoxManager
.GetMessageBoxStandard(Localization.DatabaseFileCannotDeleteTitle,
@@ -288,17 +320,19 @@ public sealed class SettingsViewModel : ViewModelBase
Icon.Error)
.ShowWindowDialogAsync(_view);
#pragma warning disable ERP022
return;
#pragma warning restore ERP022
}
}
}
try
{
var ctx = Context.Create(result);
var ctx = Context.Create(result, new SerilogLoggerFactory(Log.Logger));
await ctx.Database.MigrateAsync();
}
catch(Exception)
catch
{
await MessageBoxManager
.GetMessageBoxStandard(Localization.DatabaseFileUnusableMsgBoxTitle,
@@ -307,7 +341,9 @@ public sealed class SettingsViewModel : ViewModelBase
Icon.Error)
.ShowWindowDialogAsync(_view);
#pragma warning disable ERP022
return;
#pragma warning restore ERP022
}
DatabasePath = result;
@@ -324,10 +360,19 @@ public sealed class SettingsViewModel : ViewModelBase
if(_unArChanged)
{
Settings.Settings.Current.UnArchiverPath = UnArPath;
Settings.Settings.UnArUsable = true;
Settings.Settings.CanDecompress = true;
}
if(_databaseChanged || _repositoryChanged || _temporaryChanged || _unArChanged)
if(_compressionChanged) Settings.Settings.Current.Compression = Compression;
if(_useInternalDecompressorChanged) Settings.Settings.Current.UseInternalDecompressor = UseInternalDecompressor;
if(_databaseChanged ||
_repositoryChanged ||
_temporaryChanged ||
_unArChanged ||
_compressionChanged ||
_useInternalDecompressorChanged)
Settings.Settings.SaveSettings();
_view.Close();

View File

@@ -26,42 +26,61 @@
using System;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using ReactiveUI;
using RomRepoMgr.Core.EventArgs;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Core.Workers;
using RomRepoMgr.Database;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class SplashWindowViewModel : ViewModelBase
public sealed partial class SplashWindowViewModel : ViewModelBase
{
[ObservableProperty]
bool _checkingUnArError;
[ObservableProperty]
bool _checkingUnArOk;
[ObservableProperty]
bool _checkingUnArUnknown;
[ObservableProperty]
bool _exitVisible;
[ObservableProperty]
bool _loadingDatabaseError;
[ObservableProperty]
bool _loadingDatabaseOk;
[ObservableProperty]
bool _loadingDatabaseUnknown;
[ObservableProperty]
bool _loadingRomSetsError;
[ObservableProperty]
bool _loadingRomSetsOk;
[ObservableProperty]
bool _loadingRomSetsUnknown;
[ObservableProperty]
bool _loadingSettingsError;
[ObservableProperty]
bool _loadingSettingsOk;
[ObservableProperty]
bool _loadingSettingsUnknown;
[ObservableProperty]
bool _migratingDatabaseError;
[ObservableProperty]
bool _migratingDatabaseOk;
[ObservableProperty]
bool _migratingDatabaseUnknown;
public SplashWindowViewModel()
{
ExitCommand = ReactiveCommand.Create(ExecuteExitCommand);
ExitCommand = new RelayCommand(ExecuteExitCommand);
LoadingSettingsOk = false;
LoadingSettingsError = false;
@@ -81,103 +100,7 @@ public sealed class SplashWindowViewModel : ViewModelBase
ExitVisible = false;
}
public ReactiveCommand<Unit, Unit> ExitCommand { get; }
public bool LoadingSettingsOk
{
get => _loadingSettingsOk;
set => this.RaiseAndSetIfChanged(ref _loadingSettingsOk, value);
}
public bool LoadingSettingsError
{
get => _loadingSettingsError;
set => this.RaiseAndSetIfChanged(ref _loadingSettingsError, value);
}
public bool LoadingSettingsUnknown
{
get => _loadingSettingsUnknown;
set => this.RaiseAndSetIfChanged(ref _loadingSettingsUnknown, value);
}
public bool CheckingUnArOk
{
get => _checkingUnArOk;
set => this.RaiseAndSetIfChanged(ref _checkingUnArOk, value);
}
public bool CheckingUnArError
{
get => _checkingUnArError;
set => this.RaiseAndSetIfChanged(ref _checkingUnArError, value);
}
public bool CheckingUnArUnknown
{
get => _checkingUnArUnknown;
set => this.RaiseAndSetIfChanged(ref _checkingUnArUnknown, value);
}
public bool LoadingDatabaseOk
{
get => _loadingDatabaseOk;
set => this.RaiseAndSetIfChanged(ref _loadingDatabaseOk, value);
}
public bool LoadingDatabaseError
{
get => _loadingDatabaseError;
set => this.RaiseAndSetIfChanged(ref _loadingDatabaseError, value);
}
public bool LoadingDatabaseUnknown
{
get => _loadingDatabaseUnknown;
set => this.RaiseAndSetIfChanged(ref _loadingDatabaseUnknown, value);
}
public bool MigratingDatabaseOk
{
get => _migratingDatabaseOk;
set => this.RaiseAndSetIfChanged(ref _migratingDatabaseOk, value);
}
public bool MigratingDatabaseError
{
get => _migratingDatabaseError;
set => this.RaiseAndSetIfChanged(ref _migratingDatabaseError, value);
}
public bool MigratingDatabaseUnknown
{
get => _migratingDatabaseUnknown;
set => this.RaiseAndSetIfChanged(ref _migratingDatabaseUnknown, value);
}
public bool ExitVisible
{
get => _exitVisible;
set => this.RaiseAndSetIfChanged(ref _exitVisible, value);
}
public bool LoadingRomSetsOk
{
get => _loadingRomSetsOk;
set => this.RaiseAndSetIfChanged(ref _loadingRomSetsOk, value);
}
public bool LoadingRomSetsError
{
get => _loadingRomSetsError;
set => this.RaiseAndSetIfChanged(ref _loadingRomSetsError, value);
}
public bool LoadingRomSetsUnknown
{
get => _loadingRomSetsUnknown;
set => this.RaiseAndSetIfChanged(ref _loadingRomSetsUnknown, value);
}
public ICommand ExitCommand { get; }
public string LoadingText => "ROM Repository Manager";
@@ -198,7 +121,7 @@ public sealed class SplashWindowViewModel : ViewModelBase
}
catch(Exception e)
{
// TODO: Log error
Log.Error(e, "Error loading settings");
Dispatcher.UIThread.Post(FailedLoadingSettings);
}
});
@@ -221,13 +144,15 @@ public sealed class SplashWindowViewModel : ViewModelBase
try
{
var worker = new Compression();
Settings.Settings.UnArUsable = worker.CheckUnAr(Settings.Settings.Current.UnArchiverPath);
Settings.Settings.CanDecompress = worker.CheckUnAr(Settings.Settings.Current.UnArchiverPath) ||
Settings.Settings.Current.UseInternalDecompressor;
Dispatcher.UIThread.Post(LoadDatabase);
}
catch(Exception e)
{
// TODO: Log error
Log.Error(e, "Error checking unar");
Dispatcher.UIThread.Post(FailedCheckUnAr);
}
});
@@ -253,13 +178,14 @@ public sealed class SplashWindowViewModel : ViewModelBase
if(!Directory.Exists(dbPathFolder)) Directory.CreateDirectory(dbPathFolder);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath,
new SerilogLoggerFactory(Log.Logger));
Dispatcher.UIThread.Post(MigrateDatabase);
}
catch(Exception e)
{
// TODO: Log error
Log.Error(e, "Error loading database");
Dispatcher.UIThread.Post(FailedLoadingDatabase);
}
});
@@ -281,7 +207,8 @@ public sealed class SplashWindowViewModel : ViewModelBase
{
try
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath,
new SerilogLoggerFactory(Log.Logger));
ctx.Database.Migrate();
@@ -289,7 +216,7 @@ public sealed class SplashWindowViewModel : ViewModelBase
}
catch(Exception e)
{
// TODO: Log error
Log.Error(e, "Error migrating database");
Dispatcher.UIThread.Post(FailedMigratingDatabase);
}
});
@@ -311,7 +238,8 @@ public sealed class SplashWindowViewModel : ViewModelBase
{
try
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath,
new SerilogLoggerFactory(Log.Logger));
GotRomSets?.Invoke(this,
new RomSetsEventArgs
@@ -349,7 +277,7 @@ public sealed class SplashWindowViewModel : ViewModelBase
}
catch(Exception e)
{
// TODO: Log error
Log.Error(e, "Error loading ROM sets");
Dispatcher.UIThread.Post(FailedLoadingRomSets);
}
});

View File

@@ -23,33 +23,43 @@
// Copyright © 2020-2024 Natalia Portillo
*******************************************************************************/
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using ReactiveUI;
using RomRepoMgr.Core.Models;
using RomRepoMgr.Database;
using RomRepoMgr.Database.Models;
using RomRepoMgr.Resources;
using RomRepoMgr.Views;
using Serilog;
using Serilog.Extensions.Logging;
namespace RomRepoMgr.ViewModels;
public sealed class UpdateStatsViewModel : ViewModelBase
public sealed partial class UpdateStatsViewModel : ViewModelBase
{
readonly UpdateStats _view;
bool _canClose;
double _currentValue;
bool _indeterminateProgress;
double _maximumValue;
double _minimumValue;
bool _progressVisible;
RomSetModel _selectedRomSet;
string _statusMessage;
[ObservableProperty]
bool _canClose;
[ObservableProperty]
double _currentValue;
[ObservableProperty]
bool _indeterminateProgress;
[ObservableProperty]
double _maximumValue;
[ObservableProperty]
double _minimumValue;
[ObservableProperty]
bool _progressVisible;
[ObservableProperty]
RomSetModel _selectedRomSet;
[ObservableProperty]
string _statusMessage;
// Mock
public UpdateStatsViewModel() {}
@@ -57,69 +67,22 @@ public sealed class UpdateStatsViewModel : ViewModelBase
public UpdateStatsViewModel(UpdateStats view)
{
_view = view;
CloseCommand = ReactiveCommand.Create(ExecuteCloseCommand);
CloseCommand = new RelayCommand(ExecuteCloseCommand);
IndeterminateProgress = true;
ProgressVisible = false;
RomSets = [];
}
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public bool IndeterminateProgress
{
get => _indeterminateProgress;
set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value);
}
public double MaximumValue
{
get => _maximumValue;
set => this.RaiseAndSetIfChanged(ref _maximumValue, value);
}
public double MinimumValue
{
get => _minimumValue;
set => this.RaiseAndSetIfChanged(ref _minimumValue, value);
}
public double CurrentValue
{
get => _currentValue;
set => this.RaiseAndSetIfChanged(ref _currentValue, value);
}
public bool ProgressVisible
{
get => _progressVisible;
set => this.RaiseAndSetIfChanged(ref _progressVisible, value);
}
public RomSetModel SelectedRomSet
{
get => _selectedRomSet;
set => this.RaiseAndSetIfChanged(ref _selectedRomSet, value);
}
public bool CanClose
{
get => _canClose;
set => this.RaiseAndSetIfChanged(ref _canClose, value);
}
public ObservableCollection<RomSetModel> RomSets { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public ICommand CloseCommand { get; }
internal void OnOpened()
{
_ = Task.Run(() =>
{
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath,
new SerilogLoggerFactory(Log.Logger));
Dispatcher.UIThread.Post(() =>
{
@@ -226,10 +189,12 @@ public sealed class UpdateStatsViewModel : ViewModelBase
});
});
}
catch(Exception)
catch
#pragma warning disable PH2098
{
// Ignored
}
#pragma warning restore PH2098
pos++;
}

View File

@@ -23,8 +23,8 @@
// Copyright © 2020-2024 Natalia Portillo
*******************************************************************************/
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
namespace RomRepoMgr.ViewModels;
public class ViewModelBase : ReactiveObject {}
public class ViewModelBase : ObservableObject;

View File

@@ -40,106 +40,106 @@
<Design.DataContext>
<vm:AboutViewModel />
</Design.DataContext>
<Border Padding="15">
<Grid RowDefinitions="Auto,*,Auto">
<Grid Grid.Row="0"
ColumnDefinitions="Auto,*">
<Border Grid.Column="0"
BorderThickness="5">
<Image Source="/Assets/avalonia-logo.ico"
Width="48"
Height="48" />
</Border>
<Grid Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0"
Text="{Binding SoftwareName, Mode=OneWay}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="16"
FontWeight="Bold" />
<TextBlock Grid.Row="1"
Text="{Binding VersionText, Mode=OneWay}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</Grid>
<Grid RowDefinitions="Auto,*,Auto"
Margin="16"
RowSpacing="8">
<Grid Grid.Row="0"
ColumnSpacing="8"
ColumnDefinitions="Auto,*">
<Image Source="/Assets/avalonia-logo.ico"
Width="48"
Height="48" />
<Grid Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RowDefinitions="Auto,Auto"
RowSpacing="8">
<TextBlock Grid.Row="0"
Text="{Binding SoftwareName, Mode=OneWay}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="16"
FontWeight="Bold" />
<TextBlock Grid.Row="1"
Text="{Binding VersionText, Mode=OneWay}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</Grid>
<TabControl Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.AboutLabel}" />
</TabItem.Header>
<Grid RowDefinitions="Auto,12,Auto,12,Auto,Auto,*">
<TextBlock Grid.Row="0"
Text="{Binding SuiteName, Mode=OneWay}" />
<TextBlock Grid.Row="2"
Text="{Binding Copyright, Mode=OneWay}" />
<Button Grid.Row="4"
BorderThickness="0"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Padding="0"
Command="{Binding WebsiteCommand, Mode=OneWay}">
<!-- TODO: TextDecorations="Underline" in next Avalonia UI version -->
<TextBlock Text="{Binding Website, Mode=OneWay}"
Foreground="Blue" />
</Button>
<Button Grid.Row="5"
BorderThickness="0"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Padding="0"
Command="{Binding LicenseCommand, Mode=OneWay}">
<!-- TODO: TextDecorations="Underline" in next Avalonia UI version -->
<TextBlock Text="{x:Static resources:Localization.LicenseLabel}"
Foreground="Blue" />
</Button>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.LibrariesLabel}" />
</TabItem.Header>
<DataGrid ItemsSource="{Binding Assemblies, Mode=OneWay}"
HorizontalScrollBarVisibility="Visible">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="{x:Static resources:Localization.AssembliesLibraryText}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="{x:Static resources:Localization.AssembliesVersionText}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.AuthorsLabel}" />
</TabItem.Header>
<TextBox IsReadOnly="True"
Text="{x:Static resources:Localization.AuthorsText}" />
</TabItem>
</TabControl>
<Button Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CloseCommand, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</Grid>
</Border>
<TabControl Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.AboutLabel}" />
</TabItem.Header>
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,*"
RowSpacing="8">
<TextBlock Grid.Row="0"
Text="{Binding SuiteName, Mode=OneWay}" />
<TextBlock Grid.Row="1"
Text="{Binding Copyright, Mode=OneWay}" />
<Button Grid.Row="2"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Padding="0"
BorderThickness="0"
Command="{Binding WebsiteCommand, Mode=OneWay}">
<TextBlock Text="{Binding Website, Mode=OneWay}"
Foreground="Blue"
TextDecorations="Underline" />
</Button>
<Button Grid.Row="3"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Padding="0"
BorderThickness="0"
Command="{Binding LicenseCommand, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.LicenseLabel}"
Foreground="Blue"
TextDecorations="Underline" />
</Button>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.LibrariesLabel}" />
</TabItem.Header>
<DataGrid ItemsSource="{Binding Assemblies, Mode=OneWay}"
HorizontalScrollBarVisibility="Visible">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="{x:Static resources:Localization.AssembliesLibraryText}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Version, Mode=OneWay}"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="{x:Static resources:Localization.AssembliesVersionText}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Text="{x:Static resources:Localization.AuthorsLabel}" />
</TabItem.Header>
<TextBox IsReadOnly="True"
Text="{x:Static resources:Localization.AuthorsText}" />
</TabItem>
</TabControl>
<Button Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CloseCommand, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</Grid>
</Window>

View File

@@ -41,226 +41,212 @@
<Design.DataContext>
<vm:EditDatViewModel />
</Design.DataContext>
<Border Padding="15">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid Grid.Row="0"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetNameLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Name, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="1"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetVersionLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Version, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="2"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetAuthorLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Author, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="3"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetCategoryLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Category, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="4"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetCommentLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Comment, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="5"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetDateLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Date, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="6"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetDescriptionLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Description, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="7"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.HomepageLabel}"
FontWeight="Bold"
Padding="5" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Homepage, Mode=TwoWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.TotalMachinesLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding TotalMachines, Mode=OneWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="9"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.CompleteMachinesLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding CompleteMachines, Mode=OneWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="10"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.IncompleteMachinesLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding IncompleteMachines, Mode=OneWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="11"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.TotalRomsLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding TotalRoms, Mode=OneWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="12"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.HaveRomsLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding HaveRoms, Mode=OneWay}"
Padding="5" />
</Grid>
<Grid Grid.Row="13"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.MissRomsLabel}"
FontWeight="Bold"
Padding="5" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding MissRoms, Mode=OneWay}"
Padding="5" />
</Grid>
<StackPanel Grid.Row="14"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding SaveCommand, Mode=OneWay}"
IsVisible="{Binding Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
</Button>
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CancelCommand, Mode=OneWay}"
IsVisible="{Binding Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CancelLabel}" />
</Button>
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CloseCommand, Mode=OneWay}"
IsVisible="{Binding !Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</StackPanel>
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
Margin="16"
RowSpacing="8">
<Grid Grid.Row="0"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetNameLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Name, Mode=TwoWay}" />
</Grid>
</Border>
<Grid Grid.Row="1"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetVersionLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Version, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="2"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetAuthorLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Author, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="3"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetCategoryLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Category, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="4"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetCommentLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Comment, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="5"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetDateLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Date, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="6"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.RomSetDescriptionLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Description, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="7"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.HomepageLabel}"
FontWeight="Bold" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Homepage, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="8"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.TotalMachinesLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding TotalMachines, Mode=OneWay}" />
</Grid>
<Grid Grid.Row="9"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.CompleteMachinesLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding CompleteMachines, Mode=OneWay}" />
</Grid>
<Grid Grid.Row="10"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.IncompleteMachinesLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding IncompleteMachines, Mode=OneWay}" />
</Grid>
<Grid Grid.Row="11"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.TotalRomsLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding TotalRoms, Mode=OneWay}" />
</Grid>
<Grid Grid.Row="12"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.HaveRomsLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding HaveRoms, Mode=OneWay}" />
</Grid>
<Grid Grid.Row="13"
ColumnSpacing="8"
ColumnDefinitions="140,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{x:Static resources:Localization.MissRomsLabel}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding MissRoms, Mode=OneWay}" />
</Grid>
<StackPanel Grid.Row="14"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding SaveCommand, Mode=OneWay}"
IsVisible="{Binding Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.SaveLabel}" />
</Button>
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CancelCommand, Mode=OneWay}"
IsVisible="{Binding Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CancelLabel}" />
</Button>
<Button HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CloseCommand, Mode=OneWay}"
IsVisible="{Binding !Modified, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</StackPanel>
</Grid>
</Window>

View File

@@ -41,27 +41,27 @@
<Design.DataContext>
<vm:ExportDatViewModel />
</Design.DataContext>
<Border Padding="15">
<Grid RowDefinitions="Auto,auto,Auto,Auto">
<TextBlock Grid.Row="0"
Text="{Binding StatusMessage, Mode=OneWay}"
HorizontalAlignment="Center" />
<ProgressBar Grid.Row="1"
IsIndeterminate="True"
HorizontalAlignment="Stretch"
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
<TextBlock Grid.Row="2"
Text="{Binding ErrorMessage, Mode=OneWay}"
HorizontalAlignment="Center"
Foreground="Red"
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
<Button Grid.Row="3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
IsEnabled="{Binding CanClose, Mode=OneWay}"
Command="{Binding CloseCommand, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</Grid>
</Border>
<Grid RowDefinitions="Auto,auto,Auto,Auto"
Margin="16"
RowSpacing="8">
<TextBlock Grid.Row="0"
Text="{Binding StatusMessage, Mode=OneWay}"
HorizontalAlignment="Center" />
<ProgressBar Grid.Row="1"
IsIndeterminate="True"
HorizontalAlignment="Stretch"
IsVisible="{Binding ProgressVisible, Mode=OneWay}" />
<TextBlock Grid.Row="2"
Text="{Binding ErrorMessage, Mode=OneWay}"
HorizontalAlignment="Center"
Foreground="Red"
IsVisible="{Binding ErrorVisible, Mode=OneWay}" />
<Button Grid.Row="3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
IsEnabled="{Binding CanClose, Mode=OneWay}"
Command="{Binding CloseCommand, Mode=OneWay}">
<TextBlock Text="{x:Static resources:Localization.CloseLabel}" />
</Button>
</Grid>
</Window>

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