From 608e7e13257e6e7fa05c44fb6512fc4bcdcd4fb9 Mon Sep 17 00:00:00 2001 From: Bum Date: Sat, 7 Nov 2026 21:22:16 +0100 Subject: [PATCH 1/4] fix(session card): short sessions fit the card A session under a minute read "Less than a minute" twice; the figures row cannot shrink, so the card's content grew past its 640-unit frame, was centered, and lost its first letters at the left edge. It now reads "<1m", as short as the other times, and the content is anchored to the card's leading edge so anything wider is cut on the right instead. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ Sources/NXLibrary/Views/Components/SessionCard.swift | 4 +++- Sources/NXLibraryCore/Models/SessionSummary.swift | 2 +- Tests/NXLibraryCoreTests/SessionSummaryTests.swift | 5 +++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 842e3b2..48cbfec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ User-visible changes to NX Library are recorded here. Entries are grouped by release, with the newest first. Engine changes also have their own [upgrade notes](docs/upstream-upgrades/README.md). +## 0.1.4.2 — unreleased + +### Fixed + +- **The session card fits after a short session.** A game played for under a minute showed "Less than a minute" twice, which pushed the card's words past its left edge. It now says **<1m**, like the other times (10m, 34h 5m). + ## 0.1.4.1 — 2026-11-07 ### Added diff --git a/Sources/NXLibrary/Views/Components/SessionCard.swift b/Sources/NXLibrary/Views/Components/SessionCard.swift index ad6ad6d..899e202 100644 --- a/Sources/NXLibrary/Views/Components/SessionCard.swift +++ b/Sources/NXLibrary/Views/Components/SessionCard.swift @@ -108,6 +108,8 @@ struct SessionCardView: View { } .padding(.leading, canvas.u(20)) .padding(.trailing, canvas.u(18)) + // Words wider than the card are cut at its right edge, never pushed past its left one. + .frame(width: canvas.u(640), alignment: .leading) } .frame(width: canvas.u(640), height: canvas.u(128)) .background(Color(hex: ThemeStore.shared.palette.glass, opacity: 0.92)) @@ -117,7 +119,7 @@ struct SessionCardView: View { .shadow(color: .black.opacity(0.45), radius: canvas.u(18)) .environment(\.seleneMinimumFont, 12) .accessibilityElement(children: .combine) - .accessibilityLabel("Session ended, \(summary.gameName): \(summary.thisSession) this session, \(summary.total) total") + .accessibilityLabel("Session ended, \(summary.gameName): \(summary.thisSession.replacingOccurrences(of: "<1m", with: "under a minute")) this session, \(summary.total.replacingOccurrences(of: "<1m", with: "under a minute")) total") .task(id: summary.id) { await loadArt() } } diff --git a/Sources/NXLibraryCore/Models/SessionSummary.swift b/Sources/NXLibraryCore/Models/SessionSummary.swift index fb0304f..bbc5554 100644 --- a/Sources/NXLibraryCore/Models/SessionSummary.swift +++ b/Sources/NXLibraryCore/Models/SessionSummary.swift @@ -23,6 +23,6 @@ public struct SessionSummary: Equatable, Identifiable { } static func duration(_ interval: TimeInterval) -> String { - interval < 60 ? "Less than a minute" : LibraryPresentation.duration(interval) + interval < 60 ? "<1m" : LibraryPresentation.duration(interval) } } diff --git a/Tests/NXLibraryCoreTests/SessionSummaryTests.swift b/Tests/NXLibraryCoreTests/SessionSummaryTests.swift index 25cfc65..dc9a164 100644 --- a/Tests/NXLibraryCoreTests/SessionSummaryTests.swift +++ b/Tests/NXLibraryCoreTests/SessionSummaryTests.swift @@ -33,8 +33,9 @@ final class SessionSummaryTests: XCTestCase { func testUnderAMinuteSaysSo() throws { let summary = try XCTUnwrap(SessionSummary(session: session(lasting: 42), game: game, total: 42)) - XCTAssertEqual(summary.thisSession, "Less than a minute") - XCTAssertEqual(summary.total, "Less than a minute") + // As short as the other figures ("10m", "34h 5m"): the card's row has room for two of them and their labels. + XCTAssertEqual(summary.thisSession, "<1m") + XCTAssertEqual(summary.total, "<1m") } func testSessionsShorterThanTenSecondsShowNoCard() { From 2fcb114d53a4c3725109722159fca988a089ac2f Mon Sep 17 00:00:00 2001 From: Bum Date: Sat, 7 Nov 2026 22:31:14 +0100 Subject: [PATCH 2/4] docs(readme): latest highlights, mods and 0.1.4.1 Add a Latest highlights section under the Spotlight screenshot, a Mods section with a screenshot of the mod store, the session card, icons in the Dock and Newest engine's Update. Bring the project status to 0.1.4.1 and engine bd979ced, and replace the shipped mod browser in Next steps with languages. Co-Authored-By: Claude Opus 5.5 --- README.md | 41 +++++++++++++++++++++++++++++++------- docs/media/README.md | 1 + docs/media/mod-store.webp | Bin 0 -> 148640 bytes 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 docs/media/mod-store.webp diff --git a/README.md b/README.md index 6a02ee5..6c8f1af 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,16 @@ NX-DEMO:END -->

Your library, together.
Games, artwork and activity in Spotlight.

+### New Latest highlights + +- **Mods in a few clicks.** A store of GameBanana mods that work with your games, by category and by game. Press **Install** and the mod is on at the next launch, with no rebuild. Mods you already have work too: drop them in, turn them on, choose their order. +- **Ocarina of Time, as it should look.** The newest engine clears its fog, lighting and foliage glitches and restores its paintings' colours. Pikmin 4 runs at 60 fps, up to 120 on the Mac. +- **A recap when you stop playing.** Quit a game and you are back in your library, with a card showing how long you played this time and in total. +- **Your icon in the Dock.** The icon you pick for a game shows in Finder and the Dock too, and stays after a rebuild. +- **Builds on your external drive.** Keep your games on an external drive and the whole build stays there, so the Mac's own disk needs no free space. + +Every release is in the [changelog](CHANGELOG.md). + ## Overview NX Library is an independent SwiftUI frontend for the external [nxnative](https://github.com/seedds/nxnative) toolchain. It brings your installed game apps, local build workflow and game options into a modern Mac-native library inspired by [Selene](https://github.com/mattxslv/Selene). @@ -48,6 +58,7 @@ NX Library is an independent SwiftUI frontend for the external [nxnative](https: | **Local builds** | Select your own sources, review updates and DLC, follow progress and retry a failed installation. | | **Game options** | Keep each title's launch settings, artwork and metadata together. | | **Saves** | Back up local progress, preview imports and restore a previous copy. | +| **Mods** | Install GameBanana mods that work with the engine, or add your own, and choose what is on for the next launch. | | **Cheats** | Import matching cheat files and choose what is enabled for the next launch. | | **Personalisation** | Choose palettes, wallpapers, live backgrounds, interface sounds and controller prompts. | @@ -55,11 +66,11 @@ Game files, keys and firmware are not provided. See [Legal and third-party notic ### Project status -**0.1.3.3 alpha.** Download the packaged app from [Releases](https://github.com/Bium23/NxLibrary/releases/latest), or [build it from source](#building-from-source). +**0.1.4.1 alpha.** Download the packaged app from [Releases](https://github.com/Bium23/NxLibrary/releases/latest), or [build it from source](#building-from-source). Automated checks cover the application code, publication inputs, compilation and development-app packaging. They do not certify game compatibility. Changes are recorded in the [changelog](CHANGELOG.md), and the current checks are available under [Actions](https://github.com/Bium23/NxLibrary/actions/workflows/ci.yml). -The engine is pinned to `b64a5ff0`, with audio, memory and renderer fixes, updated game profiles and shader-build improvements, using Xcode 27.2 / Metal 32023.922. See the [engine upgrade notes](docs/upstream-upgrades/2026-11-03-65501fbb-b64a5ff0-review.md). +The engine is pinned to `bd979ced`, with Ocarina of Time (Switch 2) fixes, Pikmin 4 at 60 fps, two identical USB controllers as two players and package reading by content, using Xcode 27.2 / Metal 32023.922. See the [engine upgrade notes](docs/upstream-upgrades/README.md). ## Getting started @@ -138,6 +149,8 @@ If an installed app is moved or removed, its library entry stays with a blurred, Use **Find a game** for search, **Game Options** for title-specific actions, and the menu for themes, Settings and running-game controls. Hidden games can be restored under **Settings → Games → Hidden games**. +When you quit a game, NX Library comes back to the front, unless you had moved to another app or another game is still open. A **session card** in the top right shows how long you played this time and in total; sessions under 10 seconds show none. Turn it off in **Settings → Games → Session card**. + ![The quick menu open over Spotlight, with Theme selected](docs/media/quick-menu.webp)

Quick access.
Open the menu without leaving your library.

@@ -207,9 +220,23 @@ Open **Game Options → Cheats**, then choose or drop a matching cheat file. NX Changes apply at the next Play and are written only while the game is closed. An older or unrecognised engine build may require **Rebuild**. The engine's in-game panel, `⌘⇧C`, reads the same files. +Cheats change values while you play, for one game version only. New items, looks or gameplay are mods. + +### Mods + +- **Mod store.** The **Mods** card in Spotlight opens a store of the GameBanana mods that work with your games: a featured banner, the most popular, a row per category and a row per game, sorted by Popular, Most liked, Newest or Recently updated. Hide the card in **Settings → Spotlight**. +- **A game's Mods band.** Each game page lists its GameBanana mods and the ones you installed. Open a mod for its images, author, version and requirements, then press **Install**; **Update** appears when the author replaces the file. If the game's GameBanana page is uncertain, the band asks once, and **Wrong game?** picks another. +- **Your own mods.** File mods (a folder with `romfs` inside) go into the game's mods folder. Turn them on in the band or in **Game Options → Mods**; the order decides which mod wins when two change the same file. **Remove** moves a mod to the Trash. + +Mods apply at the next launch, without a rebuild. The engine reads file mods only: mods that patch code (`exefs`, such as 60 fps patches) or need a loader or PC tool (exlaunch, Skyline, ARCropolis, mod managers) don't work, and the store leaves them out. + +![The mod store with a featured mod, sort choices and rows of popular mods and characters](docs/media/mod-store.webp) + +

Mods for your games.
Browse what works with the engine and install it in one step.

