From 47be65a0ebc6951827432059dfc9020b7869989d Mon Sep 17 00:00:00 2001 From: ApolloF <98666317+ApolloF@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:12:41 +0200 Subject: [PATCH] Download nvngx_dlssnr.dll, Lecram's add-on and ShortFuse's add-on from the catalog mirror --- AGENTS.md | 2 +- docs/addons/lecram-dlss5.md | 6 +- docs/addons/renodx-dlss-sf.md | 10 +- docs/design.md | 19 ++-- src/DLSSUpdater/Core/AddonLibrary.cs | 2 +- src/DLSSUpdater/Core/AppSettings.cs | 6 ++ src/DLSSUpdater/Core/CatalogModel.cs | 50 +++++++++- src/DLSSUpdater/Core/ComponentStore.cs | 2 +- src/DLSSUpdater/Core/Installer.cs | 8 +- src/DLSSUpdater/Core/LecramSource.cs | 2 +- src/DLSSUpdater/Core/MethodAdvisor.cs | 2 +- src/DLSSUpdater/Core/NeuralSource.cs | 94 ++++++++++++++++++ src/DLSSUpdater/Core/NrLibrary.cs | 2 +- src/DLSSUpdater/Core/RenoDxSource.cs | 12 +-- src/DLSSUpdater/MainWindow.xaml | 13 ++- .../ViewModels/AddonLibraryViewModel.cs | 4 +- .../ViewModels/GameViewModel.Methods.cs | 2 +- src/DLSSUpdater/ViewModels/GameViewModel.cs | 2 +- src/DLSSUpdater/ViewModels/HelpTopics.cs | 36 +++---- src/DLSSUpdater/ViewModels/MainViewModel.cs | 17 ++-- .../ViewModels/OnboardingViewModel.cs | 4 +- .../ViewModels/SettingsViewModel.cs | 11 +- src/DLSSUpdater/Views/OnboardingView.xaml | 2 +- tests/DLSSUpdater.Tests/NeuralCatalogTests.cs | Bin 0 -> 17331 bytes tests/DLSSUpdater.Tests/OnboardingTests.cs | 4 +- 25 files changed, 239 insertions(+), 73 deletions(-) create mode 100644 src/DLSSUpdater/Core/NeuralSource.cs create mode 100644 tests/DLSSUpdater.Tests/NeuralCatalogTests.cs diff --git a/AGENTS.md b/AGENTS.md index 1e43c36..6868411 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ Windows desktop app that pulls the latest OptiScaler-NR, DLSS, MFG Unlock, Strea - Fetching Lecram's builds from MediaFire is private-build only (`BuildFlags.LecramChannel`, MSBuild `LecramChannel`). The default must stay off in public releases (`release.yml` passes `-p:LecramChannel=false`); a private build never self-installs the public release. ## Hard rules -- **`nvngx_dlssnr.dll` must never be committed, bundled, uploaded or redistributed** (NVIDIA's terms don't allow it). The user supplies it, and it stays in `%LocalAppData%\DLSS5Updater\components`. `.gitignore` blocks `*.dll`, `*.exe`, `*.addon64` and `*.zip`. Don't force-add binaries. +- **`nvngx_dlssnr.dll` is never committed to this repo or bundled in the exe/installer.** The app downloads it from the catalog mirror into `%LocalAppData%\DLSS5Updater\components`. `.gitignore` blocks `*.dll`, `*.exe`, `*.addon64` and `*.zip`. Don't force-add binaries. - Tests and agents must not write NVIDIA driver profiles (`NvDrs` / NvAPI `Save`), launch real games or install into real game folders. Use temp directories. ## Check command diff --git a/docs/addons/lecram-dlss5.md b/docs/addons/lecram-dlss5.md index 43ee37f..50a6495 100644 --- a/docs/addons/lecram-dlss5.md +++ b/docs/addons/lecram-dlss5.md @@ -31,7 +31,7 @@ bare) and `v11.0.0-H` once, so the label is still `v11.0.0-beta.NN` (C, test `La framework `v6.1.x` strings. Licence: no restriction found; THIRD-PARTY-NOTICES lists MIT RenoDX, ReShade, ImGui, Detours, nlohmann/json, gtl, -NVIDIA DLSS/Streamline headers. Distributed by the author to supporters (sign-in with payment email): import-only. +NVIDIA DLSS/Streamline headers. Downloaded and installed automatically from the catalog mirror (`neural.lecram`, public build too); imports stay possible. ## Files and placement @@ -249,8 +249,8 @@ Per-game build picks are untouched. ## App support -DLSS5 Updater imports the add-on (the private build can also fetch v11 builds from the author's channel, see "Update source") and installs it per game in ReShade-only mode, with the same -prerequisites as ShortFuse's add-on: ReShade 6+, a game with native DLSS and an imported `nvngx_dlssnr.dll`. It excludes +DLSS5 Updater downloads and installs the add-on automatically from the catalog mirror (`neural.lecram`; the file itself decides v10 or v11) or imports it (the private build can also fetch v11 builds from the author's channel, see "Update source") and installs it per game in ReShade-only mode, with the same +prerequisites as ShortFuse's add-on: ReShade 6+, a game with native DLSS and the NR runtime `nvngx_dlssnr.dll`. It excludes OptiScaler-NR and Deep Fried Chicken. It is not added to `[ADDON] LoadFromDllMain`, since nothing here shows it needs early load. - **Library.** Every imported build is kept: `components\lecram-dlss5\\` plus a `version.json` (id = SHA-256, diff --git a/docs/addons/renodx-dlss-sf.md b/docs/addons/renodx-dlss-sf.md index 59817ba..0898529 100644 --- a/docs/addons/renodx-dlss-sf.md +++ b/docs/addons/renodx-dlss-sf.md @@ -16,8 +16,8 @@ tags `renodx-dlss-SF-` (older ones `renodx-dlss-SF-0.55`), one zip per release holding `renodx-dlss.addon64`; the releases publish no hashes and the repository has no licence. - The catalog (`renodxDlss` section, [catalog.md](../catalog.md)) lists the newest ten of those releases with the SHA-256 the - catalog generator computed, **pinned, never mirrored**. This is the exception in `docs/design.md` section 3 (user decision - 2026-10-21): the app downloads the newest zip, refuses a hash mismatch, and imports the add-on from it. + catalog generator computed, each with a mirror copy. The app downloads and installs the newest build automatically (mirror + first, the author's URL as the fallback), refuses a hash mismatch, and imports the add-on from it (`docs/design.md` section 3). - Framework is MIT (RenoDX). ## ReShade.ini layout @@ -95,11 +95,11 @@ Setting struct (1016, for the next diff): global flag at `+0x338`, type byte at ## App support -DLSS5 Updater imports the add-on, and also downloads the newest catalog build (hash-pinned, see above; Settings can turn the -download off). A build it fetched is a library build like an import (`AddonBuild.Downloaded`, with `CatalogSha` the zip's hash so +DLSS5 Updater downloads and installs the newest catalog build automatically (hash-checked, see above; Settings can turn the +download off) and also imports the add-on. A build it fetched is a library build like an import (`AddonBuild.Downloaded`, with `CatalogSha` the zip's hash so the next check knows it is there), becomes the default when it is newer than the current default, and may go in the automatic cleanup, which never deletes imports. It installs it per game in -ReShade-only mode with ReShade 6+, a game with native DLSS and an imported `nvngx_dlssnr.dll`. It adds the add-on to +ReShade-only mode with ReShade 6+, a game with native DLSS and the NR runtime `nvngx_dlssnr.dll`. It adds the add-on to `[ADDON] LoadFromDllMain`, replaces OptiScaler-NR and excludes Deep Fried Chicken; Lecram's add-on may stay next to it. - **Library.** Every imported build is kept: `components\renodx-dlss\\renodx-dlss.addon64` plus a `version.json` (id = diff --git a/docs/design.md b/docs/design.md index 6eea672..f701988 100644 --- a/docs/design.md +++ b/docs/design.md @@ -11,7 +11,7 @@ product rules they serve. When a rule here and the code disagree, fix the code o - **Never surprise the game.** Every file placed in a game is recorded; every file replaced is backed up; uninstall and switching leave the game as it was. Nothing changes in a game without a user action (install, update, update all). - **Respect the authors.** Download only from a source the author publishes openly for that purpose. If the licence - forbids redistribution, or the builds are paid, the component is import-only in public builds. + forbids redistribution, or the builds are paid, the component is import-only in public builds (the exceptions are in section 3). - **Say why.** When the app keeps, refuses, hides or skips something, the UI says why in one line. - **Facts, not guesses.** Options, labels and help text come from the mod's own docs, config comments and release notes for that build. Undocumented values are marked undocumented (AGENTS.md, Conventions). @@ -22,7 +22,7 @@ Each component has exactly these parts. A part that doesn't apply is explicitly | Part | Rule | |---|---| -| Source | `Download` (official public source, or the catalog's hash-pinned or mirrored copy, verified), `Import` (user-supplied), or both. Import-only when the licence or a paywall requires it (section 3; one documented exception). | +| Source | `Download` (official public source, or the catalog's hash-pinned or mirrored copy, verified), `Import` (user-supplied), or both. Import-only when the licence or a paywall requires it (section 3). | | Library | Every build the app has is kept in that component's library under `components\\\`, with a `version.json` (id, label, detected version, source = downloaded/imported, date, user label). One build = one folder holding the whole set of files. No single-file "current import" slots. | | Build id | SHA-256 of the build's main file (downloads may use the release tag when the release publishes digests). Duplicate imports dedupe by id. | | Order | Newest first by the component's version (tag, version resource or detected label; one `BuildOrder` implementation). Ties: newer date first. | @@ -41,19 +41,16 @@ Each component has exactly these parts. A part that doesn't apply is explicitly - Licence forbids redistribution, or builds are paid: **import-only** in the public build. The app accepts the files the user got from the author (the release folder, the main file, or a .zip of the release) and copies them byte for byte. No probes or reverse engineering where the licence forbids it (see `docs/addons/README.md`). -- **Exception, ShortFuse's DLSS add-on (user decision 2026-10-21):** `renodx-dlss.addon64` has no licence we can find and no - official channel, but it is auto-pulled in the public build too, through the catalog. The catalog only **pins** it (the - author's release URL plus the SHA-256 of each build, never a mirror copy); the app downloads the zip, refuses a hash mismatch, - and takes the add-on out of it like an import (version resource checked, kept in the library). It is the project owner's decision - that this add-on should arrive without a manual import, in the public build too: its builds appear every few days and the only - channel is a community release list. Imports stay possible, a Settings switch turns the download off, and if the author objects - or a licence appears, it is a catalog edit, not an app release. It is the only component that is both unlicensed and - auto-downloaded. +- **Downloaded and installed automatically from the catalog mirror:** ShortFuse's `renodx-dlss.addon64`, Lecram's DLSS5 add-on + and NVIDIA's `nvngx_dlssnr.dll`. They have no official channel the app can use, so the catalog hosts each build (`renodxDlss` + and `neural` sections). The app downloads from the mirror (the author's URL is the fallback for ShortFuse's add-on), refuses a + size or SHA-256 mismatch, checks it is a PE file, and keeps it in the library like an import. Imports stay possible and a + Settings switch turns each download off. If the source ever has to change, it is a catalog edit, not an app release. - Owner-only conveniences that a public build must not have (today: fetching Lecram's paid builds from his channel) sit behind a compile-time switch (`-p:=true`, default off, `BuildFlags.`), are compiled in both variants and tested in both. Public releases build with the switch off; `tools/build-private.ps1` builds the private variant; the release bot drops it on copyparty after each release (`tools/release-bot/README.md`). -- NVIDIA's `nvngx_dlssnr.dll` is never bundled, uploaded or downloaded (AGENTS.md, Hard rules). +- `nvngx_dlssnr.dll` is never committed to this repo or bundled in the exe/installer; the app downloads it from the catalog mirror (AGENTS.md, Hard rules). ## 4. Choosing builds diff --git a/src/DLSSUpdater/Core/AddonLibrary.cs b/src/DLSSUpdater/Core/AddonLibrary.cs index bf00009..76abd41 100644 --- a/src/DLSSUpdater/Core/AddonLibrary.cs +++ b/src/DLSSUpdater/Core/AddonLibrary.cs @@ -4,7 +4,7 @@ namespace DLSSUpdater.Core; -/// The import-only add-ons that keep a library of builds, like the NR library does for nvngx_dlssnr.dll. +/// The add-ons (downloaded from the catalog or imported) that keep a library of builds, like the NR library does for nvngx_dlssnr.dll. public enum AddonKind { RenoDx, Lecram, Dfc } /// diff --git a/src/DLSSUpdater/Core/AppSettings.cs b/src/DLSSUpdater/Core/AppSettings.cs index 55c4316..aa3031e 100644 --- a/src/DLSSUpdater/Core/AppSettings.cs +++ b/src/DLSSUpdater/Core/AppSettings.cs @@ -82,6 +82,12 @@ public static bool IsKnownProxy(string? name) => public bool InstallReShade { get; set; } = true; /// Download the ReShade add-on build from reshade.me when no ReShade64.dll was imported. public bool AutoDownloadReShade { get; set; } = true; + /// Download nvngx_dlssnr.dll from the catalog mirror and keep it in the NR library. + public bool AutoDownloadDlssNr { get; set; } = true; + /// SHA-256 of the catalog's newest nvngx_dlssnr.dll once fetched, so a cleanup of it doesn't download it again at every check. + public string? CatalogDlssNrFetched { get; set; } + /// The same for the catalog's newest Lecram build. + public string? CatalogLecramFetched { get; set; } /// Check Lecram's public MediaFire channel for new v11 builds and add them to the library. public bool AutoDownloadLecram { get; set; } = true; public string LecramFolderKey { get; set; } = MediaFireClient.DefaultFolder; diff --git a/src/DLSSUpdater/Core/CatalogModel.cs b/src/DLSSUpdater/Core/CatalogModel.cs index a47eb96..1c49caf 100644 --- a/src/DLSSUpdater/Core/CatalogModel.cs +++ b/src/DLSSUpdater/Core/CatalogModel.cs @@ -18,8 +18,10 @@ public sealed class CatalogSections /// Shader repositories pinned to a release or commit (see ). public CatalogShaderPacks? ShaderPacks { get; set; } public CatalogOptiCompat? OptiscalerCompat { get; set; } - /// ShortFuse's renodx-dlss add-on: hash-pinned builds, never mirrored (licence unknown). + /// ShortFuse's renodx-dlss add-on: hash-checked builds, downloaded from the catalog mirror (the author's URL is the fallback). public CatalogRenodxDlss? RenodxDlss { get; set; } + /// nvngx_dlssnr.dll and Lecram's DLSS5 add-on: raw binaries in the catalog mirror, downloaded and installed automatically. + public CatalogNeural? Neural { get; set; } /// ReLimiter and Display Commander add-on files (ReLimiter mirrored, Display Commander pinned to the author's URL). public CatalogFrameLimiters? FrameLimiters { get; set; } } @@ -51,6 +53,52 @@ public string VersionOf(CatalogBinary file) => Release.Equals("latest", StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(Release) ? file.Version ?? file.Sha256?[..12] ?? "?" : Release.TrimStart('v', 'V'); } +public sealed class CatalogNeural +{ + public string? Updated { get; set; } + public CatalogNeuralLine? Dlssnr { get; set; } + public CatalogNeuralLine? Lecram { get; set; } +} + +/// One binary's builds: the newest in , older ones (newest first) in . +public sealed class CatalogNeuralLine +{ + public CatalogNeuralBuild? Current { get; set; } + public List History { get; set; } = []; + + /// Newest first. + public IEnumerable All => Current is null ? History : [Current, .. History]; + + /// The version, plus the short hash when another build of the line carries the same version (variants share a PE version). + public string LabelOf(CatalogNeuralBuild build) => + All.Count(b => string.Equals(b.Version, build.Version, StringComparison.OrdinalIgnoreCase)) > 1 + ? $"{build.Version} · {build.ShortHash}" + : build.Version; +} + +/// A raw binary (not a zip) in the mirror. Builds are told apart by , never by version. +public sealed class CatalogNeuralBuild +{ + public string Name { get; set; } = ""; + public string Version { get; set; } = ""; + public string Sha256 { get; set; } = ""; + public long Size { get; set; } + public string? FirstSeen { get; set; } + public string? MirrorUrl { get; set; } + /// Where the mirror's copy came from; only a fallback. + public string? Url { get; set; } + /// Lecram only: "v10" or "v11". + public string? Generation { get; set; } + + public string ShortHash => Sha256.Length >= 8 ? Sha256[..8] : Sha256; + + public CatalogBinary ToBinary() => new() + { + Name = Name, Sha256 = Sha256, Size = Size, Version = Version, FirstSeen = FirstSeen, + MirrorUrl = MirrorUrl, Url = Url ?? "", + }; +} + public sealed class CatalogRenodxDlss { public string? Updated { get; set; } diff --git a/src/DLSSUpdater/Core/ComponentStore.cs b/src/DLSSUpdater/Core/ComponentStore.cs index 947f642..009f57c 100644 --- a/src/DLSSUpdater/Core/ComponentStore.cs +++ b/src/DLSSUpdater/Core/ComponentStore.cs @@ -49,7 +49,7 @@ public sealed partial class ComponentStore public const string DlssNrFile = "nvngx_dlssnr.dll"; public const string ReShadeFile = "ReShade64.dll"; public const string MfgFile = "renodx-mfgunlock.addon64"; - /// ShortFuse's DLSS add-on with neural rendering; import-only. + /// ShortFuse's DLSS add-on with neural rendering; downloaded from the catalog, import works too. public const string RenoDxFile = "renodx-dlss.addon64"; /// Other ShortFuse-family add-ons that also inject NR or replaced the add-on earlier; never installed by this app. public static readonly string[] LegacyRenoDxFiles = ["renodx-dlssfix.addon64"]; diff --git a/src/DLSSUpdater/Core/Installer.cs b/src/DLSSUpdater/Core/Installer.cs index 27ad47d..6ca3e2c 100644 --- a/src/DLSSUpdater/Core/Installer.cs +++ b/src/DLSSUpdater/Core/Installer.cs @@ -153,7 +153,7 @@ private async Task InstallCoreAsync(GameInfo game, string targetDir, InstallOpti { if (o.Uplift) { - if (!o.DlssNr) throw new InvalidOperationException("GITC Uplift needs an imported NR runtime."); + if (!o.DlssNr) throw new InvalidOperationException("GITC Uplift needs the NR runtime (downloaded automatically, or import it in Settings)."); o = o with { ReShade = true }; var exe = game.Exes.FirstOrDefault(f => string.Equals(Path.GetDirectoryName(f), targetDir, StringComparison.OrdinalIgnoreCase)) ?? game.MainExe @@ -174,17 +174,17 @@ private async Task InstallCoreAsync(GameInfo game, string targetDir, InstallOpti var dlssNr = o.DlssNr; var nrPath = dlssNr ? store.VerifiedDlssNrPath(o.DlssNrId ?? store.SelectedDlssNr()) : null; if (o.RenoDx && (!o.ReShade || !dlssNr)) - throw new InvalidOperationException("RenoDX needs ReShade with add-on support and an imported NR runtime."); + throw new InvalidOperationException("RenoDX needs ReShade with add-on support and the NR runtime (downloaded automatically, or import it in Settings)."); // DLSS files an earlier install of ours added (for DFC or OptiScaler) don't make the game a DLSS game. var nativeDlss = (o.RenoDx || o.Lecram || o.Dfc) && HasNativeDlss(game, InstallManifest.Read(targetDir), targetDir); if (o.RenoDx && !nativeDlss) throw new InvalidOperationException("RenoDX needs a game with native DLSS."); if (o.Lecram && (!o.ReShade || !dlssNr)) - throw new InvalidOperationException("Lecram's DLSS5 add-on needs ReShade with add-on support and an imported NR runtime."); + throw new InvalidOperationException("Lecram's DLSS5 add-on needs ReShade with add-on support and the NR runtime (downloaded automatically, or import it in Settings)."); if (o.Lecram && !nativeDlss) throw new InvalidOperationException("Lecram's DLSS5 add-on needs a game with native DLSS."); if (o.Dfc && (!o.ReShade || !dlssNr)) - throw new InvalidOperationException("Deep Fried Chicken needs ReShade with add-on support and an imported NR runtime."); + throw new InvalidOperationException("Deep Fried Chicken needs ReShade with add-on support and the NR runtime (downloaded automatically, or import it in Settings)."); var renoDx = o.RenoDx ? store.RequireAddon(AddonKind.RenoDx, o.RenoDxId) : null; var lecram = o.Lecram ? store.RequireAddon(AddonKind.Lecram, o.LecramId) : null; var dfc = o.Dfc ? store.RequireAddon(AddonKind.Dfc, o.DfcId).Build : null; diff --git a/src/DLSSUpdater/Core/LecramSource.cs b/src/DLSSUpdater/Core/LecramSource.cs index 4aac2a8..6ca0125 100644 --- a/src/DLSSUpdater/Core/LecramSource.cs +++ b/src/DLSSUpdater/Core/LecramSource.cs @@ -8,7 +8,7 @@ namespace DLSSUpdater.Core; public enum LecramGeneration { V10, V11 } /// -/// Lecram's DLSS5 Neural Rendering add-on supports local imports and, in the private build only (BuildFlags.LecramChannel), the author's v11 update channel. +/// Lecram's DLSS5 Neural Rendering add-on is downloaded from the catalog mirror (see NeuralSource.cs) or imported and, in the private build only (BuildFlags.LecramChannel), also taken from the author's v11 update channel. /// v10 and older (renodx-dlss5.addon64) name themselves in the version resource; v11+ /// (dlss5-nr.addon64) has no version resource and is recognised by strings in the binary. Every imported build stays in the /// library; a game gets the one chosen for it, under its generation's file name. diff --git a/src/DLSSUpdater/Core/MethodAdvisor.cs b/src/DLSSUpdater/Core/MethodAdvisor.cs index 4887ee6..ce720df 100644 --- a/src/DLSSUpdater/Core/MethodAdvisor.cs +++ b/src/DLSSUpdater/Core/MethodAdvisor.cs @@ -450,7 +450,7 @@ private static MethodOption DlssUpdate(Ctx c, string reason, bool unverified = f Opt(Method.DlssUpdate, Availability.Available, reason, [ComponentRegistry.Dlss], null, null, unverified); private static string? NrRuntimeNeed(Ctx c) => - c.Has(ComponentRegistry.DlssNr) ? null : "Import nvngx_dlssnr.dll (the app never downloads it)"; + c.Has(ComponentRegistry.DlssNr) ? null : "nvngx_dlssnr.dll (downloaded automatically at the next update check, or import it)"; private static MethodOption Neural(Ctx c, string reason, string? extraNeed, bool unverified = false, string? unverifiedWhy = null) { diff --git a/src/DLSSUpdater/Core/NeuralSource.cs b/src/DLSSUpdater/Core/NeuralSource.cs new file mode 100644 index 0000000..3281958 --- /dev/null +++ b/src/DLSSUpdater/Core/NeuralSource.cs @@ -0,0 +1,94 @@ +namespace DLSSUpdater.Core; + +/// +/// nvngx_dlssnr.dll and Lecram's DLSS5 add-on come from the catalog mirror as raw binaries: downloaded, checked (size, SHA-256, +/// PE header) and kept in the same libraries an import fills, so "Newest", per-game picks and imports work as before. +/// Only the catalog's newest build is fetched on its own; older ones stay in the libraries once they are there. Offline or +/// refused downloads are logged and change nothing. +/// +public sealed partial class ComponentStore +{ + public Task CheckDlssNrCatalogAsync(AppSettings settings, CatalogNeuralLine? line, ICatalogFetch fetch, + IProgress? progress, CancellationToken ct) + { + if (!settings.AutoDownloadDlssNr || line?.Current is not { } newest || newest.ToBinary() is not { IsInstallable: true } file) return Task.FromResult(null); + var sha = newest.Sha256.ToLowerInvariant(); + if (sha == settings.CatalogDlssNrFetched || DlssNrVersions.Any(v => v.Id.Equals(sha, StringComparison.OrdinalIgnoreCase))) return Task.FromResult(null); + + var what = $"nvngx_dlssnr.dll {line.LabelOf(newest)}"; + return InNeuralWorkAsync(what, sha, async work => + { + var path = Path.Combine(work, DlssNrFile); + var result = await fetch.DownloadAsync(file, path, progress, ct); + if (!result.Ok) { Log.Info($"{what} not downloaded: {result.Refusal}"); return null; } + RequirePe(path, DlssNrFile); + if (!IsDlssNr(path)) Log.Warn($"{what}: the file doesn't name itself DLSSNR, but its hash matches the catalog"); + var version = RetainDlssNr(path); + settings.CatalogDlssNrFetched = sha; + if (AdoptNewerDlssNr(settings) is { } adopted) Log.Info($"NR runtime default is now {adopted.Version}"); + Log.Info($"{what} downloaded from the catalog"); + return version; + }, ct); + } + + /// The newest Lecram build from the catalog into the Lecram library; the file decides v10 or v11, like an import. + public Task CheckLecramCatalogAsync(AppSettings settings, CatalogNeuralLine? line, ICatalogFetch fetch, + IProgress? progress, CancellationToken ct) + { + if (!settings.AutoDownloadLecram || line?.Current is not { } newest || newest.ToBinary() is not { IsInstallable: true } file) return Task.FromResult(null); + var sha = newest.Sha256.ToLowerInvariant(); + if (sha == settings.CatalogLecramFetched || AddonBuilds(AddonKind.Lecram).Any(b => b.Id.Equals(sha, StringComparison.OrdinalIgnoreCase))) return Task.FromResult(null); + + var what = $"Lecram's add-on {line.LabelOf(newest)}"; + return InNeuralWorkAsync(what, sha, async work => + { + var path = Path.Combine(work, string.IsNullOrWhiteSpace(newest.Name) ? Lecram11File : Path.GetFileName(newest.Name)); + var result = await fetch.DownloadAsync(file, path, progress, ct); + if (!result.Ok) { Log.Info($"{what} not downloaded: {result.Refusal}"); return null; } + RequirePe(path, Path.GetFileName(path)); + var build = ImportLecram(path, downloaded: true); + if (newest.Generation is { } g && !g.Equals(build.Generation.ToString(), StringComparison.OrdinalIgnoreCase)) + Log.Warn($"{what}: the catalog says {g}, the file is {build.Generation}; going by the file"); + settings.CatalogLecramFetched = sha; + if (AdoptImportedAddon(settings, AddonKind.Lecram, build)) Log.Info($"Lecram's add-on default is now {build.Label}"); + settings.LecramLastResult = $"Downloaded {build.DetectedLabel} from the catalog."; + Log.Info($"{what} downloaded from the catalog"); + return build; + }, ct); + } + + private static async Task InNeuralWorkAsync(string what, string sha, Func> run, CancellationToken ct) where T : class + { + var work = Path.Combine(AppPaths.Cache, "neural-" + sha[..12]); + try + { + Directory.CreateDirectory(work); + return await run(work); + } + catch (Exception ex) when (!ct.IsCancellationRequested && ex is InvalidDataException or IOException or UnauthorizedAccessException) + { + Log.Warn($"{what} from the catalog was not usable: {ex.Message}"); + return null; + } + finally + { + try { FileUtil.DeleteDirectory(work); } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { } + } + } + + /// A real PE image: MZ, then the PE signature where the header points. The hash already proves it is the catalog's file. + internal static void RequirePe(string path, string name) + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var head = new byte[0x40]; + var ok = fs.Read(head, 0, head.Length) == head.Length && head[0] == 'M' && head[1] == 'Z'; + if (ok) + { + var pe = BitConverter.ToInt32(head, 0x3C); + var sig = new byte[4]; + ok = pe is > 0 and < 0x10000 && fs.Seek(pe, SeekOrigin.Begin) == pe && fs.Read(sig, 0, 4) == 4 && sig.AsSpan().SequenceEqual("PE\0\0"u8); + } + if (!ok) throw new InvalidDataException($"{name} is not a Windows binary."); + } +} diff --git a/src/DLSSUpdater/Core/NrLibrary.cs b/src/DLSSUpdater/Core/NrLibrary.cs index 1a2801f..765d902 100644 --- a/src/DLSSUpdater/Core/NrLibrary.cs +++ b/src/DLSSUpdater/Core/NrLibrary.cs @@ -113,7 +113,7 @@ internal NrVersion RetainDlssNr(string source) public string VerifiedDlssNrPath(string? id) { var path = DlssNrFor(id); - if (!File.Exists(path)) throw new FileNotFoundException("Import this NR version in Settings → Local components first.", path); + if (!File.Exists(path)) throw new FileNotFoundException("This NR version isn't in the library. It downloads at the next update check, or import it in Settings → Local components.", path); if (id is not null && !FileUtil.Sha256(path).Equals(id, StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("The stored NR file changed. Import it again before switching."); return path; diff --git a/src/DLSSUpdater/Core/RenoDxSource.cs b/src/DLSSUpdater/Core/RenoDxSource.cs index e826516..e83fa63 100644 --- a/src/DLSSUpdater/Core/RenoDxSource.cs +++ b/src/DLSSUpdater/Core/RenoDxSource.cs @@ -4,10 +4,9 @@ namespace DLSSUpdater.Core; /// -/// ShortFuse's renodx-dlss add-on isn't published on any official GitHub. It is imported like a user-supplied ReShade64.dll, and -/// also pulled from the catalog (docs/design.md section 3, the exception: its licence is unknown, so the catalog only pins the -/// hash of each build and never mirrors it). Either way the file is checked by its version resource and kept in the add-on -/// library (one folder per SHA-256, see AddonLibrary.cs). +/// ShortFuse's renodx-dlss add-on isn't published on any official GitHub. The app downloads it from the catalog (the mirror +/// first, the author's URL as the fallback, hash-checked either way) and still accepts an import. Either way the file is checked +/// by its version resource and kept in the add-on library (one folder per SHA-256, see AddonLibrary.cs). /// public sealed partial class ComponentStore { @@ -46,7 +45,7 @@ private AddonBuild ImportRenoDx(string source, bool downloaded) /// SHA-256 (the download is refused on a mismatch); the add-on inside is checked like an import. It becomes the default when /// it is newer than the current one, as an import does. Offline, refused or odd downloads are logged and change nothing. /// - public async Task CheckRenoDxCatalogAsync(AppSettings settings, CatalogRenodxDlss? section, CatalogDownloader downloader, + public async Task CheckRenoDxCatalogAsync(AppSettings settings, CatalogRenodxDlss? section, ICatalogFetch downloader, IProgress? progress, CancellationToken ct) { if (!settings.AutoDownloadRenoDx) return null; @@ -66,7 +65,8 @@ private AddonBuild ImportRenoDx(string source, bool downloaded) Log.Info($"ShortFuse's add-on {newest.Version} not downloaded: {result.Refusal}"); return null; } - var addon = await Task.Run(() => ExtractAddon(zip, work), ct); + // Builds from the catalog's incoming folder are the bare add-on, not a zip. + var addon = zip.EndsWith(RenoDxFile, StringComparison.OrdinalIgnoreCase) ? zip : await Task.Run(() => ExtractAddon(zip, work), ct); var build = ImportRenoDx(addon, downloaded: true); if (!string.Equals(build.CatalogSha, zipSha, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/DLSSUpdater/MainWindow.xaml b/src/DLSSUpdater/MainWindow.xaml index b772b10..3f4172c 100644 --- a/src/DLSSUpdater/MainWindow.xaml +++ b/src/DLSSUpdater/MainWindow.xaml @@ -1560,6 +1560,13 @@