Repository navigation
Description
Graphical front ends come up almost daily: #104, #244, #264, #332, #374. NX2Apple already builds games, keeps a library and starts them with their settings. To follow main, it re-applies patches to nine of our files on every sync (sync_upstream.py). I think the useful work here isn't another GUI in this repository. It's a clear interface between the engine and any app around it, so front ends can build on nxnative without forking or patching it.
Three layers
- The game's own app (
front_mac.m,front_ios.m, the iPhone's settings screen). Everything that has to appear while a game runs lives here, because no other app is on screen then: a controller that went away, "Preparing shaders", a thermal hint, an error. - The interface between the engine and its front ends, versioned, documented and tested:
- in:
settings.tsv→launcher.json→ environment switches (exists); - out while building: the NSP script's
--progress jsonevents (exists); - out while a game runs: missing. Today the host reports only in its log, as text meant for people.
- in:
- Front ends outside this repository: building, a library, launch options, artwork, updates. NX2Apple is one.
Steps
- The Mac app carries its settings list,
Contents/Resources/dm/launcher.json(Mac app carries the launcher settings; shader_split.py reports its progress #352, merged). NX2Apple's launch options already read it. - Document the interface:
docs/frontends.md, and NX2Apple at the top of the README (README points players to NX2Apple at the top; docs/frontends.md says what a front end builds on #387, merged). - Point people to front ends before they open an issue: NX2Apple first in the issue chooser, a "Graphical front ends" section in CONTRIBUTING.md (Add issue forms, a pull request template and CONTRIBUTING.md #306). Landed as 81d53bf.
- Choices that hold however the app is started. (landed as 6d1e07b, Saved choices: one store and one order for every way a game starts (#388) #431:
launcher.<VAR>in the app's preferences domain; order: launch environment, the player'senv.txt, the bundle'senv.txt, saved choices, tested defaults;DM_SAVED_CHOICES=0ignores them) The iPhone app keeps a player's choices in its own preferences,launcher.<switch>(only those that differ from the tested default). A Mac app has no such store, so choices made in a front end apply only when that front end starts the game, and a start from Finder or the Dock uses the defaults. Proposal: the Mac launcher (dm/mac/launcher.c) reads the same keys from the app's preferences domain (org.nxnative.<title ID>, CFPreferences, no Swift needed), with the order the iPhone has: environment, thenenv.txt, then stored choices, then defaults. A front end writes them there, and an in-app window, if Mac: a settings window before the game starts (resolution, display mode, frame rate) #374 gets one, would use the same store, so the two never disagree. - Runtime events. (in review: Runtime events: DM_EVENTS with frame_loop, shaders, red, end and the launcher's exit (#388) #474, then Runtime events: controller, thermal, save and the background prewarm (#388) #481 for
controller,thermal,saveand background prewarm progress) The host writes one JSON object per line to a file or descriptor a front end names (e.g.DM_EVENTS=<path|fd:N>), in the same envelope as the build events (ev,t). First events: the game started its frame loop, shader preparation progress, controller connected or disconnected, thermal state, a[RED]line, and why the game ended. The schema lives indocs/frontends.md, and a test fails on an event the docs don't list, astest_env_switches.pydoes for switches. Both the in-app overlay and outside front ends read the same events. - The controller applet with a native dialog. (in review: Controller applet: wait for the players the game asks for, in a native sheet #480. It asks only when fewer players are connected than the game needs and the device can reach that number. Landed alongside: each controller gets a player of its own on iOS, as on the Mac, fdea829 (Multi-controller support for local-multiplayer games (Smash 13.0.5 verified at 60 fps on iPhone) #504); a controller's Home and Capture buttons stay with the game, 062cd06 (Mac: keep a controller's Home and Capture buttons from macOS #541).) On a Switch, a game that needs controllers calls the controller applet, and the system shows its dialog and waits. The host answers
ShowControllerSupportat once with whoever is connected (dm/host/hle_hid.c), so a game whose controller went away carries on without one. Answering it with a native sheet (AppKit / UIKit) that waits for the players the game asks for would fix this for every game at once, following Horizon's semantics rather than per-game logic. - Status in the game's app: small notices in
front_mac.m/front_ios.m, driven by the same events. (in review: Status notices: controller, heat and background shader prewarm in the game's app (#388) #489, building on Runtime events: controller, thermal, save and the background prewarm (#388) #481) - Fewer patches for forks. Each file a front end has to patch on every sync marks a hook nxnative is missing. Bring them in one small PR at a time, so a front end can follow
mainas it is. Landed: build_ipa.sh: DM_COPY_ROMFS=NO for the unsigned IPA; entitlements in the executable's signature #478 (32ae3b1), NSP script: a game folder on an exFAT drive (AppleDouble files, rmtree) #476 (a0b8bdb), NSP script: stop early without the iOS SDK and under Rosetta #477 and NSP script: check the shader translator and glslang before the long shader steps #487 together (c464cb2). In review: NSP script: --work-dir sets the build folder and the default IPA location #475, Hades II: register the 1.0.137848 update's builds #485, iOS: find a game's DLC (aoc/) in Documents before the app bundle #486, NSP script: a shaders_stale build event when cached libraries cannot be reused #490, Pipeline archive: a base the GPU compiler rejects is left to the device; a failed archive no longer fails the build #494 (Decode BC textures on the CPU when the Metal device cannot sample them #488, CPU BC decoding, was declined). Also in review: Proposal: a game profile the host reads at run time (design note and dm_profile() accessor) #492, a proposal for a runtime game profile (one host binary for every game). Still to come: the iOS per-game data folder, after Mac app: a dataFolder key keeps a game's save, caches and log in one folder a front end chooses #460. - Controls on the same store:
launcher.DM_KEYMAP/launcher.DM_PAD_MAPas saved choices, checked with the host's own parser (in review: Mac front end: DM_KEYMAP and DM_PAD_MAP move buttons to other keys and controller buttons #386). The mapping UI from Mac: native AppKit app bundle, preferences, and controller mapping #104 follows on top. - A data folder per app:
dataFolder, andDM_SAVE/DM_LOG_DIRrelative to the app (in review: Mac app: a dataFolder key keeps a game's save, caches and log in one folder a front end chooses #460). - Community front ends: a page that lists front ends against this interface, with each author adding their own row (in review: Community front ends: a page that lists them by the interface criteria of #388 #484).
- Scope decision (@seedds): should a game's Mac app have its own settings window, drawn from
launcher.jsonlike the iPhone's (Mac: a settings window before the game starts (resolution, display mode, frame rate) #374)? Or should Mac apps start with their tested defaults, with settings coming from front ends? Decided: yes. The window landed as 1e207fa (Mac: settings window before the game starts #379), drawn fromlauncher.jsonand keeping its choices in the saved-choices store.
Design
What the player sees should be the same everywhere: the setting names, sections, tested defaults and "Reset to tested defaults" come from one list, settings.tsv / launcher.json, and every front end draws from it. How it looks follows each platform's own conventions (SwiftUI forms, Apple's Human Interface Guidelines), not one front end's style.
Out of scope here
An app to build games or keep a library inside this repository. Those stay separate projects, with nxnative providing what they need.
Activity
v0lumehi commentedon Oct 27, 2026
@LNPANDY, NX2Apple is where most people looking for an app end up, so your view would help shape this. Three questions:
sync_upstream.pyre-applies patches to nine files on every sync. Which would you most like to see upstream, so NX2Apple can followmainas it is? From the list, the RomFS archive hooks (hle_fs.c,build_ipa.sh, the NSP script) and the adjustable system clock (hle_time.c) look like candidates for small PRs here. I'm happy to help write them.org.nxnative.<title ID>, keyslauncher.<switch>, as the iPhone app stores them) work for you? The Mac launcher would then read them on every start, from Finder or the Dock too.seedds commentedon Oct 27, 2026
Let's use this issue to coordinate the shared interface. nxnative should own game execution, the settings supported by each game, and the native UI needed inside a game's app. Build wizards, game libraries, artwork and update management can remain separate community projects.
I'll take #352 first, then #387. The first milestone is that a Mac app carries its supported settings and tested defaults in
launcher.json, and frontend authors have a clear document describing the build progress protocol, launch behavior, settings, saves and logs. Once #352 is integrated, #387 should contain only the documentation changes.For the following work:
needs, while launcher.json: a level for the settings shown first, and the render scale's base size #399 uses it forlevel; Mac: settings window before the game starts #379 also introduces its own platform and resolution metadata. Reconcile these into one generator and schema that iOS, the Mac app and outside frontends consume. Document compatibility rules: readers tolerate unknown optional fields, and existing supported fields retain their meaning.env.txt, bundledenv.txt, saved choices, tested defaults. In Mac: settings window before the game starts #379, the shared model currently protects env-file keys but can overwrite inherited environment values; resolve that before integrating the window. Hiding the window and ignoring saved choices should have separately documented behavior.For the settings milestone, check Finder launch, launch through an external frontend, explicit environment overrides, and reset after rebuilding with changed defaults. Audit fork patches individually for general fixes or missing interfaces; a fork's packaging or product choices do not all need to become upstream features.
v0lumehi commentedon Oct 27, 2026
Thanks, @seedds. Agreed on the split and the order. #352 and #387 are in. Before anyone extends a UI, here is a proposal for the two things you asked to settle first: one settings schema, and one store with one precedence. It also covers a third point: how a settings or controls UI can be one interface with many skins. Nothing below is final. I'd like the authors of the open PRs on this topic to weigh in, so that we end up with one design and not six.
Who this touches
level, the render scale's base size), launcher: sub-settings that appear only when they can do anything (needs column) #355 @Xipzer (needs), ios/launcher: a Picture section, and the thermal cap as a setting #357 @Xipzer (Picture section, thermal cap), Mac: settings window before the game starts #379 @surajmandalcell (platform, resolution), Restore the Metal HUD option in Mac app builds #376 @thehobbit85 (Metal HUD), NVN on Metal: resolution upscaling (DM_UPSCALE 1 / 1.5 / 2), a Render resolution setting and a Mac menu on the shared store #405 @Amost- (upscaling), iOS: play on a connected TV (cable or AirPlay), picture capped at 1080p #367 @zorrobyte (TV output), iOS: add optional swipe camera and touch-control settings #258 @harshulkuhar (touch controls)DM_KEYMAP/DM_PAD_MAP), Mac controllers: a radial stick dead zone, value handlers off the main queue, and Nintendo face buttons by name #382 @Unayung, Mac: map a Nintendo controller's face buttons by label #295 and Map Nintendo controller face buttons by label on macOS and iOS #180 (face buttons by label, two takes on one change), Mac: add optional Xbox letter-based controller mapping #102, Add Steam Controller (2026) support on macOS #2211. One schema
Problem. #355 and #399 both claim
settings.tsv's ninth column, and #379 brings its own metadata.Proposal.
metacolumn ofkey=valuepairs separated by;. For examplelevel=basic;needs=DM_RUMBLE;platform=mac.settings.pyvalidates each key against a short, documented list and writes the pairs into the row oflauncher.jsonas fields.render_basein launcher.json: a level for the settings shown first, and the render scale's base size #399), whichsettings.pyattaches to the row.Compatibility rules (as you wrote):
settings.pyand its line indocs/frontends.md.test_env_switches.pydoes for switches.The contract is the JSON, not the TSV.
launcher.jsongets"schema": 1.docs/, so a front end can validate it and generate types from it.settings.tsvstays the authoring format; front ends never read it.Keys we already need:
level:basicmarks what a UI shows first.needs: a setting that only matters when another one is on (launcher: sub-settings that appear only when they can do anything (needs column) #355).platform:ios,mac, or both (Mac: settings window before the game starts #379; Mac app carries the launcher settings; shader_split.py reports its progress #352 needed this too).group: sub-headings inside a section, if ios/launcher: a Picture section, and the thermal cap as a setting #357's Picture section wants them.I'm happy to rewrite #399 onto this. @Xipzer and @surajmandalcell: would #355 and #379 move onto the same column? The generator change would then be one PR that the others rebase onto.
2. One store, one precedence
This is your order:
env.txtenv.txtThe store is the app's own preferences domain,
org.nxnative.<title ID>, with keyslauncher.<switch>, the same as the iPhone app.launcher.jsonwhen they are read, so a rebuild with changed defaults drops values that no longer apply.dm/mac/launcher.creads them with CFPreferences on every start, so a start from the Finder or the Dock has them too.A known limit: a front end writes another app's preferences domain, which works for an app outside the App Sandbox. A sandboxed front end could not. If that ever matters, the store moves to a file next to the saves, with the same keys and the same precedence.
Launch overrides stay temporary. A front end that starts the game with an environment does not write the store.
#379's issue: a value inherited from the environment must not be overwritten by a saved one. Precedence 1 covers exactly that case.
3. One interface, many skins
nxnative should provide the model, and any UI is a view of it:
launcher.json(settings, sections, tested defaults, metadata) and the controls (DM_KEYMAP/DM_PAD_MAPfrom Mac front end: DM_KEYMAP and DM_PAD_MAP move buttons to other keys and controller buttons #386, extended as needed).A front end then has two options:
DM_LAUNCHER=0the iPhone app uses. This needs a defined difference between "skip the window" and "ignore saved choices", as you asked.The same goes for controls. The mapping is data, and #104's AppKit preferences and controller mapping, NX2Apple's Controls page and a future in-app panel all write the same keys. @jamessturges: #104 covers a lot of this ground. Would you split it along these lines (the app bundle and preferences on one side, the mapping UI on the store on the other)?
Menus inside a running game (#405's Upscaling menu, #406's cheats) would read and write the same model wherever they change a launch setting, so a choice made in a menu shows up in every other view.
4. Runtime events
These come last, as you suggested, once 1–3 have landed.
Proposed order
metacolumn, the key list,"schema": 1and the JSON Schema,docs/frontends.md, the test).launcher.c, with your checklist: Finder launch, external front end, explicit overrides, reset after a rebuild with changed defaults.I can do 1 and 3 if nobody else wants them.
v0lumehi commentedon Oct 27, 2026
Section 1 as a concrete diff to discuss: #418 (draft, not for merge until we agree here). It contains the
metacolumn,"schema": 1, a JSON Schema indocs/launcher.schema.json, andlevelandneedsas the first two keys. #355'sneedskeeps its format there.seedds commentedon Oct 27, 2026
Thanks, @v0lumehi. I agree with sections 1–3 and the order. Decisions:
metacolumn,"schema": 1and the JSON Schema in launcher.json schema 1: a meta column for optional fields, and a JSON Schema for front ends (draft for #388) #418 are the contract. Front ends readlauncher.json, neversettings.tsv. A newmetakey comes with the PR that first uses it, added toMETA,docs/frontends.mdand the schema together.sectionbecomes a plain string in the schema, so ios/launcher: a Picture section, and the thermal cap as a setting #357's Picture section doesn't break validating readers. A reader draws an unknown section after the ones it knows, in list order.kindstays a fixed list, because a reader can't draw a kind it doesn't know. A new kind means schema 2.showncolumn (platform:ios|mac), as Mac app carries the launcher settings; shader_split.py reports its progress #352 put it. Noplatformkey inmeta.launcher.c(@v0lumehi, yes please) → Mac: settings window before the game starts #379's window on top of both → Mac: native AppKit app bundle, preferences, and controller mapping #104's mapping parts.DM_LAUNCHER=0only skips the window, and saved choices still apply. Ignoring saved choices gets its own documented switch, defined in the store PR. Launch overrides are never written to the store.@Xipzer: please move
needsonto #418'smetacolumn. The format is unchanged. @surajmandalcell: please drop #379's own metadata and propose the keys you need (resolution) on #418. The window then reads the shared store, which also fixes inherited environment values being overwritten.launcher.json schema 1: a meta column for optional fields, and the JS…
launcher.json schema 1: a meta column for optional fields, and a JSON…
seedds commentedon Oct 27, 2026
Progress: the settings schema is on
main.metacolumn,"schema": 1anddocs/launcher.schema.json.sectionis a plain string, so a new section stays schema 1; a newkindwould be schema 2.base, from the profile'srender_base).needsholds.Next is the store and precedence in
dm/mac/launcher.c(section 2 of the proposal; @v0lumehi offered to write it), checked against the list above: Finder launch, an external front end, explicit overrides, and reset after a rebuild with changed defaults.@surajmandalcell: could you rebase #379 onto the
metacolumn? Its platform split stays in theshowncolumn (platform:ios|mac). Propose any other keys it needs, such as resolution, in a PR that adds them toMETA,docs/frontends.mdand the schema together. The window should then read and write the shared store once that lands, which also fixes inherited environment values being overwritten.Saved choices: one store and one order for every way a game starts (#388
seedds commentedon Oct 27, 2026
The store and precedence landed on
mainas 6d1e07b (#431).launcher.<VAR>strings in the app's preferences domain (its bundle ID), the same keys the iPhone app keeps.DMSettingsinInfo.plist, copied fromlauncher.json).env.txt, the bundle'senv.txt, saved choices, tested defaults. Launch overrides are never written to the store.DM_SAVED_CHOICES=0ignores saved choices and leaves them saved.DM_LAUNCHER=0only skips the iPhone's settings screen, and saved choices still apply.docs/frontends.mdhas a new Saved choices section for front-end authors.@surajmandalcell: #379 can now rebase onto the
metacolumn and this store. The window should read and writelauncher.<VAR>in the app's domain and treat a variable that is already set (environment orenv.txt) as fixed, as the launcher does.Mac app: a settings window before the game starts (#379, #374, #388)
seedds commentedon Oct 27, 2026
The Mac settings window landed as 1e207fa (#379). It is drawn from
launcher.jsonwith the iPhone's model and rows, and keeps its choices in the saved-choices store. The schema, the store and the window, steps 1 to 4 of the proposal, are now onmain.A correction: f646545 (#418) also undid 1963fab and 35e1b6e by mistake. It was built from a diff against an older base. b4f1790 restores both.
Next on this issue: #104's controller mapping on the same store, #306's front-end guidance, and runtime events.
Issue forms, a pull request template and CONTRIBUTING.md (#306, #388)
varyform commentedon Oct 27, 2026
My only proposal is the ability to specify "run" (saves) folder location per mac app.
That way I can keep my whole library in a single folder, keep all game related stuff in a single folder and, probably, remove --platform=mac from my frontend app altogether.
44 remaining items
v0lumehi commentedon Oct 31, 2026
@seedds all six open PRs of this issue are rebased on today's
mainagain, mergeable and green, including clang-tidy (ratchet).A suggested order:
They touch some of the same places, mostly
docs/frontends.md(a new section after Cheats), therun:line indm/host/tests/Makefile,front_mac.manddm/mac/launcher.c. So the second one to merge may conflict. Tell me when you merge one and I rebase the rest the same day.One change in #474 affects the lint baseline.
LOGandMLOGnow go throughDM_LOG_TAGGED, and its extra branch shifted the clang analyzer's findings in files that never log. Under__clang_analyzer__the macro now expands to the old plain print, so the findings matchmainexactly (273). The compiled code is unchanged.Status notices: a small pill in the Mac and iPhone apps for controlle…
launcher.json schema 1: a meta column for optional fields, and a JSON…
Saved choices: one store and one order for every way a game starts (s…
Mac app: a settings window before the game starts (seedds#379, seedds…
Issue forms, a pull request template and CONTRIBUTING.md (seedds#306, s…