+ ### Artwork, themes and sound -- **Game Options → Edit details** changes one game's artwork or metadata. Manual artwork choices take priority over automatic images. +- **Game Options → Edit details** changes one game's artwork or metadata. Manual artwork choices take priority over automatic images. The Icon you choose in **Change artwork** also shows in Finder and the Dock for a game app without its own icon; for one with its own, NX Library asks first. **Automatic** brings back the game's icon. - **Menu → Theme** chooses palettes and wallpapers. Background choices include local images, muted looping videos and optional online sources; source credits appear in the picker. - **Settings → Look** controls wallpaper placement, sizing, brightness, game backgrounds, Reduce Motion and the Color/Monochrome app icon. - **Settings → Sound & music** controls interface cues and volume. Four interface sounds are bundled; additional sounds and wallpapers can be imported from a Selene theme folder you own. @@ -265,17 +292,17 @@ Before reporting a problem, review copied diagnostics and remove anything person ### Compatibility and limitations -- NX Library uses a manually pinned engine and checks its commit, working tree, CLI contract and Metal compiler before new builds. It does not follow upstream `main` automatically. **Settings → Engine → Newest engine** lets you choose `main` yourself before NX Library has tested it: an orange dot marks it, builds are recorded as untested, **Back to tested** returns to the pinned engine, and it is not offered when `main` needs a different Metal compiler. +- NX Library uses a manually pinned engine and checks its commit, working tree, CLI contract and Metal compiler before new builds. It does not follow upstream `main` automatically. **Settings → Engine → Newest engine** lets you choose `main` yourself before NX Library has tested it: an orange dot marks it, builds are recorded as untested, **Update** moves to a newer `main` when there is one, **Back to tested** returns to the pinned engine, and it is not offered when `main` needs a different Metal compiler. - The current engine is `bd979ced` and certifies Metal `32023.922`. **Build anyway with an untested toolchain** requires explicit confirmation and marks the resulting build as untested. A successful launch does not turn it into a certified combination. - Game compatibility depends on nxnative, the game version and the toolchain. - Output follows the window/display; native 1440p/4K internal rendering is not supported. Render scale is 50–100% where the build supports it. MetalFX is spatial upscaling. - Builds run one at a time. iOS/IPA output, automatic engine updates and notarisation are not currently provided. -The [upgrade note](docs/upstream-upgrades/2026-11-03-65501fbb-b64a5ff0-review.md) records the engine review and toolchain requirements. +The [upgrade notes](docs/upstream-upgrades/README.md) record each engine review and its toolchain requirements. ### Data and cleanup -App data lives in `~/Library/Application Support/NXLibrary/`. Local game data uses `~/Library/Application Support/nxnative//`. Backups are stored under `NXLibrary/SaveBackups/<title ID>/`. +App data lives in `~/Library/Application Support/NXLibrary/`. Local game data uses `~/Library/Application Support/nxnative/<title ID>/`, with mods in its `mods` folder. Backups are stored under `NXLibrary/SaveBackups/<title ID>/`. Storage can clear caches and logs and open the shared backups folder. **Remove Sources & Build Cache deletes the selected original source files**, while retaining the installed app and saves. Read that confirmation carefully; it differs from clearing a cache. @@ -284,7 +311,7 @@ Storage can clear caches and logs and open the shared backups folder. **Remove S - **Simpler Settings** — Keep everyday options easy to find, with advanced controls grouped within each category. - **In-app bug reporting** — Make it easier to report a problem directly from NX Library. - **Broader game discovery** — Discover and launch games from Steam, CrossOver and the Mac App Store, alongside nxnative apps. -- **An in-app mod browser** — Browse, download and install game mods without leaving the app. +- **Languages** — NX Library in more languages than English. - **Better controller support** — Refine controller settings and offer more flexible button mapping. - **Continued polish** — Fix bugs and improve navigation, feedback and visual details throughout the app. diff --git a/docs/media/README.md b/docs/media/README.md index 3eb09c8..382d4a5 100644 --- a/docs/media/README.md +++ b/docs/media/README.md @@ -16,6 +16,7 @@ Le otto catture fornite dall'utente il 2 novembre 2026 sono state rinominate nel | `Color theme.png` | [color-themes.webp](color-themes.webp) | Artwork, themes and sound: scelta della palette. | | `Background Static Wallpaper.png` | [static-wallpapers.webp](static-wallpapers.webp) | Artwork, themes and sound: sfondi statici. | | `Background Wallpaper Live .png` | [live-wallpapers.webp](live-wallpapers.webp) | Artwork, themes and sound: sfondi animati. | +| `Mods.png` (7 novembre 2026) | [mod-store.webp](mod-store.webp) | Mods: lo store con il banner Featured e le righe. | Le copie WebP sono larghe 1920 pixel, mantengono proporzioni e contenuto delle catture e usano il profilo colore sRGB. Occupano complessivamente circa 2,3 MB, rispetto ai circa 73 MB degli originali. I metadati personali non vengono trasferiti. Le didascalie e i testi alternativi sono in inglese; ciascuna immagine ha una propria riga per preservare la leggibilità. diff --git a/docs/media/mod-store.webp b/docs/media/mod-store.webp new file mode 100644 index 0000000000000000000000000000000000000000..974f9fccf85ceda723efa8f8de9a1d984cca750a GIT binary patch literal 148640 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%0Aklftk3W^LBlaRI)u*PUEfmxKD8U7$Szg!F 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 OVP~hTdguTE0000)hn)rh literal 0 HcmV?d00001 From 911e0ae96f2de439b0dd6e9f1f5c4180f16a5e7f Mon Sep 17 00:00:00 2001 From: Bum <Bium23@users.noreply.github.com> Date: Mon, 9 Nov 2026 12:12:14 +0100 Subject: [PATCH 3/4] fix: release 0.1.4.2 with nxnative 79741184 Make long builds readable with live elapsed times, hour formatting, percentages and confirmed pipeline-batch progress guarded against stale results. Recognize pack_app disk errors and avoid blaming keys for keyless sources; parse Xcode's version prefix correctly. Keep game icon source and location independent, preserve original icon provenance, restore the original or its absence when a replacement is missing, and explain the result in the picker and completed build log. Wait safely for icon signing before Play, with cancellable pending launch. Validate and cache available Title DB artwork; respect the secondary provider's image-path opt-out. Protect saved 120 FPS choices. Pin latest upstream 79741184 at the maintainer's explicit request after reviewing 71 commits and passing the compatibility gate. Keep the same certified Metal compiler and preserve the outstanding manual game checks in the upgrade record. Date/version the release and document all changes. Retain Eduardo Mozart de Oliveira's PR #13 disk fix and PR #11 progress work, adapting pipeline counting to actual ready records in the current engine log rather than compiler/cache files. Co-Authored-By: Eduardo Mozart de Oliveira <2974895+eduardomozart@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- CHANGELOG.md | 12 +- NOTICE | 8 + README.md | 10 +- .../ViewModels/AppViewModel+AppIcon.swift | 163 +++++++++++------ .../AppViewModel+PendingInstall.swift | 3 + .../NXLibrary/ViewModels/AppViewModel.swift | 104 +++++++++-- .../Views/AddGame/AddGameStepThreeView.swift | 13 +- .../Components/SpotlightTileArtwork.swift | 7 +- .../Views/Components/SwitchBoxArtCover.swift | 3 +- .../Views/GameOptions/ChangeArtworkView.swift | 88 +++++++--- .../Models/AppIconProgress.swift | 33 ++++ .../NXLibraryCore/Models/IconLaunchWait.swift | 28 +++ Sources/NXLibraryCore/Models/PortedGame.swift | 21 +++ .../Models/PortingFailureReason.swift | 13 +- .../Models/PortingPresentation.swift | 29 ++- .../Models/PortingProgress.swift | 14 +- .../Models/ToolchainRequirements.swift | 5 +- .../Services/AppIconWriter.swift | 20 +++ .../Services/ArtworkPicker.swift | 7 + .../NXLibraryCore/Services/ArtworkStore.swift | 13 ++ .../Services/AutomaticIconResolver.swift | 165 ++++++++++++++++++ .../NXLibraryCore/Services/BuildEvent.swift | 2 +- .../Services/BuildEventParser.swift | 2 +- .../Services/PipelineArchiveProgress.swift | 56 ++++++ .../AppIconPreferenceTests.swift | 97 ++++++++++ .../AppIconProgressTests.swift | 17 ++ .../AppIconWriterTests.swift | 57 +++++- Tests/NXLibraryCoreTests/AppUpdateTests.swift | 10 ++ .../AutomaticIconTests.swift | 116 ++++++++++++ .../BuildEventParserTests.swift | 6 +- .../IconLaunchWaitTests.swift | 34 ++++ .../NXNativeGameSettingsTests.swift | 26 +++ .../PipelineArchiveProgressTests.swift | 89 ++++++++++ .../PortingFailureReasonTests.swift | 21 ++- .../PortingPresentationTests.swift | 47 +++++ .../PortingProgressTests.swift | 23 +++ VERSION | 2 +- docs/architecture.md | 12 +- docs/file-map.md | 9 + docs/reviews/2026-11-09-hotfix-0.1.4.2.md | 45 +++++ .../2026-11-09-bd979ced-79741184-review.md | 153 ++++++++++++++++ docs/upstream-upgrades/README.md | 4 + 42 files changed, 1455 insertions(+), 132 deletions(-) create mode 100644 Sources/NXLibraryCore/Models/AppIconProgress.swift create mode 100644 Sources/NXLibraryCore/Models/IconLaunchWait.swift create mode 100644 Sources/NXLibraryCore/Services/AutomaticIconResolver.swift create mode 100644 Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift create mode 100644 Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift create mode 100644 Tests/NXLibraryCoreTests/AppIconProgressTests.swift create mode 100644 Tests/NXLibraryCoreTests/AutomaticIconTests.swift create mode 100644 Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift create mode 100644 Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift create mode 100644 docs/reviews/2026-11-09-hotfix-0.1.4.2.md create mode 100644 docs/upstream-upgrades/2026-11-09-bd979ced-79741184-review.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cbfec..c93880f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,21 @@ User-visible changes to NX Library are recorded here. Entries are grouped by release, with the newest first. Engine changes also have their own [upgrade notes](docs/upstream-upgrades/README.md). -## 0.1.4.2 — unreleased +## 0.1.4.2 — 2026-11-09 ### Fixed - **The session card fits after a short session.** A game played for under a minute showed "Less than a minute" twice, which pushed the card's words past its left edge. It now says **<1m**, like the other times (10m, 34h 5m). +- **The update dialog no longer asks to update an Xcode that is current.** It read the installed Xcode as "Xcode 27.2", took it for an older version, and said "New portings need Xcode 27.2; this Mac has Xcode 27.2". The warning now appears only when Xcode really is older. The update itself was never blocked. +- **A full disk is no longer mistaken for missing keys.** A game that runs out of space while its Mac app is packed now offers **Open Storage**. Failures on NUP files and extracted dumps no longer blame keys they do not need. Based on [@eduardomozart’s #13](https://github.com/Bium23/NxLibrary/pull/13). +- **Play waits for the game's icon.** If its app is being signed with a new icon, **Play** shows **Preparing** and starts it when ready. **Cancel** cancels the launch while letting the icon finish safely. A failed icon update does not launch the game. + +### Changed + +- **Updated nxnative to `79741184`.** Upstream reports fewer Ocarina of Time startup and first-draw stalls, lower memory use, lighting/fog/foliage fixes, improved frame pacing and safer pipeline reuse. Rebuilt games can offer Switch 2 controllers over Bluetooth in **Game options → More settings → Controls**, and supported profiles offer 120 fps through **Frame rate**. Cloud sync avoids overwriting saves changed after launch; other fixes cover controller rumble, H.264 movies, bracketed folder names and multi-program DLC. The Metal compiler remains **32023.922**. The next rebuild updates shader data as needed; existing game apps keep their engine until rebuilt. See the [full engine review](docs/upstream-upgrades/2026-11-09-bd979ced-79741184-review.md) for per-game support and validation limits. +- **Choose where a game icon changes.** **Change artwork → Icon → Icon location** offers **Only here** or **Use on the Mac** for every game, including Switch 2 NUPs and dumps without an original icon. **Only here** keeps the image inside NX Library and restores the original app icon (or its absence). The choice survives restarts and rebuilds; existing libraries keep their previous behavior until you make a new choice. +- **Automatic keeps your icon location choice.** It removes the personal image and uses the game's own icon, then available Title DB artwork. An app without an original icon can use the online image when its location choice allows it. Cached artwork works offline; if no replacement is found, the old personal image is removed and the original or standard macOS icon remains. The picker and finished build log explain when no icon is available. Missing artwork does not prevent Play. +- **Long builds are easier to follow.** Stage times count up while a stage runs and show hours beyond one hour, as do time estimates. Counters include a percentage. **Pipeline archive** reports batches the engine has confirmed ready, including reused batches, and estimates the remaining compilation when it has a measured rate. An unsupported log format leaves the stage indeterminate. Based on [@eduardomozart’s #11](https://github.com/Bium23/NxLibrary/pull/11), with pipeline tracking revised to ignore old caches and in-flight compiler files. ## 0.1.4.1 — 2026-11-07 diff --git a/NOTICE b/NOTICE index ffb1124..ff09feb 100644 --- a/NOTICE +++ b/NOTICE @@ -126,3 +126,11 @@ The full licence texts ship in Runtime/licenses/. The relinking approach in scripts/bundle_runtime.sh and scripts/relink_macho.py follows LumaNX's scripts/bundle_runtime.py (https://github.com/Xc1Ym/LumaNX, MIT). + +-------------------------------------------------------------------------------- +Optional online artwork source: Switch Icon Showdown +https://www.switchiconshowdown.com/ +Images are fetched only at runtime when source access permits, attributed in the +artwork picker, and are not distributed with NX Library. Artwork rights remain +with their respective owners. The Swift resolver is an independent implementation; +no source from the GPL-licensed Fritchii icon_search.cpp is included. diff --git a/README.md b/README.md index 6c8f1af..787fc68 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ NX-DEMO:END --> ### <img src="https://img.shields.io/badge/new-E8833A?style=flat" alt="New" height="20" align="absmiddle"> Latest highlights - **Mods in a few clicks.** A store of GameBanana mods that work with your games, by category and by game. Press **Install** and the mod is on at the next launch, with no rebuild. Mods you already have work too: drop them in, turn them on, choose their order. -- **Ocarina of Time, as it should look.** The newest engine clears its fog, lighting and foliage glitches and restores its paintings' colours. Pikmin 4 runs at 60 fps, up to 120 on the Mac. +- **A lighter, smoother Ocarina of Time.** The updated engine reduces repeated startup work and first-draw stalls, uses less memory and fixes lighting and fog. Supported games offer up to 120 fps after rebuilding. - **A recap when you stop playing.** Quit a game and you are back in your library, with a card showing how long you played this time and in total. -- **Your icon in the Dock.** The icon you pick for a game shows in Finder and the Dock too, and stays after a rebuild. +- **Your icon in the Dock.** Choose whether a game icon stays inside NX Library or also appears in Finder and the Dock, even after a rebuild. - **Builds on your external drive.** Keep your games on an external drive and the whole build stays there, so the Mac's own disk needs no free space. Every release is in the [changelog](CHANGELOG.md). @@ -70,7 +70,7 @@ Game files, keys and firmware are not provided. See [Legal and third-party notic Automated checks cover the application code, publication inputs, compilation and development-app packaging. They do not certify game compatibility. Changes are recorded in the [changelog](CHANGELOG.md), and the current checks are available under [Actions](https://github.com/Bium23/NxLibrary/actions/workflows/ci.yml). -The engine is pinned to `bd979ced`, with Ocarina of Time (Switch 2) fixes, Pikmin 4 at 60 fps, two identical USB controllers as two players and package reading by content, using Xcode 27.2 / Metal 32023.922. See the [engine upgrade notes](docs/upstream-upgrades/README.md). +The engine is pinned to `79741184`, with Ocarina of Time startup/memory/rendering improvements, Switch 2 Bluetooth controller support in rebuilt games, updated frame-rate profiles and cloud-save race protection, using Xcode 27.2 / Metal 32023.922. See the [engine upgrade notes](docs/upstream-upgrades/README.md). ## Getting started @@ -236,7 +236,7 @@ Mods apply at the next launch, without a rebuild. The engine reads file mods onl ### Artwork, themes and sound -- **Game Options → Edit details** changes one game's artwork or metadata. Manual artwork choices take priority over automatic images. The Icon you choose in **Change artwork** also shows in Finder and the Dock for a game app without its own icon; for one with its own, NX Library asks first. **Automatic** brings back the game's icon. +- **Game Options → Edit details** changes one game's artwork or metadata. Manual artwork choices take priority over automatic images. After choosing an Icon in **Change artwork**, choose **Only here** to keep it inside NX Library or **Use on the Mac** to show it in Finder and the Dock too. This choice is available for every game, including apps with no original icon, and survives restarts and rebuilds. **Only here** restores the original app icon (or its absence) if a custom one was already applied. **Automatic** removes the personal image and uses the game's own icon, then available online artwork, while keeping your location choice. On the Icon tab, **Icon location** changes **Only here** / **Use on the Mac** at any time, including with Automatic selected. If no image is found, the app keeps the standard macOS icon and remains playable; Change artwork explains that no icon is available. The finished build log records the separate icon result. - **Menu → Theme** chooses palettes and wallpapers. Background choices include local images, muted looping videos and optional online sources; source credits appear in the picker. - **Settings → Look** controls wallpaper placement, sizing, brightness, game backgrounds, Reduce Motion and the Color/Monochrome app icon. - **Settings → Sound & music** controls interface cues and volume. Four interface sounds are bundled; additional sounds and wallpapers can be imported from a Selene theme folder you own. @@ -293,7 +293,7 @@ Before reporting a problem, review copied diagnostics and remove anything person ### Compatibility and limitations - NX Library uses a manually pinned engine and checks its commit, working tree, CLI contract and Metal compiler before new builds. It does not follow upstream `main` automatically. **Settings → Engine → Newest engine** lets you choose `main` yourself before NX Library has tested it: an orange dot marks it, builds are recorded as untested, **Update** moves to a newer `main` when there is one, **Back to tested** returns to the pinned engine, and it is not offered when `main` needs a different Metal compiler. -- The current engine is `bd979ced` and certifies Metal `32023.922`. **Build anyway with an untested toolchain** requires explicit confirmation and marks the resulting build as untested. A successful launch does not turn it into a certified combination. +- The current engine is `79741184` and certifies Metal `32023.922`. **Build anyway with an untested toolchain** requires explicit confirmation and marks the resulting build as untested. A successful launch does not turn it into a certified combination. - Game compatibility depends on nxnative, the game version and the toolchain. - Output follows the window/display; native 1440p/4K internal rendering is not supported. Render scale is 50–100% where the build supports it. MetalFX is spatial upscaling. - Builds run one at a time. iOS/IPA output, automatic engine updates and notarisation are not currently provided. diff --git a/Sources/NXLibrary/ViewModels/AppViewModel+AppIcon.swift b/Sources/NXLibrary/ViewModels/AppViewModel+AppIcon.swift index e53f36a..86749d2 100644 --- a/Sources/NXLibrary/ViewModels/AppViewModel+AppIcon.swift +++ b/Sources/NXLibrary/ViewModels/AppViewModel+AppIcon.swift @@ -2,18 +2,33 @@ import AppKit import CoreServices import NXLibraryCore -/// The game app's own icon in Finder and the Dock follows the Icon chosen in Change artwork: -/// - an app without an icon of its own (a Switch 2 NUP, a dump) shows the player's Icon whenever there is one; -/// - an app with the game's icon keeps it, unless the player chose to show their Icon there too (the pop-up after -/// choosing one); -/// - Automatic (no Icon pick) puts the game's own icon back. +/// The Icon chosen in Change artwork can stay only inside NX Library or also appear in Finder and the Dock. +/// The explicit choice applies to every game, including apps with no original icon. Legacy libraries keep their +/// previous automatic behavior until the player makes a choice. Automatic changes the source, preserving scope. /// Written after every build that installs (a rebuild replaces the whole app), and only while the game is not /// running: otherwise `appIconPending` waits for it to quit (`syncPendingAppIcons`, at launch and on every quit). extension AppViewModel { static let appIconWriter = AppIconWriter() + static let automaticIconArtwork = ArtworkStore() private func iconKey(_ game: PortedGame) -> String { game.titleId ?? game.uniqueKey } + /// Resolve the image independently of whether the player wants it applied to the Mac app. + func automaticIconURL(for game: PortedGame) async -> URL? { + if let original = Self.appIconWriter.originalIconURL(game.url, key: iconKey(game)) { return original } + if let workspace = game.workspacePath, let titleID = game.titleId { + let extracted = URL(fileURLWithPath: workspace).appendingPathComponent("\(titleID)_build/extract/v0/icon.jpg") + if NSImage(contentsOf: extracted) != nil { return extracted } + } + guard let titleID = game.titleId else { return nil } + return await Self.automaticIconArtwork.automaticIcon(titleID: titleID, name: game.name) + } + + func usesIconOnMac(for game: PortedGame) -> Bool { + let current = libraryGames.first { $0.id == game.id } ?? game + return current.appIconOnMac ?? (current.usesPickedAppIcon || !Self.appIconWriter.hasOriginalIcon(current.url, key: iconKey(current))) + } + private func installedApp(_ game: PortedGame) -> URL? { let app = game.url guard app.pathExtension.lowercased() == "app", @@ -21,25 +36,51 @@ extension AppViewModel { return app } - /// Whether choosing an Icon should ask before changing the app: only when the game has an icon of its own to lose. - public func shouldAskAboutAppIcon(_ game: PortedGame) -> Bool { - guard let app = installedApp(game) else { return false } - return Self.appIconWriter.hasOriginalIcon(app, key: iconKey(game)) + /// Keep one cancellable Play request while the app is being signed; signing itself must finish safely. + func waitForAppIconBeforeLaunching(_ game: PortedGame) { + showSettingsNotice(title: "Preparing \(game.name)", + message: "Preparing the app icon. The game will start when it is ready. This can take a minute for a large game.", + cancelTitle: "Cancel") + guard let notice = settingsNotice else { return } + iconLaunchWait.begin(gameID: game.id, noticeID: notice.id) + launchingGameId = game.id + SoundEffectService.shared.play(.click) + } + + private func finishIconLaunchWait(for game: PortedGame, rerun: Bool, outcome: Result<Bool, Error>) { + guard iconLaunchWait.gameID == game.id, !rerun else { return } + let noticeID = iconLaunchWait.noticeID + let succeeded: Bool + if case .success = outcome { succeeded = true } else { succeeded = false } + let launchID = iconLaunchWait.finished(gameID: game.id, rerun: false, succeeded: succeeded, + visibleNoticeID: settingsNotice?.id) + if launchingGameId == game.id { launchingGameId = nil } + if settingsNotice?.id == noticeID { + settingsNotice = nil + if case .failure(let error) = outcome { + showSettingsNotice(title: "Could not prepare the game", message: error.localizedDescription) + } + } + if let launchID, let current = libraryGames.first(where: { $0.id == launchID }) { + // Recheck the current app, saves and running state, as for any Play request. + launchGame(current) + } } /// The pop-up's answer: show the player's Icon on the app too, or keep the game's own. public func setUsesPickedAppIcon(_ use: Bool, for game: PortedGame) { updateAppIconState(game) { $0.usesPickedAppIcon = use + $0.appIconOnMac = use $0.appIconPending = true } syncPendingAppIcons() } - /// The Icon pick changed without a question (an app with no icon of its own, or Automatic). + /// Automatic removes the personal image without changing the player's destination choice. public func appIconPickChanged(for game: PortedGame, removed: Bool) { updateAppIconState(game) { - if removed { $0.usesPickedAppIcon = false } + if removed { $0.useAutomaticAppIcon() } $0.appIconPending = true } syncPendingAppIcons() @@ -51,9 +92,11 @@ extension AppViewModel { for game in libraryGames where !game.appIconPending && !appIconsInFlight.contains(game.id) { guard let app = installedApp(game) else { continue } let writer = Self.appIconWriter - let wantsPick = ArtworkPicks.shared.url(titleID: iconKey(game), slot: .icon) != nil - && (game.usesPickedAppIcon || !writer.hasOriginalIcon(app, key: iconKey(game))) - if wantsPick != writer.showsWrittenIcon(app) { + let hasPick = ArtworkPicks.shared.url(titleID: iconKey(game), slot: .icon) != nil + let hasOriginal = writer.hasOriginalIcon(app, key: iconKey(game)) + let wantsIcon = game.wantsPickedAppIcon(hasPick: hasPick, hasOriginalIcon: hasOriginal) + || game.wantsAutomaticAppIcon(hasPick: hasPick, hasOriginalIcon: hasOriginal) + if wantsIcon != writer.showsWrittenIcon(app) { updateAppIconState(game) { $0.appIconPending = true } } } @@ -75,46 +118,61 @@ extension AppViewModel { let pick = ArtworkPicks.shared.url(titleID: iconKey(game), slot: .icon) let writer = Self.appIconWriter let key = iconKey(game) - let usesPick = game.usesPickedAppIcon appIconsInFlight.insert(game.id) appIconProgress[game.id] = .updating - DispatchQueue.global(qos: .userInitiated).async { [weak self] in - var outcome: Result<Bool, Error> - do { - if let pick, usesPick || !writer.hasOriginalIcon(app, key: key) { - try writer.apply(image: Data(contentsOf: pick), to: app, key: key) - outcome = .success(true) - } else if writer.showsWrittenIcon(app) { - try writer.restore(app, key: key) - outcome = .success(true) - } else { - outcome = .success(false) + let hasOriginal = writer.hasOriginalIcon(app, key: key) + let wantsPick = game.wantsPickedAppIcon(hasPick: pick != nil, hasOriginalIcon: hasOriginal) + let wantsAutomatic = game.wantsAutomaticAppIcon(hasPick: pick != nil, hasOriginalIcon: hasOriginal) + let buildLog = appIconBuildLogs[game.id] + let needsAutomaticImage = pick == nil && !hasOriginal + if needsAutomaticImage { appIconProgress[game.id] = .searching } + Task { @MainActor [weak self] in + let automatic = needsAutomaticImage ? await self?.automaticIconURL(for: game) : nil + let source: URL? + if wantsPick { source = pick } + else if wantsAutomatic { source = automatic } + else { source = nil } + self?.appIconProgress[game.id] = .updating + // Only immutable file paths and flags cross to the signing worker. Network waits and signing + // leave the main thread free; library/UI state is updated back on the main actor. + let outcome = await Task.detached(priority: .userInitiated) { () -> Result<Bool, Error> in + let writer = AppIconWriter() + do { + return .success(try writer.update(image: source.map { try Data(contentsOf: $0) }, app: app, key: key)) + } catch { return .failure(error) } + }.value + guard let self else { return } + self.appIconsInFlight.remove(game.id) + let again = self.appIconRerun.remove(game.id) != nil + if !again { self.updateAppIconState(game) { $0.appIconPending = false } } + switch outcome { + case .success(let changed): + let status: AppIconProgress + if needsAutomaticImage && automatic == nil { status = .unavailable } + else if game.appIconOnMac == false { status = .launcherOnly } + else if changed && source != nil { status = .updated } + else { status = hasOriginal ? .original : .unavailable } + self.appIconProgress[game.id] = again ? .updating : status + if changed { + Self.refreshIcon(of: app) + GameIconProvider.forget(game) + GameArtLoader.shared.forget(game) + ArtworkPicks.shared.touch() + self.logs.append(LogEntry(level: .info, message: "Updated the app icon of \(game.name).")) } - } catch { - outcome = .failure(error) + case .failure(let error): + self.appIconProgress[game.id] = .failed(error.localizedDescription) + self.logs.append(LogEntry(level: .error, + message: "Could not change the app icon of \(game.name): \(error.localizedDescription)")) } - DispatchQueue.main.async { - guard let self else { return } - self.appIconsInFlight.remove(game.id) - let again = self.appIconRerun.remove(game.id) != nil - if !again { self.updateAppIconState(game) { $0.appIconPending = false } } - switch outcome { - case .success(let changed): - self.appIconProgress[game.id] = again ? .updating : .updated - if changed { - Self.refreshIcon(of: app) - GameIconProvider.forget(game) - GameArtLoader.shared.forget(game) - ArtworkPicks.shared.touch() - self.logs.append(LogEntry(level: .info, message: "Updated the app icon of \(game.name).")) - } - case .failure(let error): - self.appIconProgress[game.id] = .failed(error.localizedDescription) - self.logs.append(LogEntry(level: .error, - message: "Could not change the app icon of \(game.name): \(error.localizedDescription)")) - } - if again { self.syncPendingAppIcons() } + if !again, let buildLog, self.appIconBuildLogs[game.id] == buildLog, + let status = self.appIconProgress[game.id] { + self.appIconBuildLogs[game.id] = nil + try? status.appendToBuildLog(buildLog) + self.logs.append(LogEntry(level: .info, message: "\(game.name): \(status.buildMessage)")) } + self.finishIconLaunchWait(for: game, rerun: again, outcome: outcome) + if again { self.syncPendingAppIcons() } } } } @@ -126,6 +184,7 @@ extension AppViewModel { guard (try? libraryStore.save(library)) != nil else { return } if let shown = libraryGames.firstIndex(where: { $0.id == game.id }) { libraryGames[shown].usesPickedAppIcon = library.games[index].usesPickedAppIcon + libraryGames[shown].appIconOnMac = library.games[index].appIconOnMac libraryGames[shown].appIconPending = library.games[index].appIconPending } } @@ -137,9 +196,3 @@ extension AppViewModel { NSWorkspace.shared.noteFileSystemChanged(app.path) } } - -/// Where a game app's icon change stands, shown in Change artwork. -enum AppIconProgress: Equatable { - case updating, updated - case failed(String) -} diff --git a/Sources/NXLibrary/ViewModels/AppViewModel+PendingInstall.swift b/Sources/NXLibrary/ViewModels/AppViewModel+PendingInstall.swift index 6d83e80..b0843fb 100644 --- a/Sources/NXLibrary/ViewModels/AppViewModel+PendingInstall.swift +++ b/Sources/NXLibrary/ViewModels/AppViewModel+PendingInstall.swift @@ -26,6 +26,9 @@ extension AppViewModel { case .installed: logs.append(LogEntry(level: .success, message: "Installed the new build of \(name).")) library.games[index].appIconPending = true + if let path = library.games[index].lastBuild?.logPath, !path.isEmpty { + appIconBuildLogs[library.games[index].id] = URL(fileURLWithPath: path) + } if gameWithInstallFailure?.id == library.games[index].id { gameWithInstallFailure = nil } if canRetryReadyInstall, lastBuildGameID == library.games[index].id { buildState = .succeeded diff --git a/Sources/NXLibrary/ViewModels/AppViewModel.swift b/Sources/NXLibrary/ViewModels/AppViewModel.swift index 0d67ae6..05ba98d 100644 --- a/Sources/NXLibrary/ViewModels/AppViewModel.swift +++ b/Sources/NXLibrary/ViewModels/AppViewModel.swift @@ -18,11 +18,21 @@ public final class AppViewModel: ObservableObject { /// Games whose app icon is being written (`AppViewModel+AppIcon`), and those asked again meanwhile. var appIconsInFlight = Set<UUID>() var appIconRerun = Set<UUID>() + /// Finished build logs waiting for their separate icon preparation result. + var appIconBuildLogs: [UUID: URL] = [:] + var iconLaunchWait = IconLaunchWait() /// The last app icon change per game, for Change artwork's status line. @Published var appIconProgress: [UUID: AppIconProgress] = [:] let saveGate = SaveOperationGate() let saveQueue = DispatchQueue(label: "local.nxlibrary.saves", qos: .userInitiated) - @Published var settingsNotice: SettingsNotice? + @Published var settingsNotice: SettingsNotice? { + didSet { + if let waitingGame = iconLaunchWait.gameID, settingsNotice?.id != iconLaunchWait.noticeID { + iconLaunchWait.cancel() + if launchingGameId == waitingGame { launchingGameId = nil } + } + } + } @Published var settingsNoticeFocus = 0 /// A SteamGridDB API key is saved (Settings → Look → SteamGridDB), so Change artwork searches it. @Published var hasSteamGridDBKey = SteamGridDBKey().load() != nil @@ -440,7 +450,7 @@ public final class AppViewModel: ObservableObject { .resolve(runtime: .locate(), repo: URL(fileURLWithPath: nxnativePath)) } - public let pinnedEngineCommit = "bd979ced3ae6bad60bf86429a71d172297d93471" + public let pinnedEngineCommit = "79741184af1558fa1ada0f61741eeecbd615b3f6" private let core = FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent("Library/Application Support/NXLibrary") lazy var libraryStore = LibraryStore(directory: core) @@ -461,6 +471,11 @@ public final class AppViewModel: ObservableObject { private var environmentCheckGeneration = UUID() private var buildStartedAt: Date? private var buildTempDirectory: URL? + private var stageElapsedTimer: Timer? + private var currentStageStartedAt: Date? + private var currentBuildCacheURL: URL? + private var pipelinePollGeneration = UUID() + private var isPollingPipelineArchive = false public init(settingsStore: AppSettingsStore = AppSettingsStore()) { self.settingsStore = settingsStore @@ -1227,14 +1242,13 @@ public final class AppViewModel: ObservableObject { return } guard savesPermitPlay(game) else { return } + // Installing a waiting build can itself start an icon update: check after installation too. + let game = installingPendingBuild(of: game) // The app is being signed again with a new icon: starting it now could read a half-written bundle. guard !appIconsInFlight.contains(game.id) else { - alertMessage = "\(game.name)'s app icon is being updated. Play again in a moment." - SoundEffectService.shared.play(.error) + waitForAppIconBeforeLaunching(game) return } - // A build that waited for the game to quit goes in first. - let game = installingPendingBuild(of: game) if !FileManager.default.fileExists(atPath: game.url.path), game.isPlayableLocally || game.status == .appMissing { let reason = GameUnavailableReason.appMissing @@ -2501,9 +2515,12 @@ public final class AppViewModel: ObservableObject { ) lastPortingCommand = [command.executable.path] + command.arguments + stopStageElapsedTimer() buildStartedAt = Date() buildTempDirectory = tempRoot buildLogURL = logURL + currentBuildCacheURL = workspace.buildCache + isPollingPipelineArchive = false buildWizardStep = 3 isBuilding = true isCancellingBuild = false @@ -2557,6 +2574,7 @@ public final class AppViewModel: ObservableObject { public func cancelBuild() { guard isBuilding, let buildService else { return } + stopStageElapsedTimer() isCancellingBuild = true currentStageName = "Cancelling build..." logs.append(LogEntry(level: .warning, message: "Cancelling build...")) @@ -2601,6 +2619,10 @@ public final class AppViewModel: ObservableObject { portingProgress?.apply(event) switch event { case .stage(let k, let n, let name, let step): + let now = Date() + pipelinePollGeneration = UUID() + isPollingPipelineArchive = false + currentStageStartedAt = now currentStageName = name stageIndex = k totalStages = n @@ -2612,10 +2634,15 @@ public final class AppViewModel: ObservableObject { if let index = buildStages.firstIndex(where: { $0.id == k }) { buildStages[index].isRunning = true buildStages[index].step = step ?? "" + if buildStages[index].elapsedSeconds == nil { + buildStages[index].elapsedSeconds = 0 + } } else { buildStages.append(BuildStageItem(id: k, name: name, - step: step ?? "", isRunning: true)) + step: step ?? "", isRunning: true, + elapsedSeconds: 0)) } + startStageElapsedTimerIfNeeded() if let step, !step.isEmpty { currentStepName = step } logs.append(LogEntry(level: .step, message: "[\(k)/\(n)] \(name)")) @@ -2626,13 +2653,19 @@ public final class AppViewModel: ObservableObject { } logs.append(LogEntry(level: .info, message: "↳ \(step)")) - case .progress(let fraction, let done, let total, let unit, let eta): + case .progress(let fraction, let done, let total, let unit, let eta, let elapsed): progressFraction = min(max(fraction, 0), 1) etaSeconds = eta + if let elapsed, + let index = buildStages.firstIndex(where: { $0.id == stageIndex && $0.isRunning }) { + buildStages[index].elapsedSeconds = elapsed + currentStageStartedAt = Date().addingTimeInterval(-elapsed) + } if let done, let total, total > 0 { if let unit, !unit.isEmpty, unit.lowercased() != "b", unit.lowercased() != "bytes" { - currentStepName = "\(Self.formatAmount(done)) / \(Self.formatAmount(total)) \(unit)" + let percent = Int((min(1, max(0, done / total)) * 100).rounded(.down)) + currentStepName = "\(Self.formatAmount(done)) / \(Self.formatAmount(total)) \(unit) (\(percent)%)" } else { let doneText = ByteCountFormatter.string( fromByteCount: Int64(done), countStyle: .file) @@ -2643,6 +2676,8 @@ public final class AppViewModel: ObservableObject { } case .stageEnd(let name, let ok, let elapsed, let summary, let warn): + pipelinePollGeneration = UUID() + isPollingPipelineArchive = false let warned = ok && warn if let index = buildStages.firstIndex(where: { $0.name == name }) ?? buildStages.firstIndex(where: { $0.isRunning }) { @@ -2650,7 +2685,7 @@ public final class AppViewModel: ObservableObject { buildStages[index].isCompleted = ok buildStages[index].isFailed = !ok buildStages[index].hasWarning = warned - buildStages[index].elapsedSeconds = elapsed + buildStages[index].elapsedSeconds = elapsed ?? buildStages[index].elapsedSeconds buildStages[index].summary = summary } logs.append(LogEntry( @@ -2669,8 +2704,54 @@ public final class AppViewModel: ObservableObject { } } + private func startStageElapsedTimerIfNeeded() { + guard !isCancellingBuild, stageElapsedTimer == nil else { return } + let timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in self?.tickStageElapsed() } + stageElapsedTimer = timer + RunLoop.main.add(timer, forMode: .common) + } + + private func stopStageElapsedTimer() { + pipelinePollGeneration = UUID() + stageElapsedTimer?.invalidate() + stageElapsedTimer = nil + currentStageStartedAt = nil + isPollingPipelineArchive = false + } + + private func tickStageElapsed() { + guard isBuilding, !isCancellingBuild, let started = currentStageStartedAt, + let index = buildStages.firstIndex(where: { $0.id == stageIndex && $0.isRunning }) else { return } + let now = Date() + let elapsed = max(0, now.timeIntervalSince(started)) + buildStages[index].elapsedSeconds = elapsed + + if currentStageName == PortingProgress.pipelineArchiveStageName, + let cacheURL = currentBuildCacheURL, + !isPollingPipelineArchive { + isPollingPipelineArchive = true + let logURL = cacheURL.appendingPathComponent("logs/pso_offline_mac.log") + let generation = pipelinePollGeneration + let architectures = buildStages[index].step.split(separator: " ").first? + .split(separator: ",").map { $0.hasPrefix("applegpu_") ? String($0) : "applegpu_" + $0 } ?? [] + DispatchQueue.global(qos: .utility).async { [weak self] in + let event = PipelineArchiveProgress().poll(logURL: logURL, architectures: architectures, + stageStartedAt: started, now: Date()) + DispatchQueue.main.async { + guard let self, self.pipelinePollGeneration == generation else { return } + self.isPollingPipelineArchive = false + if let event, self.isBuilding, !self.isCancellingBuild, self.currentStageName == PortingProgress.pipelineArchiveStageName { + self.handleBuildEvent(event) + } + } + } + } + } + private func finishBuild(_ result: BuildResult, gameID: UUID, finalApp: URL, tempDir: URL) { + stopStageElapsedTimer() + currentBuildCacheURL = nil portingProgress?.finish(success: result.success) isBuilding = false isCancellingBuild = false @@ -2745,7 +2826,7 @@ public final class AppViewModel: ObservableObject { } else { let reason = PortingFailureClassifier.classify( log: buildLogTail(result.logURL), - keysPath: keyStore.keyURL.path, + keysPath: SourceSelection.requiresKeys(game.sources) ? keyStore.keyURL.path : "", sourcePaths: game.sources.map(\.url.path) ) let message = reason.what(game: game.name) @@ -2779,6 +2860,7 @@ public final class AppViewModel: ObservableObject { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } if result.success && saved { + if result.heldApp == nil { appIconBuildLogs[gameID] = result.logURL } loadLibrary() syncPendingAppIcons() } diff --git a/Sources/NXLibrary/Views/AddGame/AddGameStepThreeView.swift b/Sources/NXLibrary/Views/AddGame/AddGameStepThreeView.swift index f9aaf73..63b5292 100644 --- a/Sources/NXLibrary/Views/AddGame/AddGameStepThreeView.swift +++ b/Sources/NXLibrary/Views/AddGame/AddGameStepThreeView.swift @@ -130,7 +130,7 @@ struct AddGameStepThreeView: View { } Spacer(minLength: canvas.u(12)) if let elapsed = stage.elapsedSeconds { - Text(elapsedText(elapsed)) + Text(PortingETA.elapsed(elapsed)) .seleneFont(14, .regular, design: .monospaced) .foregroundColor(SeleneTheme.ink60) } @@ -167,17 +167,6 @@ struct AddGameStepThreeView: View { } } - private func elapsedText(_ seconds: Double) -> String { - let total = Int(seconds.rounded()) - if total < 60 { return "\(total) s" } - return String(format: "%d:%02d", total / 60, total % 60) - } - - private func formatted(_ seconds: Double) -> String { - let total = Int(seconds.rounded()) - return String(format: "%02d:%02d", total / 60, total % 60) - } - // MARK: - Success /// The game whose new app waits for it to quit, if this porting's did. diff --git a/Sources/NXLibrary/Views/Components/SpotlightTileArtwork.swift b/Sources/NXLibrary/Views/Components/SpotlightTileArtwork.swift index 414b7b2..4697cb4 100644 --- a/Sources/NXLibrary/Views/Components/SpotlightTileArtwork.swift +++ b/Sources/NXLibrary/Views/Components/SpotlightTileArtwork.swift @@ -68,12 +68,7 @@ struct SpotlightTileArtwork: View { private func loadIcon() async -> NSImage? { guard let titleID = game.titleId, !titleID.isEmpty else { return nil } - var url = artwork.cachedURL(titleID: titleID, kind: .square) - if url == nil { - url = await artwork.fetch(titleID: titleID, remoteURL: URL(string: "https://api.nlib.cc/nx/\(titleID)/icon"), - kind: .square) - } - guard let url else { return nil } + guard let url = await artwork.automaticIcon(titleID: titleID, name: game.name) else { return nil } return await GameArtLoader.decode(at: url, maxPixelSize: 512) } } diff --git a/Sources/NXLibrary/Views/Components/SwitchBoxArtCover.swift b/Sources/NXLibrary/Views/Components/SwitchBoxArtCover.swift index 1be3b63..3040f9e 100644 --- a/Sources/NXLibrary/Views/Components/SwitchBoxArtCover.swift +++ b/Sources/NXLibrary/Views/Components/SwitchBoxArtCover.swift @@ -109,8 +109,7 @@ struct SwitchBoxArtCover: View { if let own = GameIconProvider.squareArt(for: game) { icon = own } else if let titleId = game.titleId, - let url = await artwork.fetch(titleID: titleId, remoteURL: URL(string: "https://api.nlib.cc/nx/\(titleId)/icon"), - kind: .square) { + let url = await artwork.automaticIcon(titleID: titleId, name: game.name) { icon = await GameArtLoader.decode(at: url, maxPixelSize: 512) } guard !Task.isCancelled, game.titleId != nil else { return } diff --git a/Sources/NXLibrary/Views/GameOptions/ChangeArtworkView.swift b/Sources/NXLibrary/Views/GameOptions/ChangeArtworkView.swift index d33756d..fc8c99e 100644 --- a/Sources/NXLibrary/Views/GameOptions/ChangeArtworkView.swift +++ b/Sources/NXLibrary/Views/GameOptions/ChangeArtworkView.swift @@ -35,6 +35,8 @@ struct ChangeArtworkView: View { /// Everything the online sources have for this game, every slot, looked up once. @State private var online: [ArtworkCandidate]? @State private var pickPreview: NSImage? + @State private var automaticIconPreview: NSImage? + @State private var automaticIconChecked = false @State private var message: String? @State private var keyMonitor: Any? @@ -51,8 +53,9 @@ struct ChangeArtworkView: View { @State private var browseItems: [ArtworkSlot: [Found]] = [:] @State private var browseLoading: Set<ArtworkSlot> = [] @State private var downloading = false - /// After an Icon pick for a game whose app has an icon of its own: show the pick on the app too? + /// Image source and destination are independent; the same destination dialog is available for Automatic. @State private var askAppIcon = false + @State private var editingIconLocation = false @State private var appIconFocus: SeleneConfirmButton = .confirm /// The main grid's focus, restored on leaving a source. @State private var mainFocus = 0 @@ -68,7 +71,7 @@ struct ChangeArtworkView: View { } enum Tile: Equatable { - case automatic, yourPick, art(Found), browse(ArtworkBrowseSource), file + case automatic, yourPick, iconLocation, art(Found), browse(ArtworkBrowseSource), file } private var titleKey: String { game.titleId ?? game.uniqueKey } @@ -84,7 +87,7 @@ struct ChangeArtworkView: View { if browsing != nil { return (browseItems[slot] ?? []).map(Tile.art) } // The sources to browse come right after Automatic (and Your pick), so a search is one move away and never // waits for the found images to load; From file… ends the grid. - return [.automatic] + (hasPick ? [.yourPick] : []) + ArtworkBrowseSource.allCases.map(Tile.browse) + return [.automatic] + (hasPick ? [.yourPick] : []) + (slot == .icon ? [.iconLocation] : []) + ArtworkBrowseSource.allCases.map(Tile.browse) + (found[slot] ?? []).map(Tile.art) + [.file] } @@ -165,11 +168,12 @@ struct ChangeArtworkView: View { } } .task(id: "\(slot.rawValue)|\(picks.revision)") { await loadPickPreview() } + .task(id: "icon|\(slot.rawValue)|\(picks.revision)") { await loadAutomaticIcon() } .overlay { if askAppIcon { ZStack { SeleneTheme.scrim45.ignoresSafeArea() - SeleneConfirmDialog(title: "Use it for the app too?", + SeleneConfirmDialog(title: "Where should the icon appear?", message: appIconMessage, confirmTitle: "Use on the Mac", cancelTitle: "Only here", @@ -185,7 +189,7 @@ struct ChangeArtworkView: View { private var appIconMessage: String { let running = RunningGameMonitor.runningApplication(for: game) != nil - return "\(game.name) shows it in Finder and the Dock, and keeps it after a rebuild. Automatic brings back the game's own icon." + return "Only here changes the image inside NX Library. Use on the Mac also shows it in Finder and the Dock. Automatic uses the game's own icon, then an online image if available. Changing the image keeps this location choice." + (running ? " It changes when the game quits." : " A large game takes about a minute.") } @@ -195,15 +199,12 @@ struct ChangeArtworkView: View { viewModel.setUsesPickedAppIcon(use, for: game) } - /// An Icon pick changes the game app's icon too: at once when the app has none of its own, after asking otherwise. + /// Ask for every game, including NUPs, dumps and games not built yet. private func iconPicked() { guard slot == .icon else { return } - if viewModel.shouldAskAboutAppIcon(game) { - appIconFocus = .confirm - askAppIcon = true - } else { - viewModel.appIconPickChanged(for: game, removed: false) - } + editingIconLocation = false + appIconFocus = viewModel.usesIconOnMac(for: game) ? .confirm : .cancel + askAppIcon = true } private var rightHints: [HintItem] { @@ -274,12 +275,19 @@ struct ChangeArtworkView: View { /// On the Icon tab, how the game app's own icon change stands: signing a large app takes about a minute. private var appIconStatus: String? { - guard slot == .icon, let progress = viewModel.appIconProgress[game.id] else { return nil } - switch progress { - case .updating: return "Updating the app icon in Finder and the Dock… about a minute for a large game" - case .updated: return "App icon updated in Finder and the Dock" - case .failed(let reason): return "The app icon was not changed: \(reason)" + guard slot == .icon else { return nil } + let progress = viewModel.appIconProgress[game.id] + if let progress { + switch progress { + case .searching, .updating, .failed: return progress.message + default: break + } + } + if !hasPick { + if !automaticIconChecked { return AppIconProgress.searching.message } + if automaticIconPreview == nil { return AppIconProgress.unavailable.message } } + return progress == .unavailable ? nil : progress?.message } private func browseStatus(_ source: ArtworkBrowseSource) -> String { @@ -390,7 +398,10 @@ struct ChangeArtworkView: View { private func preview(_ tile: Tile, size: CGSize) -> some View { switch tile { case .automatic: - glyphFace(.restore) + if slot == .icon, let automaticIconPreview { image(automaticIconPreview) } + else { glyphFace(.restore) } + case .iconLocation: + glyphFace(.settings) case .file: glyphFace(.folder) case .browse: @@ -423,11 +434,13 @@ struct ChangeArtworkView: View { switch slot { case .cover: return ("Automatic", "GameTDB, or a composed cover") case .background: return ("Automatic", "The game's own art, then Title DB") - case .icon: return ("Automatic", "The game's own icon") + case .icon: return ("Automatic", "The game's own icon, then Title DB") case .logo: return ("Automatic", "The store logo, then Steam") } case .yourPick: return ("Your pick", "Saved on this Mac") + case .iconLocation: + return ("Icon location", viewModel.usesIconOnMac(for: game) ? "Use on the Mac" : "Only here") case .art(let item): return (item.art.candidate.source, item.art.resolution) case .browse(let source): @@ -471,6 +484,16 @@ struct ChangeArtworkView: View { list.sort { $0.order < $1.order } found[slot] = list } + if slot == .icon, !Task.isCancelled, + let titleID = game.titleId, + let url = await AppViewModel.automaticIconArtwork.automaticIcon(titleID: titleID, name: game.name), + AppViewModel.automaticIconArtwork.automaticIconSource(titleID: titleID) == "Switch Icon Showdown", + let data = try? Data(contentsOf: url), let size = ArtworkCandidates.pixelSize(of: data), + let preview = await GameArtLoader.decode(at: url, maxPixelSize: 600), !Task.isCancelled { + let candidate = ArtworkCandidate(slot: .icon, source: "Switch Icon Showdown", url: url) + let art = LoadedArtwork(candidate: candidate, data: data, pixelWidth: size.width, pixelHeight: size.height) + found[slot, default: []].append(Found(order: candidates.count, art: art, preview: preview)) + } // Left for another tab before it finished: search again on return. if Task.isCancelled { found[slot] = nil } } @@ -535,6 +558,18 @@ struct ChangeArtworkView: View { pickPreview = await GameArtLoader.decode(at: url, maxPixelSize: 600) } + private func loadAutomaticIcon() async { + guard slot == .icon else { return } + automaticIconChecked = false + let url = await viewModel.automaticIconURL(for: game) + let preview: NSImage? + if let url { preview = await GameArtLoader.decode(at: url, maxPixelSize: 600) } + else { preview = nil } + guard !Task.isCancelled else { return } + automaticIconPreview = preview + automaticIconChecked = true + } + nonisolated private static func thumbnail(_ data: Data) -> NSImage? { guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil } let options: [CFString: Any] = [ @@ -552,10 +587,15 @@ struct ChangeArtworkView: View { message = nil switch tile { case .automatic: - guard hasPick else { return } - picks.remove(titleID: titleKey, slot: slot) + guard hasPick || slot == .icon else { return } + if hasPick { picks.remove(titleID: titleKey, slot: slot) } applied() if slot == .icon { viewModel.appIconPickChanged(for: game, removed: true) } + case .iconLocation: + editingIconLocation = true + appIconFocus = viewModel.usesIconOnMac(for: game) ? .confirm : .cancel + askAppIcon = true + SoundEffectService.shared.play(.click) case .yourPick: SoundEffectService.shared.play(.click) case .art(let item): @@ -747,7 +787,11 @@ struct ChangeArtworkView: View { SoundEffectService.shared.play(.focus) } case .confirm: answerAppIcon(appIconFocus == .confirm) - case .cancel: answerAppIcon(false) + case .cancel: + askAppIcon = false + // Back dismisses the location question. A newly chosen image still follows the saved scope. + if !editingIconLocation { viewModel.appIconPickChanged(for: game, removed: false) } + SoundEffectService.shared.play(.focus) default: break } return diff --git a/Sources/NXLibraryCore/Models/AppIconProgress.swift b/Sources/NXLibraryCore/Models/AppIconProgress.swift new file mode 100644 index 0000000..4ce6bfa --- /dev/null +++ b/Sources/NXLibraryCore/Models/AppIconProgress.swift @@ -0,0 +1,33 @@ +import Foundation + +/// The icon is prepared separately from the successful game build. Missing artwork is informational. +public enum AppIconProgress: Equatable { + case searching, updating, updated, launcherOnly, original, unavailable + case failed(String) + + public var message: String { + switch self { + case .searching: return "Looking for an icon…" + case .updating: return "Updating the app icon in Finder and the Dock… large games can take a minute" + case .updated: return "App icon updated in Finder and the Dock" + case .launcherOnly: return "Icon used only in NX Library" + case .original: return "Using the game's original app icon" + case .unavailable: return "No icon available. You can choose one." + case .failed(let reason): return "The app icon was not changed: \(reason)" + } + } + + public var buildMessage: String { + if self == .unavailable { return "Game ready. No icon found; add one in Change artwork." } + if case .failed = self { return "Game built. \(message)" } + return "Game ready. \(message)" + } + + /// Only append to an existing finished engine log. Failure to log must not affect the build or Play. + public func appendToBuildLog(_ url: URL) throws { + let handle = try FileHandle(forWritingTo: url) + defer { try? handle.close() } + try handle.seekToEnd() + try handle.write(contentsOf: Data("\n[NX Library · Icon] \(buildMessage)\n".utf8)) + } +} diff --git a/Sources/NXLibraryCore/Models/IconLaunchWait.swift b/Sources/NXLibraryCore/Models/IconLaunchWait.swift new file mode 100644 index 0000000..bc035a4 --- /dev/null +++ b/Sources/NXLibraryCore/Models/IconLaunchWait.swift @@ -0,0 +1,28 @@ +import Foundation + +/// A Play request that waits for the app's icon to finish signing. It belongs to its visible +/// preparation notice: cancelling or replacing that notice revokes the request. +public struct IconLaunchWait { + public private(set) var gameID: UUID? + public private(set) var noticeID: UUID? + + public init() {} + + public mutating func begin(gameID: UUID, noticeID: UUID) { + self.gameID = gameID + self.noticeID = noticeID + } + + public mutating func cancel() { + gameID = nil + noticeID = nil + } + + public mutating func finished(gameID: UUID, rerun: Bool, succeeded: Bool, + visibleNoticeID: UUID?) -> UUID? { + guard self.gameID == gameID, !rerun else { return nil } + let mayLaunch = succeeded && visibleNoticeID == noticeID + cancel() + return mayLaunch ? gameID : nil + } +} diff --git a/Sources/NXLibraryCore/Models/PortedGame.swift b/Sources/NXLibraryCore/Models/PortedGame.swift index 589b144..f0f8cfa 100644 --- a/Sources/NXLibraryCore/Models/PortedGame.swift +++ b/Sources/NXLibraryCore/Models/PortedGame.swift @@ -28,6 +28,9 @@ public struct PortedGame: Identifiable, Codable, Equatable { /// The player chose to show NX Library's Icon pick as the game app's icon in Finder and the Dock /// (`AppIconWriter`); written again after every build. public var usesPickedAppIcon = false + /// Explicit scope chosen in Change artwork: false means only in NX Library, even without an original app icon. + /// Nil preserves the behavior of libraries saved before 0.1.4.2 until the player makes a choice. + public var appIconOnMac: Bool? = nil /// The app's icon still has to be written or put back: the game was running. Done when it quits. public var appIconPending = false @@ -77,9 +80,27 @@ public struct PortedGame: Identifiable, Codable, Equatable { portingFailureReason = try c.decodeIfPresent(PortingFailureReason.self, forKey: .portingFailureReason) pendingInstall = try c.decodeIfPresent(PendingInstall.self, forKey: .pendingInstall) usesPickedAppIcon = try c.decodeIfPresent(Bool.self, forKey: .usesPickedAppIcon) ?? false + appIconOnMac = try c.decodeIfPresent(Bool.self, forKey: .appIconOnMac) appIconPending = try c.decodeIfPresent(Bool.self, forKey: .appIconPending) ?? false } + /// Shared by reconciliation and the icon writer, so an explicit launcher-only choice survives scans and builds. + public func wantsPickedAppIcon(hasPick: Bool, hasOriginalIcon: Bool) -> Bool { + hasPick && (appIconOnMac ?? (usesPickedAppIcon || !hasOriginalIcon)) + } + + /// Filling a genuinely missing app icon is allowed by default, but never after an explicit Only here choice. + public func wantsAutomaticAppIcon(hasPick: Bool, hasOriginalIcon: Bool) -> Bool { + !hasPick && !hasOriginalIcon && appIconOnMac != false + } + + /// Selecting the image source does not change where the image is shown. + public mutating func useAutomaticAppIcon() { + if appIconOnMac == nil && usesPickedAppIcon { appIconOnMac = true } + usesPickedAppIcon = false + appIconPending = true + } + /// The bundle id the built app runs as: the stored one, else nxnative's default /// `org.nxnative.<title id in lowercase>`. Nil when neither is known; an empty id never matches. public var effectiveBundleId: String? { diff --git a/Sources/NXLibraryCore/Models/PortingFailureReason.swift b/Sources/NXLibraryCore/Models/PortingFailureReason.swift index c61b8d5..f4a82ab 100644 --- a/Sources/NXLibraryCore/Models/PortingFailureReason.swift +++ b/Sources/NXLibraryCore/Models/PortingFailureReason.swift @@ -189,6 +189,7 @@ public enum PortingFailureClassifier { ("games.noTitleID", .noTitleID, "no (NSP with a readable title ID|game package with a readable content meta) in ", "no game package with a readable content meta in "), ("disk.low", .diskFull, "less than [0-9]+ GB free on the volume of", "GB free on the volume of"), + ("disk.app", .diskFull, #"the app needs [0-9.]+ GB on the volume of"#, "the app needs "), ("disk.full", .diskFull, "No space left on device", nil), ("icloud.detritus", .cloudFolder, "resource fork, Finder information, or similar detritus not allowed", nil), ] @@ -247,8 +248,16 @@ public enum PortingFailureClassifier { options: .regularExpression) else { return nil } return String(message[range].dropFirst(prefix.count)) case .diskFull: - guard let r = log.range(of: "less than [0-9]+ GB free", options: .regularExpression) else { return nil } - return log[r].split(separator: " ").dropFirst(2).first.map(String.init) + if let r = log.range(of: "less than [0-9]+ GB free", options: .regularExpression) { + return log[r].split(separator: " ").dropFirst(2).first.map(String.init) + } + if let r = log.range(of: #"the app needs [0-9.]+ GB on the volume of"#, options: .regularExpression) { + let parts = log[r].split(separator: " ") + if parts.count >= 4, let num = Double(parts[3]) { + return String(Int(ceil(num + 1.0))) + } + } + return nil case .noTitleID: guard let r = log.range(of: #"no (NSP with a readable title ID|game package with a readable content meta) in [^"\n]+"#, options: .regularExpression), diff --git a/Sources/NXLibraryCore/Models/PortingPresentation.swift b/Sources/NXLibraryCore/Models/PortingPresentation.swift index aaab074..ebd7ec2 100644 --- a/Sources/NXLibraryCore/Models/PortingPresentation.swift +++ b/Sources/NXLibraryCore/Models/PortingPresentation.swift @@ -17,19 +17,42 @@ public enum PortingETA { switch progress.phase { case .shaders: guard let eta = progress.shaderETA else { return nil } - if let last = lastFinishingSeconds { return "About \(minutes(eta + last)) min left" } - return "Shaders: about \(minutes(eta)) min" + if let last = lastFinishingSeconds { return "About \(duration(eta + last)) left" } + return "Shaders: about \(duration(eta))" case .finishing: + if let eta = progress.finishingETA { return "Pipelines: about \(duration(eta))" } guard let last = lastFinishingSeconds else { return "Finishing up" } - return "About \(minutes(max(0, last - progress.finishingElapsed))) min left" + return "About \(duration(max(0, last - progress.finishingElapsed))) left" case .preparing, .done, .failed: return nil } } + /// Stage time in the Porting list: seconds under a minute, m:ss under an hour, h:mm:ss from an hour on. + public static func elapsed(_ seconds: Double) -> String { + let total = max(0, Int(seconds.rounded())) + if total < 60 { return "\(total) s" } + let hours = total / 3600 + let minutes = (total % 3600) / 60 + let secs = total % 60 + if hours > 0 { + return String(format: "%d:%02d:%02d", hours, minutes, secs) + } + return String(format: "%d:%02d", minutes, secs) + } + static func minutes(_ seconds: Double) -> Int { max(1, Int((seconds / 60).rounded())) } + + static func duration(_ seconds: Double) -> String { + let total = minutes(seconds) + let hours = total / 60 + let mins = total % 60 + if hours == 0 { return "\(mins) min" } + if mins == 0 { return "\(hours) hr" } + return "\(hours) hr \(mins) min" + } } /// What a game's art tile shows. diff --git a/Sources/NXLibraryCore/Models/PortingProgress.swift b/Sources/NXLibraryCore/Models/PortingProgress.swift index e961e83..61666dd 100644 --- a/Sources/NXLibraryCore/Models/PortingProgress.swift +++ b/Sources/NXLibraryCore/Models/PortingProgress.swift @@ -15,6 +15,7 @@ public struct PortingProgress: Equatable { } public static let shaderStageName = "Mac shaders" + public static let pipelineArchiveStageName = "Pipeline archive" public static let shaderShare = 0.9 /// The stages after the shaders when the engine has not counted them yet. public static let expectedFinishingStages = 3 @@ -31,6 +32,8 @@ public struct PortingProgress: Equatable { public private(set) var stageName = "" public private(set) var stepName = "" public private(set) var shaderETA: Double? + /// ETA during finishing stages (e.g. Pipeline archive). + public private(set) var finishingETA: Double? /// Cache maintenance has no count or reliable ETA; show its activity instead. public private(set) var isUpdatingShaderCache = false /// Seconds the stages after the shaders took so far. @@ -46,6 +49,7 @@ public struct PortingProgress: Equatable { switch event { case .stage(let k, let n, let name, let stepTitle): isUpdatingShaderCache = false + finishingETA = nil step = k count = n stageName = name @@ -59,12 +63,20 @@ public struct PortingProgress: Equatable { case .step(let title): isUpdatingShaderCache = false stepName = title - case .progress(let fraction, let done, _, _, let eta): + case .progress(let fraction, let done, _, _, let eta, _): + if phase == .finishing, stageName == Self.pipelineArchiveStageName, done != nil { + finishingETA = eta + let stages = max(Self.expectedFinishingStages, count - (shaderStep ?? count)) + let clamped = min(max(fraction, 0), 0.99) + raise(to: Self.shaderShare + (1 - Self.shaderShare) * (Double(finishingEnded) + clamped) / Double(stages)) + return + } guard countsAsShaders(hasCount: done != nil) else { return } phase = .shaders raise(to: Self.shaderShare * min(max(fraction, 0), 1)) shaderETA = eta case .stageEnd(let name, let ok, let elapsed, _, _): + finishingETA = nil if name == Self.shaderStageName { raise(to: Self.shaderShare) phase = .finishing diff --git a/Sources/NXLibraryCore/Models/ToolchainRequirements.swift b/Sources/NXLibraryCore/Models/ToolchainRequirements.swift index 5ff6c58..c935cdc 100644 --- a/Sources/NXLibraryCore/Models/ToolchainRequirements.swift +++ b/Sources/NXLibraryCore/Models/ToolchainRequirements.swift @@ -49,7 +49,8 @@ public struct ToolchainRequirements: Equatable { } public enum VersionNumber { - /// Dotted numbers compared part by part; a missing part counts as 0. + /// Dotted numbers compared part by part; a missing part counts as 0. Text before the first digit is skipped, + /// so `xcodebuild -version`'s "Xcode 27.2" reads as 27.2. public static func isOlder(_ a: String, than b: String) -> Bool { let left = parts(a), right = parts(b) for i in 0..<max(left.count, right.count) { @@ -61,6 +62,6 @@ public enum VersionNumber { } private static func parts(_ version: String) -> [Int] { - version.split(separator: ".").map { Int($0.prefix { $0.isNumber }) ?? 0 } + version.drop { !$0.isNumber }.split(separator: ".").map { Int($0.prefix { $0.isNumber }) ?? 0 } } } diff --git a/Sources/NXLibraryCore/Services/AppIconWriter.swift b/Sources/NXLibraryCore/Services/AppIconWriter.swift index 01c303b..98203e0 100644 --- a/Sources/NXLibraryCore/Services/AppIconWriter.swift +++ b/Sources/NXLibraryCore/Services/AppIconWriter.swift @@ -70,6 +70,13 @@ public struct AppIconWriter { && fileManager.fileExists(atPath: backups.appendingPathComponent(key).appendingPathComponent("original.icns").path) } + /// The game's image for Automatic previews, including the backup while a personal image is applied. + public func originalIconURL(_ app: URL, key: String) -> URL? { + if hasOwnIcon(app), let info = try? plist(app) { return iconFile(app, info: info) } + let saved = backups.appendingPathComponent(key).appendingPathComponent("original.icns") + return showsWrittenIcon(app) && fileManager.fileExists(atPath: saved.path) ? saved : nil + } + /// The app shows an icon NX Library wrote. public func showsWrittenIcon(_ app: URL) -> Bool { (try? plist(app))?[Self.markKey] as? Bool == true @@ -77,6 +84,19 @@ public struct AppIconWriter { // MARK: - Writing + /// Reconcile a resolved image, including the absence of a replacement after removing a personal pick. + /// Missing artwork restores the original/generic icon instead of leaving the old pick behind. + @discardableResult + public func update(image: Data?, app: URL, key: String) throws -> Bool { + if let image { + try apply(image: image, to: app, key: key) + return true + } + guard showsWrittenIcon(app) else { return false } + try restore(app, key: key) + return true + } + /// Writes `image` (any format ImageIO reads; square art) as the app's icon. Slow for a large game: signing /// hashes the whole bundle, so call it off the main thread. public func apply(image: Data, to app: URL, key: String) throws { diff --git a/Sources/NXLibraryCore/Services/ArtworkPicker.swift b/Sources/NXLibraryCore/Services/ArtworkPicker.swift index 7971aeb..c42496e 100644 --- a/Sources/NXLibraryCore/Services/ArtworkPicker.swift +++ b/Sources/NXLibraryCore/Services/ArtworkPicker.swift @@ -140,6 +140,13 @@ public struct LoadedArtwork: Equatable, Sendable { public let pixelWidth: Int public let pixelHeight: Int + public init(candidate: ArtworkCandidate, data: Data, pixelWidth: Int, pixelHeight: Int) { + self.candidate = candidate + self.data = data + self.pixelWidth = pixelWidth + self.pixelHeight = pixelHeight + } + /// The size of the image a choice saves: the full file's when the source states it, not the preview's. public var resolution: String { if let width = candidate.width, let height = candidate.height, width > 0, height > 0 { return "\(width) × \(height)" } diff --git a/Sources/NXLibraryCore/Services/ArtworkStore.swift b/Sources/NXLibraryCore/Services/ArtworkStore.swift index 23df1d9..a8fd574 100644 --- a/Sources/NXLibraryCore/Services/ArtworkStore.swift +++ b/Sources/NXLibraryCore/Services/ArtworkStore.swift @@ -42,6 +42,7 @@ public struct URLSessionArtworkClient: ArtworkHTTPClient { public final class ArtworkStore: ObservableObject { public let cacheDirectory: URL private let client: ArtworkHTTPClient + private let automaticIcons: AutomaticIconResolver private let fileManager: FileManager public init(cacheDirectory: URL = ArtworkStore.defaultDirectory(), @@ -49,6 +50,7 @@ public final class ArtworkStore: ObservableObject { fileManager: FileManager = .default) { self.cacheDirectory = cacheDirectory self.client = client + self.automaticIcons = AutomaticIconResolver(directory: cacheDirectory, client: client) self.fileManager = fileManager } @@ -71,6 +73,17 @@ public final class ArtworkStore: ObservableObject { return nil } + /// Automatic icons never replace the player's artwork pick. Callers keep picks and the game's own icon first. + public func automaticIcon(titleID: String, name: String) async -> URL? { + if let cached = cachedURL(titleID: titleID, kind: .square) { return cached } + return await automaticIcons.resolve(titleID: titleID, name: name) + } + + public func automaticIconSource(titleID: String) -> String? { + try? String(contentsOf: cacheDirectory.appendingPathComponent(Self.folderName(for: titleID.uppercased())) + .appendingPathComponent("square-source.txt"), encoding: .utf8) + } + public func fetch(titleID: String, remoteURL: URL?, kind: ArtworkKind) async -> URL? { if let cached = cachedURL(titleID: titleID, kind: kind) { return cached } diff --git a/Sources/NXLibraryCore/Services/AutomaticIconResolver.swift b/Sources/NXLibraryCore/Services/AutomaticIconResolver.swift new file mode 100644 index 0000000..8b9dad1 --- /dev/null +++ b/Sources/NXLibraryCore/Services/AutomaticIconResolver.swift @@ -0,0 +1,165 @@ +import Foundation +import CryptoKit +import ImageIO + +/// Independently implemented from the site's HTML, without importing the GPL reference implementation. +/// Automatic matching is intentionally exact after punctuation/diacritic normalization: sequels and editions +/// are distinct games. A Switch 2 Title ID does not by itself identify a Switch 2 Edition upgrade. +enum SwitchIconShowdown { + struct Result: Equatable { let id: Int; let title: String } + + static func normalized(_ value: String) -> String { + value.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: Locale(identifier: "en_US_POSIX")) + .components(separatedBy: CharacterSet.alphanumerics.inverted).filter { !$0.isEmpty }.joined(separator: " ") + } + + static func results(in html: String) -> [Result] { + guard let regex = try? NSRegularExpression( + pattern: #"<tr\b[^>]*>\s*<td\b[^>]*>\s*<a\b[^>]*href=['"](?:\./)?detail\.php\?id=(\d{1,9})['"][^>]*>.*?</a>\s*</td>\s*<td\b[^>]*>\s*<center>([^<]+)</center>"#, + options: [.caseInsensitive, .dotMatchesLineSeparators]) else { return [] } + return regex.matches(in: html, range: NSRange(html.startIndex..., in: html)).prefix(100).compactMap { m in + guard let r = Range(m.range(at: 1), in: html), let t = Range(m.range(at: 2), in: html), + let id = Int(html[r]), id > 0 else { return nil } + return Result(id: id, title: LaunchBoxArt.unescape(String(html[t]))) + } + } + + /// Apply rules for all agents / NXLibrary, with the longest matching path winning and Allow winning ties. + /// An unavailable or unrecognizable robots response does not authorize automated image retrieval. + static func allowsIcons(in robots: String, path: String) -> Bool { + typealias Rule = (field: String, path: String) + var groups: [(agents: [String], rules: [Rule])] = [] + var agents: [String] = [], rules: [Rule] = [] + for raw in robots.components(separatedBy: .newlines) { + let line = raw.components(separatedBy: "#")[0] + let pair = line.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) + .map { $0.trimmingCharacters(in: .whitespaces) } + guard pair.count == 2 else { continue } + let field = pair[0].lowercased(), value = pair[1] + if field == "user-agent" { + if !rules.isEmpty { groups.append((agents, rules)); agents = []; rules = [] } + agents.append(value.lowercased()) + } else if field == "allow" || field == "disallow" { + rules.append((field, value)) + } + } + if !agents.isEmpty { groups.append((agents, rules)) } + guard !groups.isEmpty else { return false } + func specificity(_ names: [String]) -> Int { + names.map { $0 == "*" ? 0 : (!$0.isEmpty && "nxlibrary".hasPrefix($0) ? $0.count : -1) }.max() ?? -1 + } + let agentMatch = groups.map { specificity($0.agents) }.max() ?? -1 + var best = -1, allowed = true + for group in groups where agentMatch >= 0 && specificity(group.agents) == agentMatch { + for rule in group.rules where !rule.path.isEmpty { + let anchored = rule.path.hasSuffix("$") + let text = anchored ? String(rule.path.dropLast()) : rule.path + let pattern = "^" + text.components(separatedBy: "*") + .map { NSRegularExpression.escapedPattern(for: $0) }.joined(separator: ".*") + (anchored ? "$" : "") + guard path.range(of: pattern, options: .regularExpression) != nil else { continue } + let length = text.filter { $0 != "*" }.count + if length > best || length == best && rule.field == "allow" { + best = length; allowed = rule.field == "allow" + } + } + } + return allowed + } + + static func match(name: String, titleID: String, results: [Result]) -> Result? { + let query = normalized(name) + guard !query.isEmpty else { return nil } + let edition = " nintendo switch 2 edition" + if titleID.hasPrefix("04"), !query.hasSuffix(edition), + results.contains(where: { normalized($0.title) == query + edition }) { return nil } + let exact = results.filter { normalized($0.title) == query } + guard Set(exact.map(\.id)).count == 1 else { return nil } + return exact.first + } +} + +/// Title DB first, then one unambiguous Switch Icon Showdown result. Images stay in the artwork cache, +/// never in ArtworkPicks. Misses are cached for 15 minutes; successful images work offline. +/// Concurrent requests for the same game share a lookup; network failures cannot stop a build. +actor AutomaticIconResolver { + private let directory: URL + private let client: ArtworkHTTPClient + private var pending: [String: Task<URL?, Never>] = [:] + + init(directory: URL, client: ArtworkHTTPClient) { self.directory = directory; self.client = client } + + func resolve(titleID: String, name: String) async -> URL? { + guard titleID.count == 16, titleID.allSatisfy(\.isHexDigit), !SwitchIconShowdown.normalized(name).isEmpty else { return nil } + let title = titleID.uppercased() + let folder = directory.appendingPathComponent(title) + let image = folder.appendingPathComponent("square.png") + if let data = try? Data(contentsOf: image), CGImageSourceCreateWithData(data as CFData, nil) != nil { return image } + let key = title + ":" + SwitchIconShowdown.normalized(name) + if let work = pending[key] { return await work.value } + let hash = SHA256.hash(data: Data(key.utf8)).map { String(format: "%02x", $0) }.joined() + let miss = folder.appendingPathComponent("square-miss.txt") + if (try? String(contentsOf: miss, encoding: .utf8)) == hash, + let attrs = try? FileManager.default.attributesOfItem(atPath: miss.path), + let date = attrs[.modificationDate] as? Date, Date().timeIntervalSince(date) < 900 { return nil } + let work = Task { [client] in + await Self.lookup(titleID: title, name: name, folder: folder, image: image, miss: miss, missKey: hash, client: client) + } + pending[key] = work + let result = await work.value + pending[key] = nil + return result + } + + private static func lookup(titleID: String, name: String, folder: URL, image: URL, miss: URL, + missKey: String, client: ArtworkHTTPClient) async -> URL? { + let primary = URL(string: "https://api.nlib.cc/nx/\(titleID)/icon")! + if let data = await get(primary, client: client), let png = validImage(data), + store(png, image: image, source: "Title DB", folder: folder) { return image } + var query = URLComponents(string: "https://www.switchiconshowdown.com/search.php")! + query.queryItems = [URLQueryItem(name: "query", value: name)] + let robotsURL = URL(string: "https://www.switchiconshowdown.com/robots.txt")! + if let rulesData = await get(robotsURL, client: client, limit: 256 * 1024), + let rules = String(data: rulesData, encoding: .utf8), + let search = query.url, SwitchIconShowdown.allowsIcons(in: rules, path: search.path), + let data = await get(search, client: client, limit: 2 * 1024 * 1024), + let html = String(data: data, encoding: .utf8), + let chosen = SwitchIconShowdown.match(name: name, titleID: titleID, results: SwitchIconShowdown.results(in: html)), + let url = URL(string: "https://www.switchiconshowdown.com/images/\(chosen.id).png"), + SwitchIconShowdown.allowsIcons(in: rules, path: url.path), + let bytes = await get(url, client: client), let png = validImage(bytes), + store(png, image: image, source: "Switch Icon Showdown", folder: folder) { return image } + try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) + try? Data(missKey.utf8).write(to: miss, options: .atomic) + return nil + } + + private static func get(_ url: URL, client: ArtworkHTTPClient, limit: Int = 8 * 1024 * 1024) async -> Data? { + var request = URLRequest(url: url) + request.timeoutInterval = 8 + request.setValue("NXLibrary (macOS; automatic icon lookup)", forHTTPHeaderField: "User-Agent") + guard let (data, response) = try? await client.data(for: request), response.statusCode == 200, + !data.isEmpty, data.count <= limit, let final = response.url, + final.scheme == "https", final.user == nil, final.password == nil, + final.port == nil || final.port == 443, + ["api.nlib.cc", "www.switchiconshowdown.com", "switchiconshowdown.com"].contains(final.host?.lowercased() ?? "") else { return nil } + return data + } + + private static func validImage(_ data: Data) -> Data? { + guard let source = CGImageSourceCreateWithData(data as CFData, nil), + let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any], + let width = properties[kCGImagePropertyPixelWidth] as? Int, + let height = properties[kCGImagePropertyPixelHeight] as? Int, + width == height, width >= 32, width <= 8192 else { return nil } + return ArtworkStore.downsample(data, maxPixelSize: 512, png: true) + } + + private static func store(_ data: Data, image: URL, source: String, folder: URL) -> Bool { + do { + try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) + try data.write(to: image, options: .atomic) + try? Data(source.utf8).write(to: folder.appendingPathComponent("square-source.txt"), options: .atomic) + return true + } catch { return false } + } +} diff --git a/Sources/NXLibraryCore/Services/BuildEvent.swift b/Sources/NXLibraryCore/Services/BuildEvent.swift index 4fbfbb9..a8d2f93 100644 --- a/Sources/NXLibraryCore/Services/BuildEvent.swift +++ b/Sources/NXLibraryCore/Services/BuildEvent.swift @@ -1,7 +1,7 @@ public enum BuildEvent: Equatable { case stage(k: Int, n: Int, name: String, step: String?) case step(String) - case progress(fraction: Double, done: Double?, total: Double?, unit: String?, eta: Double?) + case progress(fraction: Double, done: Double?, total: Double?, unit: String?, eta: Double?, elapsed: Double? = nil) /// `warn`: the stage finished without what it makes, though `ok` (a pipeline archive that failed, so the app is /// packed without one). Engines before `warn` existed never send it. case stageEnd(name: String, ok: Bool, elapsed: Double?, summary: String?, warn: Bool = false) diff --git a/Sources/NXLibraryCore/Services/BuildEventParser.swift b/Sources/NXLibraryCore/Services/BuildEventParser.swift index 697700f..b5ec99f 100644 --- a/Sources/NXLibraryCore/Services/BuildEventParser.swift +++ b/Sources/NXLibraryCore/Services/BuildEventParser.swift @@ -33,7 +33,7 @@ public enum BuildEventParser { return wire.step.map { .step($0) } case "progress": return .progress(fraction: wire.frac ?? 0, done: wire.done, - total: wire.total, unit: wire.unit, eta: wire.eta) + total: wire.total, unit: wire.unit, eta: wire.eta, elapsed: wire.elapsed) case "stage_end": return .stageEnd(name: wire.name ?? "", ok: wire.ok ?? true, elapsed: wire.elapsed, summary: wire.summary, warn: wire.warn ?? false) diff --git a/Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift b/Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift new file mode 100644 index 0000000..85b6948 --- /dev/null +++ b/Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift @@ -0,0 +1,56 @@ +import Foundation + +/// Reads only the engine's current pipeline log. A batch counts when the engine reports it ready; +/// compiler temporaries and old cache keys do not prove that this build has completed a pipeline. +/// Unknown log formats leave the stage indeterminate. No upstream files are changed. +public struct PipelineArchiveProgress: Sendable { + public init() {} + + public func poll(logURL: URL, architectures: [String], stageStartedAt: Date, + now: Date = Date()) -> BuildEvent? { + guard !architectures.isEmpty, + let attrs = try? FileManager.default.attributesOfItem(atPath: logURL.path), + let modified = attrs[.modificationDate] as? Date, modified >= stageStartedAt, + let size = (attrs[.size] as? NSNumber)?.intValue, size <= 4 * 1024 * 1024, + let log = try? String(contentsOf: logURL, encoding: .utf8) else { return nil } + var totals: [String: Int] = [:] + var batches: [String: Int] = [:] + var ready = Set<String>() + var compiled = Set<String>() + // Python's parallel compute workers may join two print calls on one line. Keep both + // records, but do not consume the final physical line until the writer finishes it. + let records = log.components(separatedBy: "\n").dropLast().flatMap { line in + line.components(separatedBy: "pso_offline:").dropFirst().map { "pso_offline:" + $0 } + } + for line in records { + let words = line.split(whereSeparator: \.isWhitespace).map(String.init) + guard words.count >= 4, words[0] == "pso_offline:" else { continue } + let arch = String(words[1].dropLast()) + guard architectures.contains(arch) else { continue } + if words[2] == "metal-tt,", words.count >= 7, + let bases = Int(words[3]), let computes = Int(words[5]), + bases >= 0, computes >= 0, bases <= 10_000_000, computes <= 10_000_000 { + totals[arch] = bases + computes + } else if words[2] == "batch", words.count >= 6 { + let key = arch + ":" + words[3] + if let count = Int(words[4]), count > 0, count <= 10_000_000, + words[5] == "bases," || words[5] == "compute" { + batches[key] = count + } else if words[4] == "ready", words[5] == "(compiled)" || words[5] == "(reused)" { + ready.insert(key) + if words[5] == "(compiled)" { compiled.insert(key) } + } + } + } + guard let perArchitecture = totals.values.first, perArchitecture > 0 else { return nil } + // The same plan is compiled once for each requested GPU. Include GPUs not started yet. + let total = perArchitecture * Set(architectures).count + let done = min(total, ready.reduce(0) { $0 + (batches[$1] ?? 0) }) + let newlyCompiled = compiled.reduce(0) { $0 + (batches[$1] ?? 0) } + let elapsed = max(0, now.timeIntervalSince(stageStartedAt)) + let eta = newlyCompiled > 0 && elapsed >= 3 && done < total + ? Double(total - done) * elapsed / Double(newlyCompiled) : nil + return .progress(fraction: min(0.99, Double(done) / Double(total)), done: Double(done), + total: Double(total), unit: "pipelines", eta: eta) + } +} diff --git a/Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift b/Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift new file mode 100644 index 0000000..c6e2289 --- /dev/null +++ b/Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift @@ -0,0 +1,97 @@ +import XCTest +@testable import NXLibraryCore + +final class AppIconPreferenceTests: XCTestCase { + private func game() -> PortedGame { + PortedGame(name: "Game", url: URL(fileURLWithPath: "/tmp/Game.app"), platform: .macApp, + sizeBytes: 1, modificationDate: Date(timeIntervalSince1970: 0)) + } + + func testAutomaticPreservesBothExplicitDestinationsAcrossRebuildAndRelaunch() throws { + for onMac in [true, false] { + var g = game() + g.appIconOnMac = onMac + g.usesPickedAppIcon = onMac + g.useAutomaticAppIcon() + g.recordInstalledBuild(BuildRecord(), toolchain: ToolchainInfo(), at: g.url, version: "2.0", date: Date()) + let restored = try JSONDecoder().decode(PortedGame.self, from: JSONEncoder().encode(g)) + XCTAssertEqual(restored.appIconOnMac, onMac) + XCTAssertEqual(restored.wantsAutomaticAppIcon(hasPick: false, hasOriginalIcon: false), onMac) + XCTAssertFalse(restored.usesPickedAppIcon) + XCTAssertTrue(restored.appIconPending) + } + } + + func testAutomaticRetainsLegacyMacOptInWithoutOverridingExplicitOnlyHere() { + var legacy = game() + legacy.usesPickedAppIcon = true + legacy.useAutomaticAppIcon() + XCTAssertEqual(legacy.appIconOnMac, true) + XCTAssertFalse(legacy.usesPickedAppIcon) + + legacy.appIconOnMac = false + legacy.usesPickedAppIcon = true + legacy.useAutomaticAppIcon() + XCTAssertEqual(legacy.appIconOnMac, false) + } + + func testOnlyHereOverridesAutomaticIconForAnAppWithNoOriginalIcon() { + var g = game() + g.appIconOnMac = false + XCTAssertFalse(g.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: false)) + XCTAssertFalse(g.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: true)) + } + + func testUseOnMacRequiresAnArtworkPickAndSurvivesRebuilds() throws { + var g = game() + g.appIconOnMac = true + g.recordInstalledBuild(BuildRecord(), toolchain: ToolchainInfo(), at: g.url, version: "2.0", date: Date()) + let restored = try JSONDecoder().decode(PortedGame.self, from: JSONEncoder().encode(g)) + XCTAssertTrue(restored.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: false)) + XCTAssertTrue(restored.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: true)) + XCTAssertFalse(restored.wantsPickedAppIcon(hasPick: false, hasOriginalIcon: false)) + } + + func testExplicitOnlyHerePersistsAcrossRelaunchAndOverridesLegacyTrue() throws { + var g = game() + g.usesPickedAppIcon = true + g.appIconOnMac = false + g.recordInstalledBuild(BuildRecord(), toolchain: ToolchainInfo(), at: g.url, version: "2.0", date: Date()) + let restored = try JSONDecoder().decode(PortedGame.self, from: JSONEncoder().encode(g)) + XCTAssertEqual(restored.appIconOnMac, false) + XCTAssertFalse(restored.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: false)) + } + + func testLibraryRescanPreservesOnlyHereRatherThanTakingScannedDefaults() throws { + let dir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: dir) } + let store = LibraryStore(directory: dir) + var saved = game() + saved.appIconOnMac = false + try store.save(LibraryFile(games: [saved])) + let merged = try XCTUnwrap(try store.merge(scanned: [game()]).games.first) + XCTAssertEqual(merged.appIconOnMac, false) + XCTAssertFalse(merged.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: false)) + } + + func testAutomaticFallbackNeverOverridesAPickOriginalIconOrOnlyHere() { + var g = game() + XCTAssertTrue(g.wantsAutomaticAppIcon(hasPick: false, hasOriginalIcon: false)) + XCTAssertFalse(g.wantsAutomaticAppIcon(hasPick: true, hasOriginalIcon: false)) + XCTAssertFalse(g.wantsAutomaticAppIcon(hasPick: false, hasOriginalIcon: true)) + g.appIconOnMac = false + XCTAssertFalse(g.wantsAutomaticAppIcon(hasPick: false, hasOriginalIcon: false)) + } + + func testOlderLibrariesKeepTheirBehaviorUntilThePlayerMakesAChoice() throws { + var json = try XCTUnwrap(JSONSerialization.jsonObject(with: JSONEncoder().encode(game())) as? [String: Any]) + json.removeValue(forKey: "appIconOnMac") + let old = try JSONDecoder().decode(PortedGame.self, from: JSONSerialization.data(withJSONObject: json)) + XCTAssertNil(old.appIconOnMac) + XCTAssertTrue(old.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: false)) + XCTAssertFalse(old.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: true)) + var optedIn = old + optedIn.usesPickedAppIcon = true + XCTAssertTrue(optedIn.wantsPickedAppIcon(hasPick: true, hasOriginalIcon: true)) + } +} diff --git a/Tests/NXLibraryCoreTests/AppIconProgressTests.swift b/Tests/NXLibraryCoreTests/AppIconProgressTests.swift new file mode 100644 index 0000000..cc93df4 --- /dev/null +++ b/Tests/NXLibraryCoreTests/AppIconProgressTests.swift @@ -0,0 +1,17 @@ +import XCTest +@testable import NXLibraryCore + +final class AppIconProgressTests: XCTestCase { + func testMissingIconAddsAnInformationalResultWithoutReplacingBuildOutput() throws { + let folder = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: folder) } + try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) + let log = folder.appendingPathComponent("build.log") + try Data("engine: build completed\n".utf8).write(to: log) + try AppIconProgress.unavailable.appendToBuildLog(log) + let text = try String(contentsOf: log, encoding: .utf8) + XCTAssertTrue(text.hasPrefix("engine: build completed\n")) + XCTAssertTrue(text.contains("Game ready. No icon found")) + XCTAssertFalse(text.lowercased().contains("build failed")) + } +} diff --git a/Tests/NXLibraryCoreTests/AppIconWriterTests.swift b/Tests/NXLibraryCoreTests/AppIconWriterTests.swift index 5827344..b1a983c 100644 --- a/Tests/NXLibraryCoreTests/AppIconWriterTests.swift +++ b/Tests/NXLibraryCoreTests/AppIconWriterTests.swift @@ -7,13 +7,15 @@ final class AppIconWriterTests: XCTestCase { var calls: [[String]] = [] /// Exit status for each codesign call in turn; 0 once the list runs out. var codesignStatus: [Int32] = [] + var iconOutputs: [Data] = [] func run(_ executable: URL, _ arguments: [String], environment: [String: String]?, currentDirectory: URL?) throws -> CommandResult { calls.append([executable.lastPathComponent] + arguments) switch executable.lastPathComponent { case "iconutil": - try Data("icns".utf8).write(to: URL(fileURLWithPath: arguments[4])) + let output = iconOutputs.isEmpty ? Data("icns".utf8) : iconOutputs.removeFirst() + try output.write(to: URL(fileURLWithPath: arguments[4])) return CommandResult(status: 0, stdout: "", stderr: "") case "codesign": let status = codesignStatus.isEmpty ? 0 : codesignStatus.removeFirst() @@ -99,6 +101,30 @@ final class AppIconWriterTests: XCTestCase { XCTAssertEqual(try info(app)["CFBundleIdentifier"] as? String, "org.nxnative.test") } + func testMissingReplacementRemovesThePreviousPickAndKeepsAnIconlessAppUsable() throws { + let app = try makeApp() + try writer.apply(image: png, to: app, key: "game") + XCTAssertTrue(writer.showsWrittenIcon(app)) + XCTAssertTrue(try writer.update(image: nil, app: app, key: "game")) + XCTAssertNil(icon(app)) + XCTAssertNil(try info(app)["CFBundleIconFile"]) + XCTAssertFalse(writer.showsWrittenIcon(app)) + XCTAssertTrue(FileManager.default.fileExists(atPath: app.appendingPathComponent("Contents/MacOS/nxnative").path)) + XCTAssertFalse(try writer.update(image: nil, app: app, key: "game")) + } + + func testAutomaticReadsTheOriginalInsteadOfTheAppliedPick() throws { + let app = try makeApp(icon: "original") + let before = try XCTUnwrap(writer.originalIconURL(app, key: "game")) + XCTAssertEqual(try Data(contentsOf: before), Data("original".utf8)) + try writer.apply(image: png, to: app, key: "game") + let backedUp = try XCTUnwrap(writer.originalIconURL(app, key: "game")) + XCTAssertEqual(try Data(contentsOf: backedUp), Data("original".utf8)) + XCTAssertNotEqual(backedUp, before) + XCTAssertTrue(try writer.update(image: nil, app: app, key: "game")) + XCTAssertEqual(icon(app), "original") + } + func testOwnIconIsKeptAndRestored() throws { let app = try makeApp(icon: "original") XCTAssertTrue(writer.hasOwnIcon(app)) @@ -114,6 +140,33 @@ final class AppIconWriterTests: XCTestCase { XCTAssertTrue(writer.hasOwnIcon(app)) } + func testChangingFromAToBReplacesTheIconAndKeepsTheGamesOriginal() throws { + let app = try makeApp(icon: "game original") + runner.iconOutputs = [Data("icon A".utf8), Data("icon B".utf8)] + try writer.apply(image: png, to: app, key: "0100") + XCTAssertEqual(icon(app), "icon A") + try writer.apply(image: png, to: app, key: "0100") + XCTAssertEqual(icon(app), "icon B") + XCTAssertEqual(try String(contentsOf: root.appendingPathComponent("backups/0100/original.icns")), "game original") + try writer.restore(app, key: "0100") + XCTAssertEqual(icon(app), "game original", "Automatic must not restore A instead of the game's original") + } + + func testAutomaticThenAnotherPickStillRestoresAbsenceForAnIconlessGame() throws { + let app = try makeApp() + runner.iconOutputs = [Data("icon A".utf8), Data("icon B".utf8)] + try writer.apply(image: png, to: app, key: "0400") + XCTAssertEqual(icon(app), "icon A") + try writer.restore(app, key: "0400") + XCTAssertNil(icon(app)) + try writer.apply(image: png, to: app, key: "0400") + XCTAssertEqual(icon(app), "icon B") + try writer.restore(app, key: "0400") + XCTAssertNil(icon(app)) + XCTAssertFalse(writer.showsWrittenIcon(app)) + XCTAssertFalse(writer.hasOriginalIcon(app, key: "0400")) + } + func testRebuiltAppReplacesTheKeptOriginal() throws { _ = try makeApp(icon: "old build") try writer.apply(image: png, to: root.appendingPathComponent("Game.app"), key: "0100") @@ -137,7 +190,7 @@ final class AppIconWriterTests: XCTestCase { XCTAssertEqual(try String(contentsOf: app.appendingPathComponent("Contents/MacOS/nxnative")), "launcher") } - func testOriginalIconDecidesWhetherToAsk() throws { + func testOriginalIconIsRememberedIncludingItsAbsence() throws { let own = try makeApp(icon: "original") XCTAssertTrue(writer.hasOriginalIcon(own, key: "0100")) try writer.apply(image: png, to: own, key: "0100") diff --git a/Tests/NXLibraryCoreTests/AppUpdateTests.swift b/Tests/NXLibraryCoreTests/AppUpdateTests.swift index cf4068f..c2a2fd2 100644 --- a/Tests/NXLibraryCoreTests/AppUpdateTests.swift +++ b/Tests/NXLibraryCoreTests/AppUpdateTests.swift @@ -185,6 +185,16 @@ final class AppUpdateTests: XCTestCase { XCTAssertNil(AppUpdate.neededXcode(r, installedXcode: "27.2.1")) } + /// The app reads the installed Xcode as `xcodebuild -version`'s first line, "Xcode 27.2": the word before + /// the number must not make a current Xcode look older (0.1.4 asked to update Xcode 27.2 to 27.2). + func testRequirementNotesReadXcodebuildsOwnLine() { + let r = AppUpdate.Requirements(minimumXcode: "27.2", engineCommit: nil) + XCTAssertNil(AppUpdate.neededXcode(r, installedXcode: "Xcode 27.2")) + XCTAssertNil(AppUpdate.neededXcode(r, installedXcode: "Xcode 27.3")) + XCTAssertEqual(AppUpdate.neededXcode(r, installedXcode: "Xcode 27.1"), "27.2") + XCTAssertEqual(AppUpdate.requirementNotes(r, installedXcode: "Xcode 27.2", currentEngine: "x"), []) + } + /// scripts/release.sh reads these two values from the sources with sed; a reformat must not break it. func testReleaseScriptCanReadTheRequirements() throws { let root = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() diff --git a/Tests/NXLibraryCoreTests/AutomaticIconTests.swift b/Tests/NXLibraryCoreTests/AutomaticIconTests.swift new file mode 100644 index 0000000..6b47f47 --- /dev/null +++ b/Tests/NXLibraryCoreTests/AutomaticIconTests.swift @@ -0,0 +1,116 @@ +import XCTest +import AppKit +@testable import NXLibraryCore + +final class AutomaticIconTests: XCTestCase { + private let switch1 = "0100F2C0115B6000" + private let switch2 = "0400486024DEC000" + private let html = """ + <tr><td><a href='./detail.php?id=828'><img src='./images/100/828.png'></a></td><td><center>Hollow Knight</center></td></tr> + <tr><td><a href='./detail.php?id=13932'><img></a></td><td><center>Hollow Knight - Nintendo Switch 2 Edition</center></td></tr> + <tr><td><a href='./detail.php?id=13670'><img></a></td><td><center>Hollow Knight: Silksong</center></td></tr> + """ + + func testExactGameWinsRatherThanFirstResultOrASequel() { + let results = SwitchIconShowdown.results(in: html) + XCTAssertEqual(SwitchIconShowdown.match(name: "Hollow Knight", titleID: switch1, results: results)?.id, 828) + XCTAssertEqual(SwitchIconShowdown.match(name: "Hollow Knight - Nintendo Switch 2 Edition", titleID: switch2, results: results)?.id, 13932) + XCTAssertNil(SwitchIconShowdown.match(name: "Hollow Knight", titleID: switch1, + results: [SwitchIconShowdown.Result(id: 1, title: "Hollow Knight"), SwitchIconShowdown.Result(id: 2, title: "Hollow Knight")]), "Duplicate titles with different IDs are ambiguous") + XCTAssertNil(SwitchIconShowdown.match(name: "Hollow Knight", titleID: switch2, results: results), "Switch 2 Title ID alone does not identify the edition") + XCTAssertNil(SwitchIconShowdown.match(name: "Hollow Knight", titleID: switch1, results: Array(results.suffix(1)))) + } + + func testNormalizationAndMalformedResults() { + let text = "<tr><td><a href='./detail.php?id=12'><img></a></td><td><center>Pokémon & Friends</center></td></tr>" + XCTAssertEqual(SwitchIconShowdown.match(name: "Pokemon & Friends", titleID: switch2, results: SwitchIconShowdown.results(in: text))?.id, 12) + XCTAssertTrue(SwitchIconShowdown.results(in: "<script>detail.php?id=12</script>").isEmpty) + XCTAssertNil(SwitchIconShowdown.match(name: "", titleID: switch2, results: SwitchIconShowdown.results(in: text))) + } + + func testSpecificRobotsOptOutOverridesGeneralAllowAndUnknownPolicyIsDenied() { + XCTAssertFalse(SwitchIconShowdown.allowsIcons(in: "<html>unavailable</html>", path: "/images/828.png")) + XCTAssertFalse(SwitchIconShowdown.allowsIcons(in: "User-agent: *\nAllow: /images/\nUser-agent: NXLibrary\nDisallow: /\n", path: "/images/828.png")) + XCTAssertTrue(SwitchIconShowdown.allowsIcons(in: "User-agent: *\nDisallow: /images/\nAllow: /images/828.png\n", path: "/images/828.png")) + } + + private final class Client: ArtworkHTTPClient { + var requests: [URL] = [] + var primaryStatus = 404 + var robots = "User-agent: *\nAllow: /\n" + var searchHTML = "" + var image: Data = Data() + var offline = false + func data(for request: URLRequest) async throws -> (Data, HTTPURLResponse) { + let url = request.url! + requests.append(url) + if offline { throw URLError(.notConnectedToInternet) } + let data: Data, status: Int + if url.host == "api.nlib.cc" { data = image; status = primaryStatus } + else if url.path == "/robots.txt" { data = Data(robots.utf8); status = 200 } + else if url.path == "/search.php" { data = Data(searchHTML.utf8); status = 200 } + else { data = image; status = 200 } + return (data, HTTPURLResponse(url: url, statusCode: status, httpVersion: nil, headerFields: nil)!) + } + } + + private func folder() -> URL { + let f = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + addTeardownBlock { try? FileManager.default.removeItem(at: f) } + return f + } + + private func png() -> Data { + let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: 64, pixelsHigh: 64, bitsPerSample: 8, + samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)! + return rep.representation(using: .png, properties: [:])! + } + + func testPrimaryIconStopsTheFallback() async { + let c = Client(); c.primaryStatus = 200; c.image = png() + let store = ArtworkStore(cacheDirectory: folder(), client: c) + let url = await store.automaticIcon(titleID: switch1, name: "Hollow Knight") + XCTAssertNotNil(url) + XCTAssertEqual(c.requests.map(\.host), ["api.nlib.cc"]) + } + + func testMissingPrimaryUsesMatchedIconAndCachesForOfflineUse() async { + let c = Client(); c.searchHTML = html; c.image = png() + let store = ArtworkStore(cacheDirectory: folder(), client: c) + let first = await store.automaticIcon(titleID: switch1, name: "Hollow Knight") + XCTAssertNotNil(first) + XCTAssertEqual(c.requests.last?.path, "/images/828.png") + c.offline = true + let again = await store.automaticIcon(titleID: switch1, name: "Hollow Knight") + XCTAssertEqual(first, again) + XCTAssertEqual(c.requests.count, 4) + } + + func testAmbiguousAndUnavailableSourcesLeaveNoIconAndDoNotRetryImmediately() async { + let c = Client(); c.searchHTML = html; c.image = png() + let store = ArtworkStore(cacheDirectory: folder(), client: c) + let first = await store.automaticIcon(titleID: switch2, name: "Hollow Knight") + XCTAssertNil(first) + let second = await store.automaticIcon(titleID: switch2, name: "Hollow Knight") + XCTAssertNil(second) + XCTAssertEqual(c.requests.count, 3) + XCTAssertTrue(c.requests.allSatisfy { !$0.path.hasPrefix("/images/") }) + } + + func testSiteOptOutStopsAutomaticImageDownloads() async { + let c = Client(); c.searchHTML = html; c.image = png() + c.robots = "User-agent: *\nDisallow: /images/\n" + let store = ArtworkStore(cacheDirectory: folder(), client: c) + let result = await store.automaticIcon(titleID: switch1, name: "Hollow Knight") + XCTAssertNil(result) + XCTAssertTrue(c.requests.allSatisfy { !$0.path.hasPrefix("/images/") }) + } + + func testInvalidImageIsNotCachedAsAnIcon() async { + let c = Client(); c.primaryStatus = 200; c.searchHTML = html; c.image = Data("not an image".utf8) + let store = ArtworkStore(cacheDirectory: folder(), client: c) + let result = await store.automaticIcon(titleID: switch1, name: "Hollow Knight") + XCTAssertNil(result) + XCTAssertNil(store.cachedURL(titleID: switch1, kind: .square)) + } +} diff --git a/Tests/NXLibraryCoreTests/BuildEventParserTests.swift b/Tests/NXLibraryCoreTests/BuildEventParserTests.swift index ba28e41..3f5b88d 100644 --- a/Tests/NXLibraryCoreTests/BuildEventParserTests.swift +++ b/Tests/NXLibraryCoreTests/BuildEventParserTests.swift @@ -9,7 +9,11 @@ final class BuildEventParserTests: XCTestCase { ) XCTAssertEqual( BuildEventParser.parse(#"{"ev":"progress","frac":0.5,"done":1,"total":2,"unit":"GB","eta":10}"#), - .progress(fraction: 0.5, done: 1, total: 2, unit: "GB", eta: 10) + .progress(fraction: 0.5, done: 1, total: 2, unit: "GB", eta: 10, elapsed: nil) + ) + XCTAssertEqual( + BuildEventParser.parse(#"{"ev":"progress","frac":0.5,"done":1,"total":2,"unit":"GB","eta":10,"elapsed":4.5}"#), + .progress(fraction: 0.5, done: 1, total: 2, unit: "GB", eta: 10, elapsed: 4.5) ) } diff --git a/Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift b/Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift new file mode 100644 index 0000000..55994e0 --- /dev/null +++ b/Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift @@ -0,0 +1,34 @@ +import XCTest +@testable import NXLibraryCore + +final class IconLaunchWaitTests: XCTestCase { + func testOnlyTheRequestedGamesFinalSuccessfulUpdateMayLaunch() { + let game = UUID(), notice = UUID() + var wait = IconLaunchWait() + wait.begin(gameID: game, noticeID: notice) + XCTAssertNil(wait.finished(gameID: UUID(), rerun: false, succeeded: true, visibleNoticeID: notice)) + XCTAssertNil(wait.finished(gameID: game, rerun: true, succeeded: true, visibleNoticeID: notice)) + XCTAssertEqual(wait.finished(gameID: game, rerun: false, succeeded: true, visibleNoticeID: notice), game) + XCTAssertNil(wait.gameID) + XCTAssertNil(wait.finished(gameID: game, rerun: false, succeeded: true, visibleNoticeID: notice)) + } + + func testCancelPreventsLaunchingWhenSigningEventuallyEnds() { + let game = UUID(), notice = UUID() + var wait = IconLaunchWait() + wait.begin(gameID: game, noticeID: notice) + wait.cancel() + XCTAssertNil(wait.finished(gameID: game, rerun: false, succeeded: true, visibleNoticeID: notice)) + } + + func testFailureOrDismissedNoticeDoesNotLaunch() { + let game = UUID(), notice = UUID() + var wait = IconLaunchWait() + wait.begin(gameID: game, noticeID: notice) + XCTAssertNil(wait.finished(gameID: game, rerun: false, succeeded: false, visibleNoticeID: notice)) + XCTAssertNil(wait.gameID) + wait.begin(gameID: game, noticeID: notice) + XCTAssertNil(wait.finished(gameID: game, rerun: false, succeeded: true, visibleNoticeID: UUID())) + XCTAssertNil(wait.gameID) + } +} diff --git a/Tests/NXLibraryCoreTests/NXNativeGameSettingsTests.swift b/Tests/NXLibraryCoreTests/NXNativeGameSettingsTests.swift index 95f32dd..1e06b89 100644 --- a/Tests/NXLibraryCoreTests/NXNativeGameSettingsTests.swift +++ b/Tests/NXLibraryCoreTests/NXNativeGameSettingsTests.swift @@ -150,4 +150,30 @@ final class NXNativeGameSettingsTests: XCTestCase { try s.resetAll() XCTAssertEqual(f.store.values[domain] ?? [:], [:]) } + + func test120FPSChoiceSurvivesOtherOptionsAndLaunchMigration() throws { + let f = try fixture() + let file = f.game.url.appendingPathComponent("Contents/Resources/dm/launcher.json") + var json = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(contentsOf: file)) as? [String: Any]) + var rows = try XCTUnwrap(json["settings"] as? [[String: Any]]) + rows.append(["env": "DM_FPS_TARGET", "kind": "choice", "section": "Game", "label": "Frame rate", + "default": "60", "options": [["30", "30 fps"], ["60", "60 fps"], ["120", "120 fps"]]]) + json["settings"] = rows + try JSONSerialization.data(withJSONObject: json).write(to: file) + + let s = try settings(f) + try s.set("DM_FPS_TARGET", to: "120") + let before = s.runtimeConfig() // Legacy typed defaults represent both 60 and 120 as enable60FPS. + var changed = before + changed.fullscreen = !before.fullscreen + try s.apply(changed, changedFrom: before) + + let reopened = try settings(f) + try reopened.migrate() // The same migration used before launching a saved-choices game. + XCTAssertEqual(reopened.snapshot().value("DM_FPS_TARGET"), "120") + XCTAssertEqual(f.store.values[domain]?["DM_FPS_TARGET"], "120") + let fps = try XCTUnwrap(reopened.snapshot().setting("DM_FPS_TARGET")) + XCTAssertEqual(GameSettingValues.label(fps, "120"), "120 fps") + XCTAssertEqual(GameSettingValues.stepped(fps, from: "60", by: 1), "120") + } } diff --git a/Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift b/Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift new file mode 100644 index 0000000..d881f72 --- /dev/null +++ b/Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift @@ -0,0 +1,89 @@ +import XCTest +@testable import NXLibraryCore + +final class PipelineArchiveProgressTests: XCTestCase { + private let start = Date(timeIntervalSince1970: 1000) + private let arch = "applegpu_g17s" + + private func poll(_ text: String, age: TimeInterval = 30, stale: Bool = false) throws -> BuildEvent? { + let folder = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: folder) } + let log = folder.appendingPathComponent("pso_offline_mac.log") + try text.write(to: log, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.modificationDate: start.addingTimeInterval(stale ? -60 : age)], ofItemAtPath: log.path) + return PipelineArchiveProgress().poll(logURL: log, architectures: [arch], stageStartedAt: start, now: start.addingTimeInterval(age)) + } + + private var header: String { + "pso_offline: \(arch): metal-tt, 2500 bases, 32 compute pipelines (minutes) ...\n" + } + + func testCountsOnlyBatchesReportedReadyNotStartedOrTemporaryFiles() throws { + let text = header + """ + pso_offline: applegpu_g17s: batch 1/5: 1000 bases, compiling or checking cache + pso_offline: applegpu_g17s: batch 1/5: ready (compiled) + pso_offline: applegpu_g17s: batch 2/5: 1000 bases, compiling or checking cache + """ + "\n" + let event = try poll(text) + guard case .progress(let fraction, let done, let total, let unit, let eta, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 1000) + XCTAssertEqual(total, 2532) + XCTAssertEqual(unit, "pipelines") + XCTAssertEqual(fraction, 1000.0 / 2532, accuracy: 0.0001) + XCTAssertEqual(try XCTUnwrap(eta), 45.96, accuracy: 0.01) + } + + func testReusedBatchesCountButSupplyNoCompilationRate() throws { + let event = try poll(header + """ + pso_offline: applegpu_g17s: batch 1/5: 1000 bases, compiling or checking cache + pso_offline: applegpu_g17s: batch 1/5: ready (reused) + """ + "\n") + guard case .progress(_, let done, _, _, let eta, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 1000) + XCTAssertNil(eta) + } + + func testStaleLogDoesNotReportLastBuildsProgress() throws { + XCTAssertNil(try poll(header, stale: true)) + } + + func testTruncatedUnknownAndPartialLinesDoNotInventCounts() throws { + XCTAssertNil(try poll("a different engine's log\n")) + let event = try poll(header + "pso_offline: applegpu_g17s: batch 1/5: 1000 bases, compiling or checking cache\n" + "pso_offline: applegpu_g17s: batch 1/5: ready (compiled)") + guard case .progress(_, let done, _, _, _, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 0, "The writer has not finished this line yet") + } + + func testParallelComputeBatchesAndCustomBatchSizesUseActualCounts() throws { + let event = try poll(header + """ + pso_offline: applegpu_g17s: batch 1/3: 2500 bases, compiling or checking cache + pso_offline: applegpu_g17s: batch 1/3: ready (compiled) + pso_offline: applegpu_g17s: batch 2/3: 16 compute pipelines, compiling or checking cache + pso_offline: applegpu_g17s: batch 3/3: 16 compute pipelines, compiling or checking cache + pso_offline: applegpu_g17s: batch 3/3: ready (compiled) + pso_offline: applegpu_g17s: batch 3/3: ready (compiled) + """ + "\n") + guard case .progress(_, let done, _, _, _, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 2516, "Duplicate ready lines must not double count") + } + + func testParallelCompilerMessagesSharingALineDoNotLoseABatch() throws { + let event = try poll(header + """ + pso_offline: applegpu_g17s: batch 4/5: 16 compute pipelines, compiling or checking cachepso_offline: applegpu_g17s: batch 5/5: 16 compute pipelines, compiling or checking cache + pso_offline: applegpu_g17s: batch 4/5: ready (compiled) + pso_offline: applegpu_g17s: batch 5/5: ready (compiled) + """ + "\n") + guard case .progress(_, let done, _, _, _, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 32) + } + + func testOtherArchitecturesDoNotCountTowardThisMac() throws { + let event = try poll(header + """ + pso_offline: applegpu_g18p: batch 1/1: 2532 bases, compiling or checking cache + pso_offline: applegpu_g18p: batch 1/1: ready (compiled) + """ + "\n") + guard case .progress(_, let done, _, _, _, _) = event else { return XCTFail("Missing progress") } + XCTAssertEqual(done, 0) + } +} diff --git a/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift b/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift index c6f7d85..af5e9c3 100644 --- a/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift +++ b/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift @@ -23,6 +23,7 @@ final class PortingFailureReasonTests: XCTestCase { ("games.several", .severalGames, "[RED] more than one game in the folder, keep one game per folder: 0100000000010000, 0100000000020000"), ("games.noTitleID", .noTitleID, "[RED] no game package with a readable content meta in /Users/x/Games/Foo"), ("disk.low", .diskFull, "[RED] less than 40 GB free on the volume of /Users/x/work"), + ("disk.app", .diskFull, "the app needs 12.4 GB on the volume of /Users/x/Temp (no APFS clones from the build there) and 13.1 GB are free"), ("disk.full", .diskFull, "OSError: [Errno 28] No space left on device"), ("icloud.detritus", .cloudFolder, "x.app: resource fork, Finder information, or similar detritus not allowed"), ] @@ -103,6 +104,24 @@ final class PortingFailureReasonTests: XCTestCase { XCTAssertEqual(classify("[RED] less than 40 GB free on the volume of /x").detail, "40") } + func testPackAppDiskFullDetailIsRoundedGB() { + let log = """ + the app needs 12.4 GB on the volume of /Users/x/Temp (no APFS clones from the build there) and 13.1 GB are free + [RED] pack_app.py exit code 1 + """ + let reason = classify(log, keys: "/unreadable/prod.keys", readable: false) + XCTAssertEqual(reason.kind, .diskFull) + XCTAssertEqual(reason.detail, "14") + XCTAssertEqual(reason.what(game: "Ocarina of Time"), "The porting needs at least 14 GB free on the work volume.") + XCTAssertEqual(reason.action, .openStorage) + } + + func testSourcesWithoutKeysDoNotFallBackToKeysOnUnrecognizedFailure() { + let log = "some unknown crash or error" + let reason = classify(log, keys: "") + XCTAssertEqual(reason.kind, .unknown) + } + func testNoTitleIDDetailIsFolder() { XCTAssertEqual(classify("[RED] no game package with a readable content meta in /Users/x/Foo").detail, "/Users/x/Foo") // Engines before nxnative 5c4dc18f said it this way. @@ -248,7 +267,7 @@ final class PortingFailureEngineTests: XCTestCase { private func gitGrep(_ fragment: String, at commit: String, in repo: URL) -> Bool { let p = Process() p.executableURL = URL(fileURLWithPath: "/usr/bin/git") - p.arguments = ["-C", repo.path, "grep", "-q", "-F", fragment, commit, "--", "scripts", "dm/gfx/tools", "dm/tools"] + p.arguments = ["-C", repo.path, "grep", "-q", "-F", fragment, commit, "--", "scripts", "dm/gfx/tools", "dm/tools", "dm/mac"] p.standardOutput = FileHandle.nullDevice p.standardError = FileHandle.nullDevice do { try p.run() } catch { return false } diff --git a/Tests/NXLibraryCoreTests/PortingPresentationTests.swift b/Tests/NXLibraryCoreTests/PortingPresentationTests.swift index 4e3cc93..27ef0b9 100644 --- a/Tests/NXLibraryCoreTests/PortingPresentationTests.swift +++ b/Tests/NXLibraryCoreTests/PortingPresentationTests.swift @@ -54,6 +54,53 @@ final class PortingPresentationTests: XCTestCase { func testETAWhileFinishingCountsDownTheLastTime() { XCTAssertEqual(PortingETA.text(finishing, lastFinishingSeconds: 150), "About 2 min left") XCTAssertEqual(PortingETA.text(finishing, lastFinishingSeconds: nil), "Finishing up") + + // Live ETA during Pipeline archive takes precedence + var pipelineArchive = finishing + pipelineArchive.apply(.progress(fraction: 0.5, done: 1000, total: 2000, unit: "pipelines", eta: 5400)) + XCTAssertEqual(PortingETA.text(pipelineArchive, lastFinishingSeconds: 150), "Pipelines: about 1 hr 30 min") + XCTAssertEqual(PortingETA.text(pipelineArchive, lastFinishingSeconds: nil), "Pipelines: about 1 hr 30 min") + } + + func testPipelineEstimateDoesNotClaimToIncludePackingTheApp() { + let p = progress([.stage(k: 5, n: 8, name: "Mac shaders", step: nil), + .stageEnd(name: "Mac shaders", ok: true, elapsed: 100, summary: nil), + .stage(k: 7, n: 8, name: "Pipeline archive", step: nil), + .progress(fraction: 0.5, done: 1000, total: 2000, unit: "pipelines", eta: 3600)]) + XCTAssertEqual(PortingETA.text(p, lastFinishingSeconds: nil), "Pipelines: about 1 hr") + } + + func testETAFormatsHoursAndMinutesWhenOverAnHour() { + let oneHour = progress([.stage(k: 5, n: 7, name: "Mac shaders", step: nil), .step("compiling"), + .progress(fraction: 0.1, done: nil, total: nil, unit: nil, eta: 3600)]) + XCTAssertEqual(PortingETA.text(oneHour, lastFinishingSeconds: nil), "Shaders: about 1 hr") + + let longShaders = progress([.stage(k: 5, n: 7, name: "Mac shaders", step: nil), .step("compiling"), + .progress(fraction: 0.5, done: nil, total: nil, unit: nil, eta: 15165)]) + XCTAssertEqual(PortingETA.text(longShaders, lastFinishingSeconds: nil), "Shaders: about 4 hr 13 min") + XCTAssertEqual(PortingETA.text(longShaders, lastFinishingSeconds: 120), "About 4 hr 15 min left") + + let longFinishing = progress([.stage(k: 5, n: 7, name: "Mac shaders", step: nil), + .stageEnd(name: "Mac shaders", ok: true, elapsed: 600, summary: nil), + .stage(k: 6, n: 7, name: "Mac host", step: nil)]) + XCTAssertEqual(PortingETA.text(longFinishing, lastFinishingSeconds: 3600), "About 1 hr left") + XCTAssertEqual(PortingETA.text(longFinishing, lastFinishingSeconds: 3900), "About 1 hr 5 min left") + } + + // MARK: - Stage Elapsed Time + + func testElapsedFormatsSecondsMinutesAndHours() { + XCTAssertEqual(PortingETA.elapsed(0), "0 s") + XCTAssertEqual(PortingETA.elapsed(6), "6 s") + XCTAssertEqual(PortingETA.elapsed(59.4), "59 s") + XCTAssertEqual(PortingETA.elapsed(60), "1:00") + XCTAssertEqual(PortingETA.elapsed(61), "1:01") + XCTAssertEqual(PortingETA.elapsed(397), "6:37") + XCTAssertEqual(PortingETA.elapsed(3599), "59:59") + XCTAssertEqual(PortingETA.elapsed(3600), "1:00:00") + XCTAssertEqual(PortingETA.elapsed(3665), "1:01:05") + // 252 minutes 45 seconds (15165 s) -> 4 hours 12 minutes 45 seconds + XCTAssertEqual(PortingETA.elapsed(15165), "4:12:45") } // MARK: - Tile look diff --git a/Tests/NXLibraryCoreTests/PortingProgressTests.swift b/Tests/NXLibraryCoreTests/PortingProgressTests.swift index 55e7ae2..f5c1207 100644 --- a/Tests/NXLibraryCoreTests/PortingProgressTests.swift +++ b/Tests/NXLibraryCoreTests/PortingProgressTests.swift @@ -170,6 +170,29 @@ final class PortingProgressTests: XCTestCase { let progress = run(beforeShaders + [.say("removing the shader cache of other chain versions")]) XCTAssertNil(PortingETA.text(progress, lastFinishingSeconds: nil)) } + + func testPipelineArchiveProgressAdvancesOverallAndSetsFinishingETA() { + var progress = run(shaders + [ + .stageEnd(name: "Mac shaders", ok: true, elapsed: 607, summary: nil), + .stage(k: 6, n: 8, name: "Mac host", step: "building"), + .stageEnd(name: "Mac host", ok: true, elapsed: 10, summary: nil), + .stage(k: 7, n: 8, name: "Pipeline archive", step: "applegpu_g13g") + ]) + XCTAssertEqual(progress.phase, .finishing) + XCTAssertEqual(progress.overall, 0.9 + 0.1 / 3, accuracy: 1e-9) // 93.3% + XCTAssertNil(progress.finishingETA) + + // Halfway through Pipeline archive + progress.apply(.progress(fraction: 0.5, done: 1000, total: 2000, unit: "pipelines", eta: 3600)) + XCTAssertEqual(progress.finishingETA, 3600) + // overall should be 0.9 + 0.1 * (1 + 0.5) / 3 = 0.9 + 0.05 = 0.95 (95%) + XCTAssertEqual(progress.overall, 0.95, accuracy: 1e-9) + + // Stage ends: clears finishingETA, overall becomes 0.9 + 0.1 * 2 / 3 = 0.9666... + progress.apply(.stageEnd(name: "Pipeline archive", ok: true, elapsed: 120, summary: nil)) + XCTAssertNil(progress.finishingETA) + XCTAssertEqual(progress.overall, 0.9 + 0.2 / 3, accuracy: 1e-9) + } } final class BuildWarningTests: XCTestCase { diff --git a/VERSION b/VERSION index 473b31b..de629fc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.4.1 +0.1.4.2 diff --git a/docs/architecture.md b/docs/architecture.md index 51184ed..4eebc4a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # NX Library — guida tecnica -Aggiornata il 5 novembre 2026 sul codice del repository. La mappa comprende 253 file Swift di produzione e 104 file di test e supporto ai test. Il ramo porta nxnative a bd979ced (8 novembre 2026, 0.1.4.1; candidato in attesa di validazione manuale: rebuild di Ocarina of Time, che ritraduce gli shader). Il pin precedente, e5f03403, è stato confermato dal maintainer con un rebuild di Ocarina of Time; l'aggiunta di un gioco NSP con le chiavi resta da provare a mano. Le funzioni della 0.1.3 e della 0.1.4 aggiunte dopo l'ultima conferma restano in attesa di validazione manuale. +Aggiornamento 9 novembre 2026 per NX Library 0.1.4.2. Il pin è `79741184`, con Metal 32023.922 / Xcode 27.2: adozione esplicitamente richiesta dal maintainer dopo la revisione di 71 commit. Gate e test automatici superati; una build/avvio manuale sull'esatto nuovo hash resta da confermare e non viene dichiarata eseguita. Ocarina installato è già su `7ffa63cc`, con la stessa catena shader e cinque commit di differenza. Vedi la [revisione engine](upstream-upgrades/2026-11-09-bd979ced-79741184-review.md) per evidenze, decisioni UI/backend e limiti. Questa guida descrive il funzionamento attuale. Il [catalogo dei file](file-map.md) assegna una responsabilità a ogni file versionato, suddiviso per area. @@ -117,7 +117,7 @@ Get ready mostra soltanto i sei requisiti tecnici, in una griglia 3×2; i proble `CompatibilityChecker` verifica commit, modifiche del checkout, contratto CLI e compilatore Metal richiesto dal lock del motore. `ToolchainRequirements.current` contiene Xcode minimo, major .NET e asset Metal. `ToolchainGateCopy` produce la spiegazione del risultato per la UI. -Il lock del pin attuale certifica Metal `32023.922`, asset Apple `27B47`, con Xcode minimo `27.2`. La [nota dell’upgrade del 6 novembre](upstream-upgrades/2026-11-06-4a0551e4-04df08d4-review.md) registra 04df08d4, confermato dal maintainer con un build di Ocarina of Time; quella [e5f03403 → bd979ced](upstream-upgrades/2026-11-08-e5f03403-bd979ced-review.md) registra il pin attuale, candidato in attesa di validazione manuale; quella [7fe9fc4d → e5f03403](upstream-upgrades/2026-11-07-7fe9fc4d-e5f03403-review.md) registra il pin precedente, confermato dal maintainer con un rebuild di Ocarina of Time; quella [b2d46b86 → 7fe9fc4d](upstream-upgrades/2026-11-07-b2d46b86-7fe9fc4d-review.md) registra il pin precedente, confermato dal maintainer con un rebuild di Ocarina of Time; quella [04df08d4 → b2d46b86](upstream-upgrades/2026-11-06-04df08d4-b2d46b86-review.md) registra b2d46b86, che aggiunge il controllo dei file `.zs` danneggiati in NUP e cartelle (riga `files.format` del classificatore); quella [del 5 novembre](upstream-upgrades/2026-11-05-b64a5ff0-4a0551e4-review.md) registra gate certificato senza override, build reale di Wonder con archivio pipeline e avvio confermato dall’utente per 4a0551e4. Un build con override riceve `toolchainVerified=false` e `--allow-other-metal`. Play valuta la disponibilità dell’app già installata separatamente dal gate per un nuovo porting. +Il lock del pin attuale certifica Metal `32023.922`, asset Apple `27B47`, con Xcode minimo `27.2`. La [nota dell’upgrade del 6 novembre](upstream-upgrades/2026-11-06-4a0551e4-04df08d4-review.md) registra 04df08d4, confermato dal maintainer con un build di Ocarina of Time; quella [bd979ced → 79741184](upstream-upgrades/2026-11-09-bd979ced-79741184-review.md) registra il pin della 0.1.4.2, adottato su richiesta esplicita del maintainer con gate e test automatici superati ma build/avvio sull'esatto hash ancora non confermati; quella [e5f03403 → bd979ced](upstream-upgrades/2026-11-08-e5f03403-bd979ced-review.md) registra il pin della 0.1.4.1; quella [7fe9fc4d → e5f03403](upstream-upgrades/2026-11-07-7fe9fc4d-e5f03403-review.md) registra il pin precedente, confermato dal maintainer con un rebuild di Ocarina of Time; quella [b2d46b86 → 7fe9fc4d](upstream-upgrades/2026-11-07-b2d46b86-7fe9fc4d-review.md) registra il pin precedente, confermato dal maintainer con un rebuild di Ocarina of Time; quella [04df08d4 → b2d46b86](upstream-upgrades/2026-11-06-04df08d4-b2d46b86-review.md) registra b2d46b86, che aggiunge il controllo dei file `.zs` danneggiati in NUP e cartelle (riga `files.format` del classificatore); quella [del 5 novembre](upstream-upgrades/2026-11-05-b64a5ff0-4a0551e4-review.md) registra gate certificato senza override, build reale di Wonder con archivio pipeline e avvio confermato dall’utente per 4a0551e4. Un build con override riceve `toolchainVerified=false` e `--allow-other-metal`. Play valuta la disponibilità dell’app già installata separatamente dal gate per un nuovo porting. L'engine più nuovo è una scelta esplicita del giocatore (Settings → Engine → **Newest engine**). Dopo il controllo Upstream, che misura sempre i commit rispetto al pin, `NewestEngine.fetchLock` legge il lock di main da GitHub: se certifica un Metal diverso da quello del lock del pin, la riga dice che serve un NX Library più nuovo e non offre il cambio. `NewestEngineSwitcher` rifiuta un checkout con file modificati, scarica, rilegge il lock con git e solo allora fa `checkout --detach`. La scelta (`NewestEngineChoice`) è salvata, diventa il commit atteso dal gate e quello registrato nelle build, accende l'override (che resta bloccato finché la scelta è attiva) e marca le build come non testate; il gate mostra **Newest engine** in arancione, mai Certified. Il Setup continua a installare il pin, ma per il commit scelto mostra la scheda pronta in arancione con **Back to tested** e non la riporta indietro da solo. La scelta decade quando il checkout non è più su quel commit o quando un aggiornamento di NX Library porta un pin che lo contiene; se l'override l'aveva acceso la scelta, torna spento. Settings e Setup usano la stessa frase (`NewestEngineCopy.note`). @@ -150,6 +150,8 @@ Se il motore termina correttamente con un bundle valido e fallisce solo l’inst Prima della pubblicazione, l’installer registra una piccola ricevuta accanto all’app con percorso di staging e identità filesystem dei bundle. Una ricevuta per destinazione resta dopo il successo e viene aggiornata al prossimo porting: se il processo termina prima del salvataggio del catalogo, il tentativo successivo riconosce l’app già pubblicata e completa il record, anche dopo la pulizia dello staging. Evita così un secondo scambio che rimetterebbe la vecchia app al suo posto. Una destinazione modificata esternamente durante lo stesso tentativo causa un errore conservativo. La vecchia app e l’eventuale sorgente copiata vengono eliminate solo dopo la pubblicazione. +La 0.1.4.2 (in attesa di validazione manuale) mostra il tempo trascorso della fase attiva e formatta durate ed ETA in ore oltre i 60 minuti. Un timer a 1 Hz sul main run loop aggiorna la fase; il controllo di `logs/pso_offline_mac.log` gira fuori dal main thread. `PipelineArchiveProgress` legge soltanto log modificati dopo l'inizio della fase e conta i gruppi dichiarati `ready`, distinguendo `compiled` e `reused`: i file temporanei del compilatore e le chiavi delle vecchie cache non sono una prova di lavoro completato. Le dimensioni dei gruppi vengono dal log, senza assumere quelle predefinite. La stima riguarda le pipeline, non il confezionamento successivo dell'app. Formati ignoti non inventano un avanzamento; cambi di fase, annullamento e fine build invalidano i controlli ancora in corso. `PortingFailureClassifier` riconosce anche lo spazio richiesto da `pack_app.py`; il fallback sulle chiavi si applica solo alle sorgenti che le richiedono. + `BuildRecord` conserva l’esito e la provenienza del build. `PortingFailureClassifier` riconosce i messaggi del motore e propone un’azione, presentata da `AppViewModel+Porting`. `PortingNotice` decide se notificare l’esito mentre l’app è in background. `NotificationService` gestisce la notifica di sistema e la destinazione del clic. Gli adattamenti di b64a5ff0 riconoscono i nuovi errori di chiavi troppo vecchie, conservando la versione firmware quando conosciuta, e il ticket comune mancante nel pacchetto. Quest'ultimo usa il kind persistito `keys` con un dettaglio identificativo: anche un launcher precedente riesce a leggere la voce durante un rollback. La UI corrente offre la spiegazione sul ticket senza proporre di sostituire le chiavi o forzare il porting. L'evento `say` con cui l'engine sposta una vecchia cache shader nel nuovo file (`PortingProgress.shaderCacheWorkMessage`) accende `PortingProgress.isUpdatingShaderCache`; `PortingETA` mostra **Updating shader cache** senza avanzare la percentuale né stimare una durata. Lo stato si azzera a stage o step successivo e al termine del build. `PortingFailureEngineTests` verifica che l'engine del pin stampi ancora quel testo. @@ -162,6 +164,8 @@ Il flusso Play controlla operazioni di salvataggio, recuperi e installazioni in Off rimuove la preferenza cloud salvata dal launcher del gioco anche all’avvio. Gli override avanzati `DM_SAVE_SYNC` nei file o nell’ambiente mantengono la precedenza del motore: `SaveLocation.inspect` ne segnala l’origine e mantiene il blocco delle operazioni incompatibili con la sincronizzazione attiva. +Nella 0.1.4.2 (in attesa di validazione manuale), Play durante la firma dell'icona apre il dialogo Selene **Preparing** con **Cancel**. `IconLaunchWait` lega la richiesta all'identità del gioco e del dialogo; la chiusura o sostituzione del dialogo revoca la richiesta. Solo l'ultimo aggiornamento icona riuscito può riprendere Play, che rilegge il gioco corrente e ripete tutti i controlli dei salvataggi e dell'app. Un errore non avvia il gioco. Cancel lascia terminare la firma, necessaria a mantenere il bundle valido. L'attesa usa `launchingGameId` e blocca anche l'aggiornamento del frontend. + `RunningGameMonitor` confronta i bundle ID della libreria con le applicazioni in esecuzione e offre Resume e Close Game. `LaunchReadinessProbe` attende una finestra visibile del processo per chiudere la schermata di lancio. `PlaySessionStore` registra avvio, heartbeat, fine e riconciliazione delle sessioni. `ActivitySummary` aggrega la settimana, mentre `LibraryPresentation` e `GameDetailPresentation` preparano i valori delle schermate. ## Cheats @@ -214,7 +218,9 @@ LaunchBox e SteamGridDB non entrano nella griglia automatica: ciascuno ha un riq `NintendoStoreMetadataService`, `GameTDBStore`, Title DB e le sorgenti di artwork raccolgono descrizioni, date, generi, copertine, loghi, sfondi e screenshot. `ArtworkNames` confronta i nomi per limitare i candidati al gioco o alle sue edizioni. `ArtworkPicker` e `MetadataOptions` aggregano i risultati per i selettori. `ArtworkPicks` e `MetadataPicks` conservano le scelte manuali. -L'icona scelta in Change artwork (slot Icon, conservata a 1024 px) diventa anche l'icona dell'app del gioco in Finder e nel Dock (`AppIconWriter`, `AppViewModel+AppIcon`). Un'app senza icona propria (NUP Switch 2, dump) la riceve subito; un'app con l'icona del gioco la riceve solo se il giocatore lo sceglie nel pop-up dopo la scelta; Automatic rimette l'icona del gioco. La scrittura crea `Resources/AppIcon.icns`, marca `Info.plist` con `NXLibraryAppIcon`, rifirma il bundle ad hoc come l'engine e verifica con `codesign --verify --deep --strict`; l'icona originale resta in `Application Support/NXLibrary/app-icons/<TitleID>/`, e ogni errore rimette i file toccati. Avviene dopo ogni build installata (la rebuild sostituisce l'app, e l'engine ricrea il pacchetto se trova file cambiati), all'avvio e quando un gioco si chiude, mai mentre il gioco gira o si costruisce; durante la firma (circa 50 secondi per un gioco di 9–12 GB) avvio e build del gioco attendono, e la riga di stato di Change artwork mostra l'avanzamento. Una scelta fatta mentre la stessa app si firma viene rieseguita alla fine, così vale l'ultima. Dopo ogni scansione della libreria `reconcileAppIcons` confronta lo stato voluto con l'app (marca `NXLibraryAppIcon`) e rimette in coda le differenze, per esempio una scelta fatta mentre l'app era stata spostata. +La 0.1.4.2 prepara inoltre un resolver delle icone automatiche (`AutomaticIconResolver`, in attesa di validazione manuale e di chiarimento dell'accesso al provider secondario). Il gioco e le scelte manuali mantengono precedenza; `ArtworkStore.automaticIcon` prova Title DB e poi una corrispondenza esatta normalizzata in Switch Icon Showdown, senza confondere sequel, omonimi o edizioni. Un title ID Switch 2 da solo non identifica una Switch 2 Edition. La cache PNG resta separata da `ArtworkPicks`, con attribuzione e una sola piccola voce negativa per gioco (15 minuti). Il resolver condivide le richieste concorrenti della stessa istanza, limita dimensioni e tempi delle risposte, verifica host e immagini quadrate e consulta `robots.txt` prima del download secondario. **Al 9 novembre 2026, il sito esclude `/images/` dai crawler: il resolver non scarica le immagini di Switch Icon Showdown. Non considerare la issue #12 risolta o questa fonte operativa finché non viene chiarito un accesso consentito.** Le immagini automatiche disponibili possono riempire l'icona di un'app che non ne ha una, solo senza pick manuale e senza un esplicito Only here. Le app con icona propria rimangono intatte. Fallimenti di rete o ricerche ambigue non fanno fallire il porting. Nessun file del motore viene modificato. + +L'icona scelta in Change artwork (slot Icon, conservata a 1024 px) diventa anche l'icona dell'app del gioco in Finder e nel Dock (`AppIconWriter`, `AppViewModel+AppIcon`). Nella 0.1.4.2 (in attesa di validazione manuale) il pop-up offre **Only here** / **Use on the Mac** per ogni gioco, anche senza icona propria o non ancora costruito. `PortedGame.appIconOnMac` memorizza la scelta esplicita: false vieta la scrittura anche per NUP e dump; true la richiede quando esiste un'Icon pick; nil conserva la regola precedente (`usesPickedAppIcon` oppure assenza di icona originale) per le vecchie librerie fino alla prossima scelta. Riconciliazione e scrittura condividono `wantsPickedAppIcon`. Only here ripristina l'icona originale o la sua assenza se una personalizzata era già applicata. Automatic rimuove solo la scelta dell'immagine, conserva la destinazione esplicita (e l'opt-in legacy) e usa prima l'originale, anche dal backup durante una personalizzazione, poi l'icona estratta o quella online disponibile. La tile Icon location permette di modificare la destinazione anche senza pick. Se la ricerca non trova un sostituto, il writer ripristina l'originale o la sua assenza, senza lasciare la vecchia immagine personalizzata. La tile Automatic mostra l'anteprima risolta; lo stato distingue ricerca, firma e icona non disponibile. AppIconProgress conserva i testi condivisi fra UI e log. Dopo una build installata, inclusa un'installazione differita, l'esito della preparazione viene aggiunto al relativo log già chiuso dall'engine e al log della UI; assenza di artwork non cambia il successo della build. La riga informativa viene scritta solo dopo l'ultimo aggiornamento in coda, non per le successive modifiche manuali. Scansioni e rebuild conservano la preferenza. La scrittura crea `Resources/AppIcon.icns`, marca `Info.plist` con `NXLibraryAppIcon`, rifirma il bundle ad hoc come l'engine e verifica con `codesign --verify --deep --strict`; l'icona originale resta in `Application Support/NXLibrary/app-icons/<TitleID>/`, e ogni errore rimette i file toccati. Avviene dopo ogni build installata (la rebuild sostituisce l'app, e l'engine ricrea il pacchetto se trova file cambiati), all'avvio e quando un gioco si chiude, mai mentre il gioco gira o si costruisce; durante la firma (circa 50 secondi per un gioco di 9–12 GB) avvio e build del gioco attendono, e la riga di stato di Change artwork mostra l'avanzamento. Una scelta fatta mentre la stessa app si firma viene rieseguita alla fine, così vale l'ultima. Dopo ogni scansione della libreria `reconcileAppIcons` confronta lo stato voluto con l'app (marca `NXLibraryAppIcon`) e rimette in coda le differenze, per esempio una scelta fatta mentre l'app era stata spostata. `ArtworkStore` mantiene la cache immagini. `BoxArtService` risolve le copertine Switch di GameTDB. `GameArtLoader` e `SeleneHeroArt` alimentano sfondo e logo delle schermate con caricamento e cache dedicati. diff --git a/docs/file-map.md b/docs/file-map.md index 41e7602..7fbdd2b 100644 --- a/docs/file-map.md +++ b/docs/file-map.md @@ -180,6 +180,9 @@ La pubblicazione comprende anche [CONTRIBUTING.md](../CONTRIBUTING.md), [SECURIT | [Sources/NXLibraryCore/Models/TargetPlatform.swift](../Sources/NXLibraryCore/Models/TargetPlatform.swift) | Definisce il target mac-app e le sue etichette per l’interfaccia. | | [Sources/NXLibraryCore/Models/GameRuntimeConfig.swift](../Sources/NXLibraryCore/Models/GameRuntimeConfig.swift) | Persiste opzioni per titolo, prepara Review e lingua del rebuild, mappa DM_* e unisce env.txt. | | [Sources/NXLibraryCore/Models/LauncherSettings.swift](../Sources/NXLibraryCore/Models/LauncherSettings.swift) | Legge launcher.json e verifica i controlli supportati, con fallback per le build precedenti. | +| [Sources/NXLibraryCore/Models/AppIconProgress.swift](../Sources/NXLibraryCore/Models/AppIconProgress.swift) | Stati e testi della preparazione icona, con esito informativo aggiunto al log della build conclusa. | +| [Sources/NXLibraryCore/Models/IconLaunchWait.swift](../Sources/NXLibraryCore/Models/IconLaunchWait.swift) | Conserva una richiesta Play durante la firma dell'icona e la revoca su annullamento, errore o chiusura del dialogo. | +| [Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift](../Sources/NXLibraryCore/Services/PipelineArchiveProgress.swift) | Legge il log pipeline della build corrente e conta solo i gruppi confermati pronti dal motore. | | [Sources/NXLibraryCore/Models/PortingProgress.swift](../Sources/NXLibraryCore/Models/PortingProgress.swift) | Aggrega eventi del motore in fasi e percentuale complessiva per la UI. | | [Sources/NXLibraryCore/Models/PortingPresentation.swift](../Sources/NXLibraryCore/Models/PortingPresentation.swift) | Definisce fallimento persistito, ETA, aspetto della tile e pill del porting. | | [Sources/NXLibraryCore/Models/PortingFailureReason.swift](../Sources/NXLibraryCore/Models/PortingFailureReason.swift) | Classifica messaggi del motore e rifiuti preliminari in motivazioni e azioni spiegabili. | @@ -255,6 +258,7 @@ La pubblicazione comprende anche [CONTRIBUTING.md](../CONTRIBUTING.md), [SECURIT | File | Responsabilità | |---|---| | [Sources/NXLibraryCore/Models/OnlineBackgrounds.swift](../Sources/NXLibraryCore/Models/OnlineBackgrounds.swift) | Modella sorgenti e risultati e interpreta le risposte dei provider di sfondi fissi e video. | +| [Sources/NXLibraryCore/Services/AutomaticIconResolver.swift](../Sources/NXLibraryCore/Services/AutomaticIconResolver.swift) | Risolve icone automatiche con cache, abbinamento prudente e rispetto delle restrizioni del provider. | | [Sources/NXLibraryCore/Services/ArtworkStore.swift](../Sources/NXLibraryCore/Services/ArtworkStore.swift) | Scarica, ridimensiona e conserva immagini e screenshot nella cache per titolo e tipo. | | [Sources/NXLibraryCore/Services/AppIconWriter.swift](../Sources/NXLibraryCore/Services/AppIconWriter.swift) | Scrive l’icona scelta nell’app del gioco, conserva l’originale, rifirma e verifica, e annulla tutto in caso di errore. | | [Sources/NXLibraryCore/Services/ArtworkPicker.swift](../Sources/NXLibraryCore/Services/ArtworkPicker.swift) | Aggrega candidati per cover, sfondo, icona e logo e conserva le scelte manuali. | @@ -456,6 +460,7 @@ La pubblicazione comprende anche [CONTRIBUTING.md](../CONTRIBUTING.md), [SECURIT | [Tests/NXLibraryCoreTests/AppUpdatePresentationTests.swift](../Tests/NXLibraryCoreTests/AppUpdatePresentationTests.swift) | Verifica che i controlli manuali non permettano sostituzioni di copie di sviluppo, in sola lettura o con una versione uguale/più recente. | | [Tests/NXLibraryCoreTests/ArtworkPickerFocusTests.swift](../Tests/NXLibraryCoreTests/ArtworkPickerFocusTests.swift) | Spostamenti nella griglia dei candidati e passaggi fra slot di artwork. | | [Tests/NXLibraryCoreTests/ArtworkPickerTests.swift](../Tests/NXLibraryCoreTests/ArtworkPickerTests.swift) | Decodifica Title DB, candidati per slot e scelte manuali. | +| [Tests/NXLibraryCoreTests/AutomaticIconTests.swift](../Tests/NXLibraryCoreTests/AutomaticIconTests.swift) | Priorità delle fonti, ambiguità, edizioni, cache offline, immagini invalide e opt-out del sito. | | [Tests/NXLibraryCoreTests/ArtworkStoreTests.swift](../Tests/NXLibraryCoreTests/ArtworkStoreTests.swift) | Cache, download, immagini trasparenti, ridimensionamento e fallback. | | [Tests/NXLibraryCoreTests/BoxArtServiceTests.swift](../Tests/NXLibraryCoreTests/BoxArtServiceTests.swift) | Matching GameTDB, regioni e durata delle ricerche senza risultato. | | [Tests/NXLibraryCoreTests/BuildCommandBuilderTests.swift](../Tests/NXLibraryCoreTests/BuildCommandBuilderTests.swift) | Contratto CLI, opzioni runtime/build e propagazione dell’override Metal. | @@ -528,6 +533,10 @@ La pubblicazione comprende anche [CONTRIBUTING.md](../CONTRIBUTING.md), [SECURIT | [Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift](../Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift) | Classificazione dei fallimenti e corrispondenza con i messaggi del pin, anche da un checkout separato scelto con NX_TEST_ENGINE_REPO. | | [Tests/NXLibraryCoreTests/PortingNoticeTests.swift](../Tests/NXLibraryCoreTests/PortingNoticeTests.swift) | Condizioni e testi delle notifiche di esito del porting. | | [Tests/NXLibraryCoreTests/PortingPresentationTests.swift](../Tests/NXLibraryCoreTests/PortingPresentationTests.swift) | ETA, tile, pill e rappresentazione dei fallimenti del porting. | +| [Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift](../Tests/NXLibraryCoreTests/AppIconPreferenceTests.swift) | Ambito dell’icona: scelta esplicita, vecchie librerie, scansioni, persistenza e rebuild. | +| [Tests/NXLibraryCoreTests/AppIconProgressTests.swift](../Tests/NXLibraryCoreTests/AppIconProgressTests.swift) | Verifica che l'esito di un'icona mancante non sovrascriva il log o segnali una build fallita. | +| [Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift](../Tests/NXLibraryCoreTests/IconLaunchWaitTests.swift) | Attesa Play: successo, errore, annullamento, gioco diverso e nuova firma in coda. | +| [Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift](../Tests/NXLibraryCoreTests/PipelineArchiveProgressTests.swift) | Log obsoleti, righe incomplete, cache riusata, gruppi paralleli e GPU diverse. | | [Tests/NXLibraryCoreTests/PortingProgressTests.swift](../Tests/NXLibraryCoreTests/PortingProgressTests.swift) | Aggregazione delle fasi, avanzamento monotono e peso degli shader. | | [Tests/NXLibraryCoreTests/ProdKeysLocationsTests.swift](../Tests/NXLibraryCoreTests/ProdKeysLocationsTests.swift) | Selezione e validazione dei possibili percorsi delle chiavi. | | [Tests/NXLibraryCoreTests/RunningGamesTests.swift](../Tests/NXLibraryCoreTests/RunningGamesTests.swift) | Associazione bundle ID, applicazioni aperte e voci della libreria. | diff --git a/docs/reviews/2026-11-09-hotfix-0.1.4.2.md b/docs/reviews/2026-11-09-hotfix-0.1.4.2.md new file mode 100644 index 0000000..2ce147a --- /dev/null +++ b/docs/reviews/2026-11-09-hotfix-0.1.4.2.md @@ -0,0 +1,45 @@ +# 0.1.4.2 — release record, 2026-11-09 + +The maintainer authorized committing, integrating and publishing 0.1.4.2, and explicitly requested adopting latest nxnative 79741184 rather than retaining bd979ced. The Icon / Automatic / Icon location flow was confirmed in Dev. Exact-hash game build/visible launch, build-progress cancellation and signing-wait stress cases not explicitly reported remain unconfirmed; release authorization is not represented as having performed them. + +## Final scope + +- Session card: `<1m` and a fixed leading-aligned content width prevent short-session text overflow (earlier commit 608e7e1). +- Xcode requirements: parse the installed `Xcode 27.2` prefix correctly and avoid asking to upgrade an already-current toolchain. +- Disk errors: recognize pack_app's space check including its 1 GiB reserve; NUP/extracted sources do not receive the missing-keys fallback. Retain @eduardomozart's PR #13 fix/tests and extend the engine-message check to dm/mac. +- Long builds: elapsed time updates, hour formatting and percentages from @eduardomozart's PR #11. Pipeline tracking was replaced with a current-log reader: actual batch sizes, confirmed ready (compiled/reused), no inference from temporary/cache files, joined parallel-worker records, generation guards after cancellation/next stage, and an estimate limited to pipelines. +- Icon source and location are independent. Automatic preserves Only here / Use on the Mac and original-image provenance. Icon location is reachable through the existing keyboard/controller grid. Missing replacements remove the prior personal app icon and restore the original/generic icon. The picker and finished build log explain a missing image; Play remains available. +- Play waits safely for icon signing; Cancel or a replaced notice revokes the pending launch. Rapid changes finish one signature then reread the latest source/location. +- Title DB fallback validates/caches square images separately from personal picks. Switch Icon Showdown remains blocked by its current /images/ opt-out; no claim that issue #12's full automatic Switch 2 fallback is solved. LaunchBox remains a manual artwork source. +- A regression check preserves a saved 120 FPS selection through unrelated edits and launch migration. Six upstream profiles currently offer 120; Ocarina offers 30/60. Metadata support is not a physical 120 FPS gameplay certification. +- README highlights and mod-store screenshot/documentation (earlier commit 2fcb114). +- Pin latest upstream 79741184 on the maintainer's explicit instruction. See the [71-commit review](../upstream-upgrades/2026-11-09-bd979ced-79741184-review.md): same Metal 32023.922, no new Python packages, changed shader chain, backend/runtime improvements and existing metadata-driven controls. Installed games retain their embedded engine until rebuilt. + +## Verification evidence + +- Full Swift suite: **1,166 tests, zero failures** before final release packaging; scripts/release.sh repeats it against the exact committed release tree. +- Publication/runtime checks and **17 Python tests** passed. +- Dev 0.1.4.2 compilation and strict/deep signature verification passed. Release script validates the release marker/version, updater ZIP extraction, extracted signature, and DMG creation. +- Real Ocarina offline-pipeline log replay: **34,815 / 34,815** confirmed pipelines, including the joined-worker-record case. This verifies parsing and is not a new interactive build. +- Existing Ocarina build inspected in both catalog and installed DMCommit: **7ffa63cc**, completed successfully on 2026-11-09 with Metal 32023.922 / Xcode 27.2. It already contains 66 of the 71 reviewed engine commits and the same shader chain. Do not ask for another large Ocarina rebuild just to see those already-present changes. A smaller game can supply exact-new-pin manual validation later. +- Icon rollback tests use synthetic bundles, distinct A/B image outputs and original/absent-original restoration. Missing-icon log append preserves engine output. No game assets or real keys are used by tests. +- No computer-use/UI automation was performed. No cache or released installed app was replaced by the agent's verification. + +## Remaining human checks + +- A build and visible launch on exact 79741184, optionally a smaller game. Observe pipeline progress, end-of-build icon log and destination persistence. +- Build cancel/restart without stale progress; Play during signing and Cancel without a delayed launch; rapid icon changes, running-game deferral and persistence where not already confirmed. +- Actual 120 FPS gameplay and Switch 2 controller pairing/motion/rumble with appropriate hardware, if used. +- Installing the published update through the user's released copy remains a maintainer check; archive/signature validation does not claim that the interactive update ran. + +## Community follow-up + +Public replies were posted to issues [#3](https://github.com/Bium23/NxLibrary/issues/3#issuecomment-6738355696), [#9](https://github.com/Bium23/NxLibrary/issues/9#issuecomment-6738356069), [#10](https://github.com/Bium23/NxLibrary/issues/10#issuecomment-6738356404) and [#12](https://github.com/Bium23/NxLibrary/issues/12#issuecomment-6738356680). Grid, Homebrew, localization and the full secondary-provider icon fallback remain open proposals; do not close them because this release ships adjacent fixes. + +PR [#13](https://github.com/Bium23/NxLibrary/pull/13#issuecomment-6738357603) is retained with attribution; PR [#11](https://github.com/Bium23/NxLibrary/pull/11#issuecomment-6738357113) is adapted with its pipeline implementation replaced. Once the hotfix is on main, link its integration commit/release and close those contribution PRs as incorporated, without falsely marking the originals merged. Keep author/co-author credit to Eduardo Mozart de Oliveira (@eduardomozart). + +PR [#1](https://github.com/Bium23/NxLibrary/pull/1#issuecomment-6738358023) mixes an obsolete settings implementation with unverified performance changes and conflicts with current main. Current saved-settings builds already preserve 120; older custom builds without DMSettings need rebuilding. A focused measured performance PR was requested. It remains open rather than closing an unverified optimization as solved. + +## Packaging + +VERSION and release notes name 0.1.4.2. Publish only through scripts/release.sh from the pushed main tree, using the reviewed notes in docs/private/release-0.1.4.2/notes.md. The script writes exact engine/Xcode requirement metadata for the installed app's updater. Keep former release artifacts outside work/release before its regeneration. Final remote release/asset/PR results are recorded in the task's handoff after publication, rather than inventing them in advance. diff --git a/docs/upstream-upgrades/2026-11-09-bd979ced-79741184-review.md b/docs/upstream-upgrades/2026-11-09-bd979ced-79741184-review.md new file mode 100644 index 0000000..820aeab --- /dev/null +++ b/docs/upstream-upgrades/2026-11-09-bd979ced-79741184-review.md @@ -0,0 +1,153 @@ +# 2026-11-09 — nxnative bd979ced → 79741184 review + +**Status: adopted for 0.1.4.2 at the maintainer's explicit direction on 2026-11-09.** Previous pin: `bd979ced3ae6bad60bf86429a71d172297d93471`. New pin: `79741184af1558fa1ada0f61741eeecbd615b3f6` (upstream main fetched again immediately before adoption), **71 commits, 254 files**. The maintainer explicitly requested the latest engine for the release instead of retaining the old pin pending another game build. This overrides that adoption order for this release only; **no real build or visible launch on the exact new commit is claimed**. Automated tests, compatibility gate and release-archive verification remain required. Later upstream commits are outside this review. + +## Why adopt this engine + +Ocarina of Time benefits across build, startup and play: fewer first-draw stalls (task/mesh pipelines prewarmed and archived), archived-pipeline lookups indexed, compaction limited by tier instead of repeatedly rebuilding the whole archive, large bundled archives mapped instead of copied into RAM, render-target heaps reused as resolution changes, fixes for mist/depth/light-probe artifacts, and safer mesh dispatches above 1,024 workgroups. Frame-step changes target slow motion under load, separately from FPS. Shader arithmetic and HMMA lowering improve translation/GPU work. + +Upstream measurements are evidence for investigating, **not measurements or promises for this Mac**. Examples: one HMMA rewrite phase fell from 932 CPU seconds to 3.5 with identical output (1b9775a0; not total build duration); one external-drive Ocarina footprint fell from 19.0 to 12.2 GB (a1e155a3); distinct repeated-start scenarios fell from 762 seconds to 3.3 (4a815619) or 713 seconds to 23.6 and later 2.4 (4c3a03af). Those scenarios, devices, caches and routes differ. + +Other upstream results include The Rogue Prince of Persia on Mac (60/up to 120 FPS), OCTOPATH TRAVELER at 60, Minecraft Dungeons II at 60/up to 120, Hyrule Warriors: Age of Calamity v458752 at 60, and fixes affecting Luigi's Mansion 3, Super Mario Odyssey, Sparking! ZERO, Red Dead Redemption, LEGO Star Wars: The Skywalker Saga, Fire Emblem Fortune's Weave, Aokana, Minecraft, and multi-program Metal Gear Solid DLC. They require rebuilt game apps and are not all tested by the maintainer. + +## Contract and integration decisions + +| Capability / change | Decision in NX Library | Reason / practical effect | +|---|---|---| +| Shader/runtime fixes and DMSH_CHAIN `3ee52af3ca18` → `dedc9391c30b` | Backend | Upstream owns translation and cache invalidation. Keep the complete game build cache; next build re-splits/compiles the required shader data automatically. No manual cache deletion. Installed apps retain their old host until rebuilt. | +| Pipeline archive indexes, compaction, task/mesh prewarm, memory mapping | Backend | New game host manages these. Offline `pso_offline` tools/log producer are unchanged in this delta, so the hotfix's confirmed-batch progress reader remains applicable. Startup prewarming is not the offline pipeline stage and is not included in that ETA. | +| Dynamic resolution now supports Ocarina | Existing UI | Engine capability-gated `launcher.json` supplies the row and updated copy. No extra global switch. Off keeps full resolution; on lets the game reduce resolution under load. | +| New 60/120 FPS profiles | Existing UI | Existing generic choice rows read actual options from each rebuilt app's launcher.json; no promise that a listed 120 FPS mode will be sustained on this Mac. | +| `DM_SW2`, Joy-Con 2 / Pro Controller 2 BLE | Existing Game options UI + backend | Rebuilt apps advertise the toggle in Controls; off by default. Upstream pack_app writes NSBluetoothAlwaysUsageDescription. Pairing, rumble, motion, dual/single Joy-Cons and the mouse sensor are implemented in the game host. New C callbacks apply to an embedded host frontend, which NX Library is not; no duplicate Bluetooth transport in the library UI. This does not add BLE controller navigation to NX Library itself. Physical devices still need manual validation. | +| Cloud send race protection | Backend | Upstream no longer overwrites a cloud save it could not compare at start or one changed afterward. Existing newer-save-wins behavior and backups remain when DM_SAVE_SYNC_CHOICE is unset. | +| `DM_SAVE_SYNC_CHOICE` / DMSaveSyncChoice | New conflict-selection UI not adopted in this hotfix | NX Library currently chooses cloud location and protects import/restore, but does not compare both cloud container headers/hashes or ask which conflicting save wins. Passing ask without implementing that complete workflow would silently prevent synchronization. Leave unset, retaining upstream's existing behavior and added race protection; no new conflict dialog is claimed. | +| `--icon` / ICON | Existing reversible artwork writer retained; direct build override not adopted for this candidate | The new engine accepts a JPEG/PNG before its control icon. Blindly passing a personal/online image would make it the packed app's apparent original and break Automatic/Only here restoration. Current post-install writer preserves the true original or its absence, supports queued choices and signing rollback. A direct-pack optimization needs explicit original provenance through new/held builds; it is not required to build with this engine and no avoided-signing performance claim is made. | +| `--shader-files`, iOS pulling libraries, M1 iOS CPU target, AFC push | Not adopted: outside macOS app scope | --shader-files is explicitly refused with --platform mac-app. NX Library does not package or push iPhone IPAs; no libimobiledevice dependency added. | +| Brackets in folder names; multi-program DLC indices | Backend | Upstream fixes glob/file discovery and DLC application-ID indexing. Source symlinks/workspaces remain unchanged. | +| New diagnostics / internal defaults | No main UI controls | DM_JCMS_LOG, DM_NVN_BC_ALIAS, DM_SW2_VERBOSE/gyro mapping, DM_YIELD_CLOCK are diagnostics. Runtime render-target reuse stays upstream's default. | + +## Limits and tradeoffs to keep visible + +- The slow-motion frame-step fix is reasoned from the affected games and code, but the upstream note explicitly leaves a before/after run on a sufficiently slow Mac open. Validate actual game speed, not only the FPS counter. +- OCTOPATH TRAVELER's upstream 60 FPS route has more CPU-side long frames than the 30 FPS route; the new cap is not a claim that stutter is resolved. Unity 120 FPS cases also depend on display/vsync behavior. +- BLE controller validation differs by device: Joy-Con 2 motion/pairing/dual/single have upstream physical checks; Pro Controller 2 gyro scale/axes are borrowed and its rumble was not physically certified in the referenced note. Later mouse-sensor support supersedes playing.md's earlier “not yet” statement. No local physical controller test has been performed here. +- Upstream states that a newly signed game build asks for Bluetooth access again. Our icon writer also re-signs the bundle; a repeated Bluetooth permission prompt after an icon change is therefore a possible interaction to verify, not a separately verified macOS guarantee. +- A chain change means a longer next rebuild even with the existing cache. Extraction/workspace data are retained; upstream may replace/prune obsolete generated chain libraries as part of its own normal migration. No manual game-cache deletion was performed. + +## Reviewed sources + +- Complete commit inventory, implementation-facing commit notes and all Front-ends notes in the fixed range; changed source contracts in scripts/build_ipa_from_nsp.py, dm/mac/pack_app.py, docs/frontends.md. +- Candidate script `-h` executed with Dev's bundled Python from a temporary read-only source snapshot. New arguments: --icon and --shader-files/--no-shader-files; mac-app/progress JSON/work-dir/title selection remain. +- docs/env_switches.tsv and settings.tsv delta, changed game README notes, changed playing/settings and reference docs (frame rate, memory, movies, NVN objects, packing, rendering, rewriter, services, shaders, threads). Game code/data tables are upstream-only. +- Python README install line and .github/requirements/tools.txt have no new dependency; no runtime.lock/NOTICE package update is needed. Diagnostic-only NumPy/Pillow/texture2ddecoder remain intentionally unbundled as documented by EngineRuntimeRequirementsTests. +- dmsh_chain.lock still certifies Metal 32023.922 for macOS and iOS. minimumXcode remains 27.2; the existing 27B47 asset is unchanged. Translator DLL fingerprints change, not the Metal asset. +- `game` event's icon may now be --icon rather than an embedded original; NX Library currently ignores this event and does not misclassify it as provenance. Other event contracts are unchanged. + +## Verification and adoption record + +- Full frontend suite with the candidate override passed **1,166 tests**, including PortingFailureEngineTests and EngineRuntimeRequirementsTests. The release script re-runs the suite against the now-updated source pin. +- Compatibility gate passed for exact HEAD 79741184, clean engine tree, Metal 32023.922, Python 3.14 and required CLI options. The same toolchain remains in use. +- Dev 0.1.4.2 was built and strict/deep signature verification passed during candidate evaluation. Final release packaging is validated by scripts/release.sh, which checks its ZIP with the same validator used by app updates. +- Actual candidate-generated launcher metadata decoded through NX Library: 18 Ocarina settings, including the off-by-default Switch 2 controller toggle and capability-gated dynamic-resolution control. Six 120 FPS profiles were generated and checked. Synthetic metadata is not a game/hardware test. +- Initial evaluation selected the candidate through NewestEngineSwitcher. The maintainer subsequently explicitly requested adopting the latest commit and publishing 0.1.4.2 without waiting for another game rebuild. The source pin is now 79741184. Existing cached upstream status is rejected when its base pin differs; a Newest choice already included in the pin is retired by the existing keeps rule. No automatic following of main was introduced. +- A real build and visible game launch on exact 79741184 remain **unconfirmed**. The previous installed Ocarina build is evidence for 7ffa63cc only. This release does not claim local before/after performance or physical controller verification. + +## Existing Ocarina build discovered before asking for another rebuild + +The maintainer pointed out that Ocarina had already been rebuilt near main. Inspection confirms **7ffa63cca09f718d6c9deb830656291b608e4ddb** in both the library build record and the installed app's DMCommit (`7ffa63cca09f`). Its successful build ended at 2026-11-09 09:08:32 UTC (Metal 32023.922, Xcode 27.2, recorded as Newest/untested). No rebuild is needed merely to see the major startup/memory/shader improvements listed above: this build already includes the first 66 of the 71 reviewed commits. + +Only five commits follow that installed engine: samples-passed counter correctness (cfbdd93c), bracketed-folder discovery (cb4a97a6), multi-program DLC indexing (d9cd9de7), Core Haptics exception recovery (7238bfd1), and H.264 Annex B packet framing (79741184). **No shader chain or Metal lock change exists in this five-commit range.** The counter and haptics fixes can affect shared runtime paths, so they are not dismissed as irrelevant to Ocarina; however, they do not justify automatically asking the maintainer to repeat the whole expensive build for the already-present improvements. A real build and visible launch on 79741184 are still required before reporting that exact hash as manually game-validated; a smaller suitable game can serve that check. Existing 7ffa63cc gameplay cannot be reported as 79741184 gameplay. + +## 120 FPS integration check + +Six current profiles explicitly declare fps_max 120: DRAGON BALL: Sparking! ZERO (010035F022078000), Mario & Luigi: Brothership (01006D0017F7A000), The Rogue Prince of Persia (01008D9022462000), Minecraft Dungeons II (0100A7C01B792000), Pikmin 4 (0100B7C00933A000), Fire Emblem Fortune's Weave (04007EF01F8AE000). Running the actual candidate settings generator for all six confirms **30 / 60 / 120** choices; Ocarina's generator and its installed launcher.json both offer **30 / 60**. + +NX Library's generic Frame rate row already displays the build's choices and writes the exact string to saved settings. Before launching a modern DMSettings app, it migrates those choices and passes no legacy 30/60 FPS environment override. A focused regression check verifies that a saved 120 choice survives unrelated option edits and the same migration called before launch. No new 120 FPS control or global override is needed. Per-game versions/hardware/refresh-rate limits still follow upstream's profile notes; actual 120 FPS gameplay and visual selection on the maintainer's hardware are not newly certified by these metadata checks. + +## Focused maintainer check + +1. After updating/reopening, verify Settings → Engine uses pin 7974118 and the expected Metal compiler. A Newest choice at this same commit is retired; an override the player enabled independently remains their explicit choice. +2. Do not repeat Ocarina just for changes already present in its 7ffa63cc build. To complete manual validation of 79741184, build one suitable real game with the existing cache (a smaller title is acceptable); observe pipeline progress, finish, saved log and icon location. +3. Launch the newly built game to a visible screen. For Ocarina-specific evaluation, use the existing 7ffa63cc app or a later deliberately selected rebuild, load the usual save and enter Kokiri Forest. Check lighting/fog, foliage, terrain and movement speed. Keep save/route/settings comparable; fixed full resolution versus Dynamic resolution are separate comparisons. +4. Quit and launch again twice: assess startup and first-entry stutters. The initial shader/build and a cold first prewarm should not be confused with subsequent starts. Record elapsed startup/memory only if comparable, without promising upstream's figures. +5. Optional with hardware: Game options → Controls → Switch 2 controllers; enable, pair by Sync while the game runs, check controls/Home/rumble. macOS asks Bluetooth per newly signed game build; the game host handles this, not NX Library. + +After a future real build/launch: record the actual build/toolchain/log and human-visible launch; do not retroactively claim that adopting the pin performed those checks. Pin, README, architecture and changelog are updated for the maintainer-directed release. Rollback uses Back to tested; installed game apps keep their engine until rebuilt. A rollback changes the shader chain again. + +## Every commit classified (oldest first) + +| Commit | Area | Upstream change | +|---|---|---| +| [4f47d211](https://github.com/seedds/nxnative/commit/4f47d211) | Host / runtime | ROMANCE OF THE THREE KINGDOMS XIV: Diplomacy and Strategy Expansion Pack plays on the Mac (1.0.10 with its DLC, from the provisional profile unchanged); the deferred-release drain no longer reads entries it has handed back to the queue: with g_smu busy it sorted its list again after returning the retired resources, and another drain could free them in between (a crash in class_conformsToProtocol from a completion handler, the game's main menu) | +| [51ecd693](https://github.com/seedds/nxnative/commit/51ecd693) | Shader / pipeline | Colour targets are write-masked per component by the pixel shader's output map (SPH word 18, now in the reflection's raster facts): the translator stores only the mapped components and Metal wrote the rest of the attachment undefined; a target's load is skipped only when the map covers every channel its format stores (chain 094f939bfef9; reported by Lichii on Ocarina of Time's depth pass, not reproduced here) | +| [ea0c98df](https://github.com/seedds/nxnative/commit/ea0c98df) | Host / runtime | Honor NVN queue dependencies and preserve conditional state across continuations | +| [7d8dcdf8](https://github.com/seedds/nxnative/commit/7d8dcdf8) | Game profiles / frame rate | Ocarina of Time and Mario Kart World no longer play in slow motion when frames run long: their frame step is the host's (fps_target 60), from stubs placed by the function they patch on any build that keeps it (MATCH_FRAME_STEP, nso_match.locate_stub), a game binding where frame-step stubs were build-bound; Ocarina's frame ratio is the host's step x its base rate / 60, Mario Kart World's interval the step (at most 2) unless its own 30 fps mode is set (both places named by Lichii, facts only) | +| [e940e1f3](https://github.com/seedds/nxnative/commit/e940e1f3) | Game profiles / frame rate | The Rogue Prince of Persia plays on the Mac (1.1.0, Unity 6000.0.64f1 IL2CPP, from the provisional profile unchanged in its keys): the title, the first-run options, the opening narration and movie, the first level with running, jumping, climbing, the weapon and the wall run; 30 fps as the game asks, sound in the game's mix, no host change | +| [eed7c429](https://github.com/seedds/nxnative/commit/eed7c429) | Shader / pipeline | A pipeline archive flush period is handed over at its 2,000th capture, during a compacting prewarm too, and a pipeline the capture refuses is built by the plain compiler: one period of 8,693 bases filled Metal's archiver store (MDB_MAP_FULL, a 10 MB LMDB map per serializer) and Ocarina of Time's Kakariko Village lost 1,502 pipelines at 30-70 ms a frame | +| [555122a9](https://github.com/seedds/nxnative/commit/555122a9) | Shader / pipeline | Reads through a uniform buffer's handle table are no longer marked as texture pool reads: the mark went through the bitmap address in the table's header, which htab_gpu leaves 0, and Fire Emblem Fortune's Weave's lighting pass at Alcineus Bridge took 196.7 ms a frame on an M1 Pro (4 fps); a table's index is its element, not a texture ID, and the host has made every texture the table names (chain c096b1d1dc53; found by HammerChain in PR #1738) | +| [a00f03bb](https://github.com/seedds/nxnative/commit/a00f03bb) | Input / display / audio | Mac: one Pro Controller is one pad: IOHIDManager hands a device over once per matching dictionary it meets, so the HID reader knows a pad by its I/O Registry entry, not by its IOHIDDeviceRef (PR #1722) | +| [62f6a52c](https://github.com/seedds/nxnative/commit/62f6a52c) | Game profiles / frame rate | The Rogue Prince of Persia plays at 60 fps and up to 120 on the Mac: Unity's QualitySettings.vSyncCount and Application.targetFrameRate are found through the engine's internal-call table in any Unity build that keeps it (unity_fps.py; Unity 6000.0 and 2021.3 read), and the host writes them; the display's vsync event runs at 120 Hz while 120 fps holds, as Unity's NVN device waits for each vblank (Sparking! ZERO and Pikmin 4 unchanged at 120) | +| [1b9775a0](https://github.com/seedds/nxnative/commit/1b9775a0) | Shader / pipeline | The HMMA rewrite finds each function's opening and end once: Ocarina of Time's 71 HMMA shaders took 932 s of CPU in hmma_msl, now 3.5 s, with byte-identical output (chain 579d06bf3932; PR #1711) | +| [1794a35a](https://github.com/seedds/nxnative/commit/1794a35a) | Docs / tests | Fix patch registry test namespace types and shrink the lint baseline | +| [651369bc](https://github.com/seedds/nxnative/commit/651369bc) | Build / extraction / CLI | An IPA can carry the shader library as its separate files, for install tools that cannot copy a large file: --shader-files in the NSP script (SHADER_FILES, DM_SHADER_FILES=1 for build_ipa.sh, pack_ipa.py --shader-files), off by default. AltServer copies each file to the device with one afc_file_write, which libimobiledevice sends with one send() returning an int, so Tears of the Kingdom 1.4.3's 2.10 GiB b-<chain>.img failed to install (#1688); the host reads a library folder before an image (PR #1697) | +| [04419bc2](https://github.com/seedds/nxnative/commit/04419bc2) | Shader / pipeline | Ocarina of Time runs at full resolution, and its dynamic resolution no longer sits at its floor: 128-bit raw global loads are one vector load (GPU 16.3 -> 12.6 ms on an M4 Pro), occlusion conditions are read once landed (-0.6 ms), a frame-start timestamp from a slot its encoder never wrote is dropped, and a wait on a queued WRITE event signal is released at encode (chain 89f44f9b6bb0) | +| [c78d7375](https://github.com/seedds/nxnative/commit/c78d7375) | Docs / tests | Fix loop capture in frame-step registry test | +| [b62b1c54](https://github.com/seedds/nxnative/commit/b62b1c54) | Cloud saves | Cloud saves: a send never replaces a cloud save the start did not compare, and a front end can ask the player when both changed: DM_SAVE_SYNC_CHOICE (ask, cloud:<hash>, local:<hash>, local:none, offline); without it the newer save still wins (PR #1785) | +| [3cbe7a6f](https://github.com/seedds/nxnative/commit/3cbe7a6f) | Docs / tests | README: questions and results go to GitHub Discussions, with Ocarina of Time's performance followed in discussion #1789; the acknowledgements move to the footer | +| [ab20f4af](https://github.com/seedds/nxnative/commit/ab20f4af) | Game profiles / frame rate | Minecraft Dungeons II plays at 60 fps, up to 120 on the Mac: its config sets rhi.SyncInterval 2 and t.MaxFPS 0, and its bUseFixedFrameRate is off, so the present interval the host writes (found by name) is all it needs; the same walk at 30, 60 and 120 ends at the same spot (frame time p50 33.4 / 16.7 / 8.3 ms on an M4 Pro), and the user judged the speed right at 120. Its UE 5 FixedFrameRate reflection entry has a layout ue_fixed_rate.py does not read (open; no write needed here) | +| [028a2631](https://github.com/seedds/nxnative/commit/028a2631) | Input / display / audio | Luigi's Mansion 3 holds 60 fps again: in display mode nvnWindowGetPresentInterval answers the interval the window presents at, so the game no longer paces itself for its own interval 2 now that vi::ListFrameHistory lists the presents | +| [8bdfcb73](https://github.com/seedds/nxnative/commit/8bdfcb73) | Host / runtime | Clock waits sleep to their predicted end: a yield loop that reads the clock backs off from its start and, at a steady period, sleeps on time to just before its next end; Three Houses' frame-limiter cut is removed (issue #635) | +| [877c7eca](https://github.com/seedds/nxnative/commit/877c7eca) | Shader / pipeline | Ocarina of Time's mist no longer flickers with dark specks: a pixel shader comparing gl_FragCoord.z with a depth it read takes that depth as its own z when it lies in the gap Apple's GPU leaves between the depth it stores and the fragment's [[position]].z (chain a02f1e0dfdde) | +| [8e4a993b](https://github.com/seedds/nxnative/commit/8e4a993b) | Shader / pipeline | gl_FragCoord.z against the depth stored for its own surface: an M6 stores exactly the fragment's z (check_t_zsnap: 0 of 718,400 pixels, against 9,511 on an M4 Pro), so the mist specks were the M4 generation's; M1-M3 and iPhones not measured | +| [eddae74f](https://github.com/seedds/nxnative/commit/eddae74f) | Host / runtime | Event descriptors are numbered as the bsd service numbers them, the lowest free one from 0, and Select is Poll over its sets: Minecraft's NetherNet put its descriptor (0x4000) in a 1024-bit fd_set on the stack and crashed 2 KB past it (PR #1651) | +| [37f076a5](https://github.com/seedds/nxnative/commit/37f076a5) | Shader / pipeline | Extended products leave spirv-cross's optnone helper: scalar uint and int calls of spvMulExtended reach inline overloads that take the halves of the 64-bit product, so the compiler folds Switch 2's constant divisions (Ocarina of Time: 348,587 calls, 98% by a power of two); on an M4 Pro the Kokiri Forest deck route ran at 59.1 fps instead of 54.0, GPU 15.55 -> 14.76 ms, and the title movie's GPU 12.96 -> 12.44 ms (chain 39417042cfe8; PR #1741) | +| [8fd4b724](https://github.com/seedds/nxnative/commit/8fd4b724) | Game profiles / frame rate | OCTOPATH TRAVELER plays at 60 fps: the rewriter finds UE 4.18's console variables registered by inlined static constructors, which address each object from a page base made far before the name (str x8, [x22, #k] the vtable, then str x0, [x22, #k + 0x10] the value pointer); the base is taken back to its adrp + add across forward branches over else blocks, and nothing is found if another write or a branch from outside lands between (issue #635) | +| [7b6506dd](https://github.com/seedds/nxnative/commit/7b6506dd) | Host / runtime | A dispatch fills its compute bindings before it opens an encoder: a binding that upgrades its texture for a view of another format copies the pixels through the copy encoder, which opened a second encoder beside DM_NVN_DISPATCH_OWN's (Ocarina of Time crashed in AGX prepareForEnqueue at the Kokiri Forest load) and by default landed after the dispatch's barrier, so the shader could read the old pixels (PR #1755) | +| [9b69d27d](https://github.com/seedds/nxnative/commit/9b69d27d) | Host / runtime | A WaitSync on a sync no fence had reached when it was submitted passes: Sparking! ZERO's single queue parked for good at its first load after the title, and its RHI interrupt thread's SyncWait stalled the game | +| [3b6b9803](https://github.com/seedds/nxnative/commit/3b6b9803) | Docs / tests | t_present_crop presents through a real queue again: since ea0c98df a QueuePresent goes through the queue scheduler, and the fixture's null queue crashed it (SIGSEGV at nvn_queue.inc:207); the fixture now holds replay with nvn_queue_test_hold while the window or layer crop changes, checks that the held replay queues nothing, waits for the frame's display ticket, and runs with present snapshots and the encode thread each on and off (PR #1757) | +| [50aefc12](https://github.com/seedds/nxnative/commit/50aefc12) | Game profiles / frame rate | Fire Emblem Engage and Pokémon Brilliant Diamond lose their build-bound patches: their SDK rows give way to the SDK-wide rules (node area and 2 MB alignment), Engage's vSyncCount cut to the host's write of the level's count (fps_target 60), and Brilliant Diamond's fixed targetFrameRate row to unity_fps.py, which now reads Unity 2019.4 / 2017.4's internal calls registered from code and their framed manager getters (issue #635) | +| [7de11da5](https://github.com/seedds/nxnative/commit/7de11da5) | Game profiles / frame rate | Ocarina of Time's dynamic resolution climbs to full size: with DM_DYNAMIC_RESOLUTION=1 a stub in place of the game's level function climbs on the load measured at each level, instead of the game's extrapolation by pixel count that kept it at 683x384 for good | +| [7f502220](https://github.com/seedds/nxnative/commit/7f502220) | Shader / pipeline | Render targets in a GPU-only pool are placed in heaps of their own, and a freed target's heap waits resident for the next target it fits: Ocarina of Time's dynamic resolution, free to move since 04419bc2, made about 3 new Metal textures a frame in its 305 MB target pool; on an M6 (16 GB), alternating the switch every 300 frames while the resolution moved, the target site added 455-521 MB to the residency set per 300 frames off and 27-37 MB on, frame p90 33-50 ms against 17-18 ms (issue #1831) | +| [35b6b78c](https://github.com/seedds/nxnative/commit/35b6b78c) | Host / runtime | A sampler keeps its state from byte 8 on: Super Mario Odyssey's sampler cache (main+0x980e20, 1.4.1) keys its entries by a CRC-16 (0xA001) of bytes 8 to 0x5f of the NVNsampler alone, and the filter and wrap modes at bytes 4 to 7 made samplers that differ only in them one entry; from the regress route's save it registered 168 states 3,368 to 5,737 times instead of 168 (151,626 of 276,480 initializations met a CRC another state had, none now). nvnSamplerInitialize writes every byte of the record; t_nvn.c checks equal states give equal bytes and filter or wrap alone a different CRC (first reported by the S2Arm project) | +| [6059cbab](https://github.com/seedds/nxnative/commit/6059cbab) | Game profiles / frame rate | The last four SDK rows give way to the SDK-wide rules: Ace Attorney Investigations Collection's (311deab1), Brain Memory Training Academy's (12f16103) and Patrick's Parabox's (9c73fb73) builds now take the 2 MB alignment beside the node area cut, and Hollow Knight: Silksong's (e01950c5) rewrites byte-identical; only SDK 4.4.0's row is left (issue #635) | +| [d8b2b64a](https://github.com/seedds/nxnative/commit/d8b2b64a) | Docs / tests | Fix CI lint and fixture failures after the queue and shader updates | +| [2c2c5491](https://github.com/seedds/nxnative/commit/2c2c5491) | Game profiles / frame rate | Engine-wide rewriter rules replace three games' build cuts: Unity 2019's ALLOC_BUCKET block limit found by the allocator's name (unity_bucket.py; Subnautica and Below Zero), and Unity 2018's ThreadedStreamBuffer load-acquires matched by function in any main (engine_patches.py, loaded like sdk_patches.py; FINAL FANTASY CRYSTAL CHRONICLES 1.0.0 and 1.0.3, Raging Loop) (issue #635) | +| [56e4f2ab](https://github.com/seedds/nxnative/commit/56e4f2ab) | Game profiles / frame rate | Remove the three patches.py files the engine-wide rules of 2c2c5491 replaced (Subnautica, Subnautica: Below Zero, FINAL FANTASY CRYSTAL CHRONICLES Remastered Edition), left out of that commit (issue #635) | +| [bf420fda](https://github.com/seedds/nxnative/commit/bf420fda) | Shader / pipeline | Native task / mesh pipelines are recorded, prewarmed and kept in the pipeline archive: Ocarina of Time's foliage, shadows and enemies no longer stall at their first draw (59.5 s of builds at the Kokiri Forest entry on an M5 Max with a cold Metal cache, one frame of 42.9 s), and its profile seeds 123 of them (PR #1368) | +| [e3ad8255](https://github.com/seedds/nxnative/commit/e3ad8255) | Host / runtime | Every exclusive access gets ordering: the rewriter sets bit 15 of each ldxr* / ldxp and stxr* / stxp (acquire on the load, release on the store), by encoding in every module of every game, instead of acquire for one lock shape alone, which matched none of the 123 modules on the game drives. On an Apple M6 a plain store published with stxr or stxp was read stale 12,858 and 10,961 times in 100 million rounds, never with stlxr or stlxp, and an atomic increment cost the same either way. Rewriting all 123 modules with and without the rule changes only those bits (1,045,422 in 78 modules, plus 1,499 copies in exclusive windows); Fire Emblem Engage and Super Mario Odyssey run the same with the modules swapped. The words stay out of changed.tsv, so cheats may still write them (first reported by the S2Arm project) | +| [4139d41a](https://github.com/seedds/nxnative/commit/4139d41a) | Build / extraction / CLI | The iOS app is built for the M1, the oldest chip it supports, not the A18: AV1 movies no longer stop an M1 iPad with SIGILL in dav1d; and push_romfs.sh no longer empties the app's container, saves included, and sends the image over AFC, as devicectl reads a whole image into the Mac's memory (PR #1829) | +| [09dcb627](https://github.com/seedds/nxnative/commit/09dcb627) | Shader / pipeline | A mesh shader with no task shader launches its workgroups through an object function of its own, in blocks of 1024: Metal holds a mesh grid to 1024 threadgroups, and with no object stage a draw's workgroups were the mesh grid, so Ocarina of Time's foliage drew up to 1,771 in one indirect draw, read out of bounds and page faulted the GPU on an M2 Max (issue #1326). The draw stays one Metal draw with no limit of its own (chain 38c7b39dc6fd; PR #1708) | +| [270b6fd5](https://github.com/seedds/nxnative/commit/270b6fd5) | Host / runtime | A vertex stream in no memory pool is a red line instead of an earlier draw's buffer, and SetNumActiveTextures limits the swap chain textures AcquireTexture hands out: bind_vbufs reports the first stream the vertex shader reads whose non-zero address is in no pool, after binding the others, and the draw is skipped as for an index buffer in no pool (a transform feedback capture and the vertex dump too; a pulling pipeline reads zeros; a geometry capture, which shares its encoder with the job's later stages, logs and keeps the old binding). The window keeps the active count (win_t / win_builder_t nactive), the acquire rotates through it, and its getters answer it; every game in the Mac's run logs asks for its whole swap chain, and the S2Arm project reports Super Mario Bros. Wonder sets 2 of 3. t_vbuf and t_nvn check both (first reported by the S2Arm project) | +| [dbd0a5ec](https://github.com/seedds/nxnative/commit/dbd0a5ec) | Docs / tests | Pause tests (red): a thread paused inside an SVC keeps paused set after the resume and leaves by the fast path, so the next SetThreadActivity(pause) returns while it runs guest code and GetThreadContext3 reports an all-zero context (t_pause cases 2-3; t_pause_stress, the stop-the-world pattern of IL2CPP's collector) | +| [e00d55d4](https://github.com/seedds/nxnative/commit/e00d55d4) | Host / runtime | A resume clears paused, and a thread waiting for it in the pause signal handler sets it again on every wakeup, as the SVC exit does: a thread paused inside an SVC left by the lock-free exit with the flag still set, so the next SetThreadActivity(pause) returned while it ran guest code and GetThreadContext3 gave an all-zero context (Aokana: FMOD's stream thread decoded through IL2CPP's liveness marks and crashed in BufferedReadStream.Seek); clearing alone would leave a pause that comes before the woken handler retakes G waiting for ever | +| [a1e155a3](https://github.com/seedds/nxnative/commit/a1e155a3) | Shader / pipeline | The pipeline archive bundled with the app is mapped too, not read into memory: it opened before the window in which a .mtl4a read asks for a mapping, so on an external drive Foundation read the whole file into memory (Ocarina of Time: footprint 19.0 → 12.2 GB, Malloc Large 5,032 → 135 MB, one 4.8 GB NSData). One window now covers every archive open in pso4_init, the bundled archive and the device's folder (PR #1817) | +| [a342d640](https://github.com/seedds/nxnative/commit/a342d640) | Input / display / audio | Switch 2 controllers over Bluetooth LE on the Mac (Joy-Con 2, Pro Controller 2): an off-by-default setting, pairing by Sync during the game, motion, and Home through the hold-to-quit prompt (issue #1267) | +| [8c436e33](https://github.com/seedds/nxnative/commit/8c436e33) | Input / display / audio | Rumble for Switch 2 controllers over Bluetooth LE on the full-key handles, coded as the console codes it: logarithmic frequency codes, the sample count, each of the game's values in its own 5 ms sample (2 of 3, issue #1267) | +| [36e6e4ae](https://github.com/seedds/nxnative/commit/36e6e4ae) | Input / display / audio | Joy-Cons as dual or single npads: a pair is a JoyDual npad with a sensor per Joy-Con, splits when the game asks, and the Switch 2 Joy-Cons choose by a three-second hold (3 of 3, issue #1267) | +| [413e8808](https://github.com/seedds/nxnative/commit/413e8808) | Input / display / audio | The Joy-Con 2's mouse sensor and pointer: a front end reports each Joy-Con's sensor (dm_joy_mouse), the game reads it as JcmsStates and as an Nmouse pointer, and a Joy-Con 2 pad shows the Joy-Con 2 npad styles (issue #1267, after #1363) | +| [3376af3c](https://github.com/seedds/nxnative/commit/3376af3c) | Shader / pipeline | A 64- or 128-bit raw global load that misses every pool reads 0 in all its words again, and a vector load's follower scan takes only its own LDG's words: the vector helper returned 0 for word 0 but never wrote gmem_vec, so words 1..3 read the previous load's words or undefined data, and with two LDG.128 in one block a word of the second could read the first's (chain 5628839860cf, PR #1813) | +| [f7a54525](https://github.com/seedds/nxnative/commit/f7a54525) | Shader / pipeline | A shader package's records carry their keys, and dmsh_build.py finds a map row's record by its key, not by the row's index: a package from another shader_split.py run than the map translated every row after a dropped or added one from its neighbour's bytes (issue #1841: Ocarina of Time built through NX2Apple kept ps_lib.bin's 4,711 records beside a map of 4,707 rows, and the present pass ran a terrain pixel shader, a black screen with sound); such a package now stops the shader build (chain 3df44a5a4e0c) | +| [7479adf8](https://github.com/seedds/nxnative/commit/7479adf8) | Game profiles / frame rate | Reuse checked Unity bucket instruction decodes | +| [4a815619](https://github.com/seedds/nxnative/commit/4a815619) | Shader / pipeline | A prewarm writes the pipeline archive a file per full period, not a file per 10 s timer flush, and what the archive holds unwritten is written at quit and starts writing on going to the background: on Ocarina of Time a cold start left 80 run files, so the next start compacted them all, 762 s before the first frame with 0 compiled in the log; now the cold start leaves 5 files and the next start takes 3.3 s (PR #1879, issue #1747) | +| [3870fcc0](https://github.com/seedds/nxnative/commit/3870fcc0) | Host / runtime | GPU copies and render passes into a 3D texture uploaded from its storage are written into its storage slice by slice, as for 2D textures: nvn_tex_mirror_b2t and nvn_tex_mirror_t2t refused 3D, and copy_b2t, copy_t2t and rt_mirror_add skipped the readback for it, so a 3D copy reached only the host texture (issue #1856: LEGO Star Wars: The Skywalker Saga fills its BC6H light-probe volumes a slice at a time with buffer copies, then moves them into new textures by copying their storage; the moved volumes held the pool's old bytes and every dynamically lit object came out at the half-float maximum, white). write_region_bl takes the depth slice (nvn_bl_offset already interleaves slices by the level's block depth), a region's images are the depth slices of a 3D texture's one layer (region_image), the readback reads a 3D texture's slices at origin z, and WriteTexels writes a 3D region in place as well | +| [143c0d09](https://github.com/seedds/nxnative/commit/143c0d09) | Game profiles / frame rate | Hyrule Warriors: Age of Calamity v458752 plays at 60 fps and Tears of the Kingdom's frame step is game-bound: both are placed by their functions (MATCH_FRAME_STEP with others), a matched stub reads the globals it needs from its functions' adrp pairs (ctx.address), and a derived profile keeps fps_target when the profile has a function-matched frame step (issue #635) | +| [4c3a03af](https://github.com/seedds/nxnative/commit/4c3a03af) | Shader / pipeline | Pipeline archive compaction takes run files by size tier from the pipeline count in their names, never a full period's file: on Ocarina of Time 9 files of play beside a compaction's 5 made the next start capture 8,543 archived pipelines again, 713 s before the first frame on an M4; now that start takes 23.6 s, compiling only the 262 bases new to the list (PR #1880) | +| [d902bc66](https://github.com/seedds/nxnative/commit/d902bc66) | Docs / tests | t_render_enable_queue and t_render_counter_overlap turn the profile key render_cond on themselves: they check its replay (render-enable blocks, pending counter values), which a profile without the key leaves out by design, so under Breath of the Wild's profile 9 of 46 and 1 of 42 checks failed while Ocarina of Time's passed | +| [a9ee2476](https://github.com/seedds/nxnative/commit/a9ee2476) | Game profiles / frame rate | NieR:Automata's cuts and host table stay build-bound, kept: none of their sites has an exact match in ASTRAL CHAIN or Bayonetta 1-3, so game is their loosest binding, and 1.0.3 is likely both releases' last update; moving the host table would also need matched rules that write fixups (issue #635) | +| [e4e5ff64](https://github.com/seedds/nxnative/commit/e4e5ff64) | Shader / pipeline | Translated shaders compile with -fwrapv: Ocarina of Time's mesh-shader models no longer take their ambient light from the wrong probe texels (the bright band on a dark wall in Gohma's room) | +| [7a7acfee](https://github.com/seedds/nxnative/commit/7a7acfee) | Build / extraction / CLI | An iOS build keeps the pulling vertex libraries whatever the profile's pso_mtl4 says: the launcher's Metal 4 pipelines toggle turns the path on for any NVN game, and its prebuild builds the bases from them, so on Echoes of Wisdom both toggles built nothing (PR #1758, issue #1684) | +| [2ae28018](https://github.com/seedds/nxnative/commit/2ae28018) | Docs / tests | docs: list community front ends with short descriptions | +| [70b9a68a](https://github.com/seedds/nxnative/commit/70b9a68a) | Shader / pipeline | Each pipeline archive file has an index beside it, so a lookup asks the one file that holds the pipeline and a miss asks none, whatever the number of files: a lookup asked every file in turn (about 0.06 ms each one it missed in), which is why a few files had to make a start a compaction that compiles every base again under the #342 workaround (Ocarina of Time on an M4: 713 s after play, PR #1880; 762 s after a cold start's 80 files, issue #1747) | +| [3303b5a7](https://github.com/seedds/nxnative/commit/3303b5a7) | Build / extraction / CLI | The prebuild stamp names a library file the run lacked, and counts as done only while the library still lacks it: an IPA packed without the pulling libraries, run with DM_PSO_MTL4=1, stamped its prebuild as finished, so an app that gained them never built their bases (PR #1177) | +| [6cbb87c9](https://github.com/seedds/nxnative/commit/6cbb87c9) | Docs / tests | README lists Switch 2 games without screenshots | +| [fed627bc](https://github.com/seedds/nxnative/commit/fed627bc) | Build / extraction / CLI | The NSP script takes an app icon: --icon / ICON, a JPEG or PNG of any size, before the control data's icon, which a dump never has and a NUP may lack; such builds had a blank icon and no way to give them one (an icon put in extract/<version>/ went with the next fresh unpack). The path is checked before anything is extracted (PR #1551) | +| [06973c5f](https://github.com/seedds/nxnative/commit/06973c5f) | Shader / pipeline | Tears of the Kingdom's vertex-shader visibility counter stays a single-shader rule, kept: the shader-build logs of the 51 game builds on the drive show no shader failing to translate or compile and no subgroup operation, so a general ballot lowering would be a chain change for no measured gain (issue #635) | +| [e82888e6](https://github.com/seedds/nxnative/commit/e82888e6) | Docs / tests | README puts community front ends directly below the games list | +| [7ffa63cc](https://github.com/seedds/nxnative/commit/7ffa63cc) | Docs / tests | Bananza records the community iPhone test from issue #1183 | +| [cfbdd93c](https://github.com/seedds/nxnative/commit/cfbdd93c) | Host / runtime | The samples-passed counter is one running total in queue order: a game that never resets it reads its occlusion queries again (Red Dead Redemption's distant characters had no bodies), and a report a submission after its reset no longer reads slots a later submission overwrote (PR #1867) | +| [cb4a97a6](https://github.com/seedds/nxnative/commit/cb4a97a6) | Build / extraction / CLI | A folder whose name has brackets no longer hides files from the tools that search it: the rewriter loaded no game's patches.py (no cuts, observation gates or frame-step stubs, and no error), the matcher found no registered cuts, a provisional profile was guessed with no host hooks, and the shader split found no NRO captured from a boot (PR #1826) | +| [d9cd9de7](https://github.com/seedds/nxnative/commit/d9cd9de7) | Build / extraction / CLI | A program's DLC are numbered from the application's title ID: a program packed alone (--program N) or with its launcher had every DLC image N below its index, so METAL GEAR SOLID: Peace Walker - Master Collection Version's program 1 mounted the Pre-Order Bonus where it asked for the Multilingual Pack and ended at start (PR #1874) | +| [7238bfd1](https://github.com/seedds/nxnative/commit/7238bfd1) | Host / runtime | A Core Haptics engine that stops under a call raises an NSException: the rumble adapter drops it as a failed engine instead of ending the app (PR #1694) | +| [79741184](https://github.com/seedds/nxnative/commit/79741184) | Host / runtime | H.264 movie packets are read as Annex B only: a 4-byte NAL length from 256 to 511 begins 00 00 01, so the length-prefixed fallback could not be told from a start code (issue #629) | + +Latest automatic verification after the 120 FPS persistence check: **1,166 tests, zero failures** against candidate 79741184. No production behavior changed in this follow-up; no game was rebuilt or launched by the agent. diff --git a/docs/upstream-upgrades/README.md b/docs/upstream-upgrades/README.md index 0945ee2..7c8d74e 100644 --- a/docs/upstream-upgrades/README.md +++ b/docs/upstream-upgrades/README.md @@ -76,3 +76,7 @@ This directory records the review that must happen before the pin changes, plus - [2026-11-07 — b2d46b86 → 7fe9fc4d](2026-11-07-b2d46b86-7fe9fc4d-review.md) (0.1.4.1: Ocarina of Time rebuild confirmed by the maintainer without the dotted rows; terrain holes and white blotches fixed upstream) - [2026-11-07 — 7fe9fc4d → e5f03403](2026-11-07-7fe9fc4d-e5f03403-review.md) (0.1.4.1: Ocarina of Time rebuild confirmed by the maintainer; packages read by their content metas, NSP with keys still to check by hand) - [2026-11-08 — e5f03403 → bd979ced](2026-11-08-e5f03403-bd979ced-review.md) (0.1.4.1 candidate: Ocarina's foliage no longer flashes white, Pikmin 4 at 60 fps; shaders translate again; awaiting the maintainer's rebuild) + +## Maintainer-directed adoption + +- [2026-11-09 — bd979ced → 79741184](2026-11-09-bd979ced-79741184-review.md) (0.1.4.2: 71 commits; adopted at the maintainer's explicit request after automated review/gate checks; exact-hash real game build/launch remains unconfirmed). From d2729fef35a387dc9b9de1b7081e122b476d2241 Mon Sep 17 00:00:00 2001 From: Bum <Bium23@users.noreply.github.com> Date: Mon, 9 Nov 2026 12:12:59 +0100 Subject: [PATCH 4/4] docs: update project status to 0.1.4.2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 787fc68..3221ba6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Game files, keys and firmware are not provided. See [Legal and third-party notic ### Project status -**0.1.4.1 alpha.** Download the packaged app from [Releases](https://github.com/Bium23/NxLibrary/releases/latest), or [build it from source](#building-from-source). +**0.1.4.2 alpha.** Download the packaged app from [Releases](https://github.com/Bium23/NxLibrary/releases/latest), or [build it from source](#building-from-source). Automated checks cover the application code, publication inputs, compilation and development-app packaging. They do not certify game compatibility. Changes are recorded in the [changelog](CHANGELOG.md), and the current checks are available under [Actions](https://github.com/Bium23/NxLibrary/actions/workflows/ci.yml).