Commit Graph
18 Commits
Author SHA1 Message Date
seedds 787f9a178e Hades runs on the Mac through a new Vulkan-on-Metal layer
Hades (0100535012974000, update 1.0.38233, SDK 9) renders with Vulkan through The Forge, not NVN.
The host now implements its 100 vk* imports on Metal in dm/gfx/vk/hle_vk.m, and the Vulkan loader
(subsdk0) and NVIDIA's driver (subsdk1) are not loaded. The title menu, the intro and Tartarus gameplay
run at 60 fps on the Mac, and a Metal validation run is clean. The iPhone build is untested.

- dm/gfx/vk/hle_vk.m: one Tegra X1-shaped device. Memory is a lazily made shared MTLBuffer; images are
  private MTLTextures. Command buffers are recorded on the host and replayed at vkQueueSubmit on the
  layer's own queue, which signals an MTLSharedEvent. Descriptor sets, update templates, render passes
  and pipelines are supported. The swapchain presents through the NVN layer's present path.
  vkGet*ProcAddr look names up in the HLE table, also trying a name without its KHR suffix.
- dm/gfx/tools/vk_shaders.py: SPIR-V to one metallib plus a slot table. It renumbers bindings per kind,
  flips vertex y, and lists only the resources the Metal entry point takes.
- nvn_mtl.m/h: mtl_device, mtl_ext_tex and mtl_present_ext for external producers. Their present
  waits on the producer's event.
- hle_nvn.c: dm_gfx_enter/leave and dm_vk_drain, so the background suspend also covers Vulkan submits.
- hle_oe.c: GetNotificationMessageEvent returns a host-owned auto-clear SystemEvent. The SDK's version
  returned an event the no-op oe::Initialize never created.
- patch_nso.py: the byte-table jump search looks 16 instructions back, not 8 (Hades main+0x1135d0).
  The output for every other module is byte-identical.
- Profile key `graphics vulkan`: build_ipa.sh ships $MSL_IOS/vk instead of the NVN chain libraries.
- dm/third_party/vulkan-headers: Vulkan-Headers 1.4.363, unmodified.
- dm/.gitignore: per-game host binaries (dm/host/dmhost_*).
2026-10-18 10:29:06 +08:00
seedds 7fdf6dccbe README screenshots of every game; NSP script packs unknown games with a provisional profile; Link's Awakening stencil copy and outdoor shaders
- README: a gallery with one picture per game (docs/images): iPhone shots
  with the virtual gamepad for Mario Wonder, 3D World, BotW and Link's
  Awakening; Mac shots (captioned) for Engage, Metroid Dread and 13
  Sentinels. Link's Awakening is now listed as in testing on an iPhone.
- scripts/build_ipa_from_nsp.py: a game with no profile is surveyed, then
  gets a provisional profile guessed from the survey in
  <NSP folder>/<id>_build/profile/<id>/ (outside the repo, reused on the
  next run) and a best-effort IPA; --survey-only keeps the old behaviour.
  game.py searches DM_GAMES_DIR after dm/games; the host Makefile reaches
  such a profile through a symlink; build_ipa.sh finds it through game.py.
  zip_store also stores files whose headers say they are compressed.
- copy_t2t_draw: depth-stencil copies now carry the stencil ([[stencil]]
  export from an X32_Stencil8 view). Link's Awakening's occlusion pass
  tests stencil bit 1 of its quarter-size depth copy, whose stencil was
  the new texture's undefined content.
- Link's Awakening: four outdoor shaders with the foliage shader's input
  loop failed on the iPhone (Half2 / normalized formats); listed in
  shader_facts_manual.tsv. Chain c4995bb3161c.
2026-10-17 21:04:33 +08:00
seedds ab5c8ff1f4 Link's Awakening: game sound, doorway lighting tiles and missing foliage fixed
- Sound: SDK 8 numbers the DSP commands differently and its DeviceSink is
  0x10, not 0x13, so every rendered frame played silence (only the movies
  had sound). capture() now takes the one 0x148-byte command with 1-6
  inputs as the sink when the expected ID is missing.
- Lighting: the light-culling compute shader reads its workgroup count
  from NVN's driver segment c0 (+0x200). dmsh_build's drv_reads scan only
  matches fragment-shader names, so compute and vertex shaders got an
  all-zero c0: every tile row wrote row 0, and the doorway step flickered
  with white 64-pixel tiles. A declared driver-segment binding now counts
  as read, and compute c0 carries the dispatch size (0x200) and the
  uniform buffer table (0x210 + 16 r). BotW and SM3DW render as before.
- Foliage: the shadow / depth pre-pass pipeline 147c829e79fb322f failed
  (uint4 inputs fed 16-bit normalized formats), so with depth test EQUAL
  the palm tree and flowers never drew. New profile file
  shader_facts_manual.tsv (merged by shader_split.py) declares those
  inputs float; Link's Awakening's chain is now ccb18ba743bf.
2026-10-17 15:34:02 +08:00
seedds b89c050e18 BotW: cream cloud boxes behind HUD prompts fixed (layout captures read alpha from red in all 26 layout shaders); work folders move to the NSP folder
The prompt mask is a layout capture (panes tagged CaptureOn / A8): an R8
render target mapped (R, 0, 0, 1) holding the blurred alpha in red. Besides
the black shadow (e6a88e12), each prompt bar draws a cream glow (aa209f57)
whose alpha is min(cloud alpha, capture alpha); with only the shadow shader
listed, the glow filled each key's rectangle with the Kumo64 cloud.
alpha_from_red now lists every pixel-shader variant of the layout library
(main's BNSH at 0x1d83000) and tex_alpha_red only converts render targets.

Game work folders now live in <NSP folder>/<id>-work instead of the home
folder: AGENTS.md and the docs say so, and build_ipa.sh requires W and
writes the IPA next to the work folder by default.
2026-10-17 11:34:58 +08:00
seedds e734ced7d9 Zelda Link's Awakening runs on the Mac (profile 01006BB00C6F0000, 1.1.0, SDK 8); BotW HUD prompt boxes fixed
BotW: the button-prompt shadow samples an R8 blur whose swizzle (R,0,0,1)
made alpha 1, so every prompt got a solid box. New profile key
alpha_from_red takes alpha from red for the named shader.

Link's Awakening:
- NVN lower-left window origin (negative viewport height, winding flip,
  flipped present and raw screenshots)
- texture copies with format change, scaling, mirroring and depth
  (copy_t2t_draw); SetCopyImageStride; nvnWindowGetPresentInterval
- oe::PopNotificationMessage, vi SetLayerCrop / DestroyLayer /
  CloseDisplay / Finalize, audren P_SIZE 0x40 for SDK 8, hid style-set
  update event signalled once on bind
- Vorbis movie audio through stb_vorbis (dm/third_party/stb_vorbis)
- patch_nso jump tables: other index registers, mov copies, masked copies
2026-10-17 08:28:44 +08:00
seedds b68ac089a3 SM3DW magenta blocks fixed (same-pass feedback snapshot); English default language; English-only comments, bilingual README
- Magenta 16x16 blocks in Super Mario 3D World 1-1 (iPhone only): the composite shader 66a71a3081b91b9a
  renders into the RGB10A2 image while sampling it in the same pass. That is undefined in Metal: the
  M4 Pro reads the old content, the A20 Pro sometimes returns whole 16x16 blocks as (1,0,1). Found with
  the new device self-check DM_NVN_MAGSCAN (blocks first appear on that attachment, and still appear
  with the suspected particle dropped). fb_prepare ends the pass, copies the attachment level into a
  snapshot and binds the snapshot for that draw, only for shaders in the new profile key feedback_snap
  (doing it for every shader cost BotW 16.6 -> 36.6 ms). DM_NVN_FB_SNAP=0 / all / <keys> overrides.
- The game language now defaults to English (nn::oe::GetDesiredLanguage returns en-US; DM_LANG=zh-Hans
  in Documents/env.txt for Chinese).
- Code comments, docstrings and docs are English only; the zh-CN doc twins are removed. README.md has the
  English text followed by the same text in Chinese. Files the shader chain version hashes keep their
  bilingual comments (editing them changes every game's chain version).
2026-10-17 00:41:10 +08:00
seedds 84d747012e Touch gamepad gets a 3D layout (D-pad, right stick, L3 / R3) and an in-game 2D / 3D layout button; pad_layout picks each game's default 2026-10-16 22:06:19 +08:00
seedds 68cfe6c47e Super Mario 3D World + Bowser's Fury (1.2.2) reaches World 1-1 and Lake Lapcat on the Mac; textures sampled with another type get compatible views
- Profile 010028600EBDA000 (SDK 10.4.0, NVN API 53): 11,080 shaders from Yaz0 SARCs (BNSH, BAHS v9, effects, main).
- Shader key hashes min(0x880, control +0x20) bytes (0x850 / 0x848 controls); other games' tables unchanged.
- Profile keys device_api (NVN API version the game checks) and shared_font (ttf / bfttf at the shared-font address).
- File system hooks run on a per-thread host stack (HLE_FORCE_BIG, hle_bigstack.c): the save thread has 8 KB.
- Washed-out scenes: a one-layer 2D array sampled as texture2d read one value on Metal; tex_fit binds 2D / array / cube views.
- Unwritten colour outputs get a zero write mask; NVN 1D textures are one-row 2D.
- Hooks: InlineKeyboard, settings LanguageCode, ListAllUsers, oe perf / album, nifm SDK 10, prepo session, vi indirect image size;
  nvnQueueAcquireTexture, SamplerCompare, debug groups, TextureGetSwizzle / GetStorageClass.
- NSP script builds nx_extract in a temp folder (an apostrophe in the path broke MSBuild silently); DM_KEYS left stick.
2026-10-16 13:04:49 +08:00
seedds 550db7dc5a 13 Sentinels: Aegis Rim (1.10) reaches its first battle on the Mac; Mario holds interval-2 frames for two display ticks
- New profile 01003FC01670C000: sdk and subsdk0 are byte-identical to Metroid Dread's; data is six CRI CPKs; the 751 shaders are GLSLC outputs compiled into main (shader_split.py: shader_main_glslc), built as single-stage programs and paired per draw (shader_separable: every vertex shader is paired with a cover of the pixel shader inputs, so vs_pad lets any pair link)
- Host hooks for SDK 11 calls that reach a service: hid::GetMouseState, fs::CleanDirectoryRecursively, fs::EnableGlobalFileDataCache, oe::SetUserInactivityDetectionTimeExtendedUnsafe, time::ToPosixTime, audio::GetAudioOutName
- StandardAllocator::Allocate(size): on this build only, blocks of 64 KB or more are 64 KB aligned; the engine cuts three ring heaps out of a GPU pool at 64 KB alignment relative to its GPU address, and with the host's 4 KB-aligned address the third heap kept base 0 (null write when the prologue enters its first battle)
- NVN: triangle fans and indexed quads, vertex streams 14 and 15 on Metal slots 14 / 15 (Metal assertion buffer index 31), TextureBuilderSetPackagedTextureData copied into the storage at TextureInitialize, InsertDebugMarker as a no-op
- Mario 1.2.1 sets present interval 2 for single frames after a slow one and advances its time step by the interval; frames were shown for one tick and the game jumped ahead on the iPhone. The display link now keeps a frame for its whole period, and the 30 fps target waits until interval 2 has held for a second (Engage, which asks for 1 with its 60 fps cut, is unchanged). Test switch DM_TEST_PRESENT_LAG
- Survey notes: extra modules are supported, GLSLC outputs in main are counted
- Regression: Mario 1.2.1, Engage and Metroid Dread runs show only their known red lines; t_nvn 63/63; t_present_crop / t_mip_extent build again (t_mip_extent's one failure is the same at a9c56fd)
2026-10-15 23:41:10 +08:00
seedds 753dee6400 Metroid Dread (2.1.0) reaches gameplay on the Mac at 60 fps, cutscenes at 30
New profile 010093801237C000 (MercurySteam engine, SDK 11.4.3). A new game
runs through the title, the story recap and the opening cutscene into ZDR,
with movies and music. An unsigned IPA builds from the NSP folder; it has
not been tested on a device yet.

Rewriter and loader
- main has a 1.8 GB bss (the engine's static heap): when an appended patch
  area would start beyond 112 MB, it goes 32 MB before the module base
  (negative PATCH_OFF, IMAGE_SIZE / FILE_IMAGE_LEN). Mac loader, iOS
  embedding (no bss in the executable) and QueryMemory handle both layouts;
  Mario's and Engage's output is byte-identical.
- Observation gates may sit right after a bl as well as at function entries.
- Used memory counts that bss, so SetHeapSize asks for 1.38 GB, not the pool.

Host
- Hooks: oe::FinishStartupLogo, fs::CommitSaveData / DeleteDirectory (SDK 11
  names), hid::detail::SendVibrationValues (vibration graph flush).
- Guest paths resolve "." and ".." (Atk stream paths use ../../).
- Movies: the SDK's own demuxer runs natively; the decoder supports the
  native-texture mode (RGBA into the client's NVNtexture, sync signalled),
  the D1 destructor, copied client events, MediaCodec-like Flush / Stop, and
  an asynchronous input signal after Start.
- Same-core preemption approximation (profile key core_preempt, Dread only)
  plus gates that wait for the movie player's request ring: the game frees
  objects its higher-priority threads still use, relying on them preempting
  the main thread. 12 of 12 runs clean, before about half crashed.
- SleepThread is precise (macOS timer slack made 33 ms sleeps 36.4 ms): the
  game's 30 Hz sound thread drives cutscene frames, now 33.3 ms. Mario's
  frame-time p95 19.7 -> 18.4 ms.
- DM_KEYS takes <key>*<count>/<interval>.

Graphics
- BindProgram binds per stage: Dread's depth prepass unbinds only the
  fragment stage, which used to drop every prepass draw and leave scenes
  black. Composite programs give a vertex-only pipeline.
- WriteTexels writes regions in place (the map writes one texel per call);
  t_nvn checks 15 regions byte for byte.
- Pass dumps print Z lines for dropped draws.
- shader_split.py reads MSHD containers (1,010 shaders, 659 pairs).

Regression: Mario reaches frame 6900 with only the known red line; Engage
plays its opening movie and battle cutscene.
2026-10-15 21:30:44 +08:00
seedds 5c7ac33752 App name and bundle ID come from the base NSP
- App name = the game name in the base NSP's control data (control.nacp, saved by nx_extract), read by the new dm/tools/nacp.py that the NSP script and build_ipa.sh share; Engage → "Fire Emblem Engage", Mario → "Super Mario Bros. Wonder"
- Bundle ID = org.nxnative.<base game title ID in lowercase>, e.g. org.nxnative.0100a6301214e000: unique per game and independent of language or version. A new bundle ID installs as a separate app, so existing saves (Documents/save) have to be copied over once
- Overrides: the NSP script's APP_NAME / BUNDLE_ID settings (--app-name / --bundle-id), build_ipa.sh's DM_DISPLAY_NAME / DM_BUNDLE_ID. With no readable name the NSP script stops and says to re-extract
- Profiles drop app_name / bundle_id; a plain name key is only a label for game.py list, error messages and the Mac window title
- Verified: the built IPA's Info.plist has CFBundleDisplayName "Fire Emblem Engage" and CFBundleIdentifier org.nxnative.0100a6301214e000
2026-10-15 14:34:13 +08:00
seedds 7f0cc98cdc Renamed nxnative; there is no default game
- The repository is now nxnative (first called smbw-mobile): a general NSP → native app converter, not a Mario port. README title and history lines updated; each game keeps its own app name and bundle ID (Mario's org.smbwmobile.app stays, so installed apps keep their saves)
- No default game: game.load() without DM_GAME, make -C dm/host / dm/gfx/tests without GAME, and build_ipa.sh without GAME stop and list the profiles (title ID and app name). The host Makefile still lets clean and a bare make (libvpx only) through; host unit tests only need GAME when they use the profile
- The NSP script is unchanged in use: it picks the profile from the NSPs' title ID
- Docs (README, AGENTS.md, profile and gfx READMEs, docs/agents) describe the required GAME / DM_GAME. dmsh_chain.py / dmsh_build.py comments still mention the old default: they are part of the chain hash
2026-10-15 14:01:11 +08:00
seedds 4d0e1a11cb Game profiles are named after the base game's title ID
- dm/games/smbw → dm/games/010015100B514000 (Mario, still the default everywhere), dm/games/fe_engage → dm/games/0100A6301214E000; GAME= / DM_GAME= take the title ID, and game.py lists the profiles (ID and app name) when given an unknown name
- Defaults updated in game.py, the host / host tests / gfx tests Makefiles and build_ipa.sh; workspaces follow as ~/<title ID>-work and NSP builds as <NSP folder>/<title ID>_build/
- Both chain versions are unchanged (Mario 271056163ea2, Engage a295a8035b7b: the hash covers table contents and keys, not the profile name). dmsh_chain.py and dmsh_build.py keep "default smbw" in their comments on purpose, since their source is part of the chain hash and editing them would force a full shader rebuild
- dmsh_chain.h headers regenerated (comment only); README, AGENTS.md, profile READMEs, gfx READMEs and docs/agents use the IDs
- Mario regression t82 clean; Engage boots and mounts its DLC under the new name; t_fs 68/68, t_fs_case 51/51, t_oe 47/47, t_nvn 61/61; iOS builds
2026-10-15 13:52:22 +08:00
seedds 564e0c1ed9 Engage runs at 60 fps on the game's own 60 fps timing
- A rewriter cut on Engage 2.0.0 main (8c08b971…, 0x32f92d4) makes Unity's QualitySettings.set_vSyncCount wrapper only set 1, so the game asks for present interval 1 and game mode gives 60 fps
- The game's frame-counted logic steps by GameTime.VsyncFixedCount (= vSyncCount, about 96 sites: cameras, map cursor, unit actors, menu cursors and input repeat, hub NPCs); with a count of 1 at 60 fps it runs at the speed of the original at 30. VsyncDeltaCount / VsyncDeltaTime (hub player movement) and combat movement use real time
- Measured on the Mac, holding Down for 0.45 s in the save list: 60 fps with the cut and the original 30 fps both move 6 slots; without the cut, 60 fps forced with DM_PRESENT_INTERVAL=display reaches the end of the list (9 or more)
- Prologue save → battle → combat → dialogue for 370 s at 60 fps, no crash. Mario unaffected (the cut is keyed by Engage's build ID); chain versions unchanged
- Profile comment, profile READMEs and the README games row updated
2026-10-15 12:56:47 +08:00
seedds 71cacc2226 Present interval is a per-game profile setting; Engage runs at its own 30 fps
- New game.tsv key present_interval: game (default) paces frames by the interval the game sets, as on the Switch; display presents one frame per display tick. Before, display was the default whenever there was a window (always on iOS), a D2R choice that only suits games whose logic does not count frames
- In game mode a game asking for interval 2 gets the 30 fps target, so on iOS the display link runs at 30 Hz too (it used to pull the guest to the next 60 Hz tick whatever the period)
- Fire Emblem Engage sets present_interval game: the 60 fps mods for 2.0.0 also fix player and NPC movement that runs too fast at 60. DM_PRESENT_INTERVAL=display (env.txt on iOS) still gives 60 fps
- Frame-interval and dropped-frame statistics use the target frame rate (33.3 ms at 30 fps) instead of a fixed 16.7 ms
- Mario is unchanged (it asks for interval 1); both chain versions unchanged; t_nvn 61/61, Mario regression t80 clean
2026-10-15 11:00:09 +08:00
seedds 97fb028917 scripts/: build_ipa_from_nsp.py moves here and runs from the CLI too
- Scripts the user runs now live in scripts/ at the repository root;
  build_ipa_from_nsp.py moves there from dm/ios/tools (history kept).
  AGENTS.md records the convention.
- It still runs in Spyder (F5, settings block, prompts for an empty
  NSP folder or prod.keys). From a terminal it now takes options that
  override the settings: <NSP folder>, -k prod.keys, -p version,
  -o out.ipa, --app-name, --keep-extracted, --env-file (-h for help).
  Without keyboard input a missing value is an error instead of a hung
  prompt. Exit codes 0 / 1 / 130 in a terminal; inside IPython (Spyder)
  it does not call sys.exit, so the console stays clean.
- The repository root is found by walking up to dm/tools/nx_extract
  rather than a fixed depth.
- Checked: -h from another directory; error paths (missing folder, no
  terminal input, empty folder, unregistered version) exit 1 with a
  clear message; a CLI run on the Engage NSP folder identifies the game,
  skips DLC, picks base + update, passes the tool check and stops at
  the free-space check; IPython %run under the Spyder interpreter
  (prompts, -h, an error) keeps the console alive.
- README, dm/games READMEs and docs/agents point at the new path.
2026-10-15 08:38:58 +08:00
seedds 41ac235637 Unity shaders: UnityFS reader, Engage shader tables and NVN layout
- New dm/gfx/tools/unityfs.py: UnityFS bundles (LZ4 / LZMA blocks),
  serialized files (formats 19..22), objects read by their type trees
  (Unity's common string table comes from the game's own main), and a
  Shader object's per-platform compressed subprograms. The player's
  own files have no type trees and borrow the bundles' Shader tree of
  the same type hash.
- shader_split.py reads Unity Shader objects in a second pass: each
  Switch subprogram is one NVN program (0x60-byte header of per-stage
  offset / control size / code size, then control + code per stage);
  a program's vertex / pixel stages form a pair. Engage: 9,269
  shaders, 4,634 pairs. Mario's tables and package are byte-identical.
- Engage profile: shader groups engine / assets, measured NVN layout
  (texture handles 0x168 + 8 r, 128 slots; separate samplers 0x568),
  regenerated tables, chain a295a8035b7b. 9,268 of 9,269 compile (one
  geometry shader; Metal has none).
- dmsh_build.py: the translator's built-in bases only matter with
  per-slot facts, so the guard checks that; c2 reads count the sampler
  table and 16-byte rows overlapping a table as fillable.
- game.py: DM_GAME_NVN_SAMP_BASE. NSP script survey note updated.
- Mario chain is now 271056163ea2 (Mac and iOS libraries rebuilt,
  16,266 of 16,268 as before; c1_full regenerated).
2026-10-14 20:29:26 +08:00
seedds 8783deee5b Game profiles: per-game constants and tables in dm/games/<id>
- dm/games/<id>/game.tsv holds what is specific to one game: title and
  program ID, known builds (module build IDs, display versions),
  package and shader versions, code modules, RomFS marker, zip store
  list, memory split, NVN driver constants, shader split groups, app
  name / bundle ID / IPA name. Mario's profile is smbw, the default.
- The shader tables, c1_full.tsv, pso_manifest.tsv, bf_want.tsv and
  dmsh_chain.h move from dm/gfx/nvn to dm/games/smbw.
- dm/games/game.py loads profiles (DM_GAME, by title ID), answers
  shell queries and generates the host's game_profile.h.
- Shader tools (dmsh_chain, dmsh_build, dmsh_facts, shader_split,
  tbdr_budget) take tables and constants from the profile. dmsh_build
  stops if the profile's texture / sampler bases differ from the
  translator's built-in ones. dmsh_chain hashes the profile's nvn_* /
  shader_* keys; the chain version is now 02ed42db694f. All 16,266
  translated shaders are byte-identical to 5f1ec82dfcf0's, and
  c1_full.tsv differs only in its header.
- Host: GAME ?= smbw in the Makefile; memory split, display versions,
  NVN bases, default modules, window title and the shader map path
  come from the profile. D2R-only code (Settings.json edits, the
  main+0x3509a0 read) runs only on D2R's build, which also stops a
  stray Settings.json in Mario's iOS save.
- iOS: build_ipa.sh, gen_build.py, project.yml and main.m take the
  app name, bundle ID, category, modules, RomFS marker and tables from
  the profile (GAME=<id>, workspace ~/<id>-work).
- dm/games/README(.zh-CN).md: the keys and how to add a game.
2026-10-14 10:59:12 +08:00