DRAGON QUEST XI S plays on the Mac: profile 01006C300E9F0000 (update 1.0.4, SDK 7.3.2)
New profile for Square Enix's Unreal Engine 4.18 game: from the opening movie and the title through the prologue into free play at Cobblestone Tor, 30 fps with sound and saves, no red line. An IPA builds from the NSP folder; it has not run on an iPhone yet. - Files and shaders: Unreal 4.18 legacy (version 4) paks with LZ5 blocks, decoded on the Mac with the game's own LZ5_decompress_safe (uepak.py, hle_oodle.c serving after nninitStartup); 38,892 shaders inline in the cooked packages as zlib-compressed LSLG records (shader_split.py ue_legacy_jobs, ue_inline_shaders; the NSP script and profile_guess know both pak kinds). - Memory: Unreal's address region capped at 4064 MB (game_args, MB up to 4095); SDK 7.3.2 cuts in patch_nso.py (node area 62 MB, 32 MB alignment for large regions; the build Odyssey shares, which boots unchanged) for Engage's 5 GB block. Rewriter: a jump table whose bounds check sits right before the load (check_before_copy). - Host, for every game: audio catch-up frames wait for queue space (Square Enix's sound driver never waits on the renderer event: 11,000 updates a second before); movie decoder events read at signal time as the SDK does, audio decoders keep 32 outputs (the player plays only once every track holds one: the opening movie never started in half the boots), and no input after an end-of-stream packet until Flush / Stop (a skipped movie mapped 2.25 GB of end-of-stream samples); ec shop accessors, friends, auto-sleep, album, file data cache and movie hooks. - Graphics, for every game: a geometry shader that only picks the layer (Unreal's FWriteToSliceGS, the colour-grading LUT) folded into the vertex shader's layer entry points (dmsh_build.py gs_slice / vs_layer_entry, nvn_mtl.m prog_fold_gs); cube-map array depth counted in layer-faces in the Metal texture, the upload and the guest layout (t_nvn checks it); nvnTextureGetTextureAddress and the command memory size / free getters. - Shader chain changed (dmsh_build.py): DQ11 rebuilt for Mac and iOS; every other profile's dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (no c1 or uniform read touched). Mac checks on this host: Big Brain Academy (movie loops, 23 catch-up renders as before), Monster Hunter Rise (movies), Super Mario Odyssey (boot on the cut sdk).
This commit is contained in:
1 parent
0beed52f4a
commit
df0820eb68
116 files changed
+83114
-168
No files matched your search
@@ -1,3 +1,3 @@
|
||||
# chain b-61eff511fce3 2026-10-21T21:45:38+08:00
|
||||
# chain b-664c5edfc04a 2026-10-21T21:45:38+08:00 (list carried over from b-61eff511fce3: that chain change did not touch any c1 read)
|
||||
6405f3e55facc7c9
|
||||
d9097b8aba3a7080
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-61eff511fce3 2026-10-21T21:45:49+08:00
|
||||
# chain b-664c5edfc04a 2026-10-21T21:45:49+08:00 (list carried over from b-61eff511fce3: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00008cfe4fc1d31b 5:304 6:1856 7:64 12:16
|
||||
0004d9615081f2dd 5:304 6:1856 7:64
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100000000010000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "61eff511fce3"
|
||||
#define DMSH_CHAIN "664c5edfc04a"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-b3fcfcb9ddcc 2026-10-21T23:23:14+08:00 (list carried over from b-6c7fd55d331c: that chain change did not touch any c1 read)
|
||||
# chain b-5010ee7f8a0e 2026-10-21T23:23:14+08:00 (list carried over from b-b3fcfcb9ddcc: that chain change did not touch any c1 read)
|
||||
0ffd2c92087e4b54
|
||||
69d3868c8d828fe6
|
||||
8a06e3dda01235c1
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010005501E68C000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "b3fcfcb9ddcc"
|
||||
#define DMSH_CHAIN "5010ee7f8a0e"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-1a20936f730c 2026-10-21T23:25:42+08:00
|
||||
# chain b-8e946feb9199 2026-10-21T23:25:42+08:00 (list carried over from b-1a20936f730c: that chain change did not touch any c1 read)
|
||||
35cbc686dd8461b8
|
||||
4872bec9a5567f8b
|
||||
54a618792b2d5f0f
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-1a20936f730c 2026-10-21T23:25:49+08:00
|
||||
# chain b-8e946feb9199 2026-10-21T23:25:49+08:00 (list carried over from b-1a20936f730c: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
000429e5b56f54be 0:304 2:528 3:704
|
||||
0011e7066f8b040e 0:144 1:224 2:112
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010015100B514000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "1a20936f730c"
|
||||
#define DMSH_CHAIN "8e946feb9199"
|
||||
@@ -1,3 +1,3 @@
|
||||
# chain b-7f751dfec0ce 2026-10-21T23:27:25+08:00
|
||||
# chain b-9237eda03d2b 2026-10-21T23:27:25+08:00 (list carried over from b-7f751dfec0ce: that chain change did not touch any c1 read)
|
||||
25c5829f891f9b2c
|
||||
976839d3ba92c8f3
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-7f751dfec0ce 2026-10-21T23:27:30+08:00
|
||||
# chain b-9237eda03d2b 2026-10-21T23:27:30+08:00 (list carried over from b-7f751dfec0ce: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
000064cdb6305b93 5:304 7:64 12:320
|
||||
00021722b48824b8 5:304 6:1872 7:112
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010028600EBDA000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "7f751dfec0ce"
|
||||
#define DMSH_CHAIN "9237eda03d2b"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-8200d93ca492 2026-10-21T23:44:26+08:00
|
||||
# chain b-6d8251f74fab 2026-10-21T23:44:26+08:00 (list carried over from b-8200d93ca492: that chain change did not touch any c1 read)
|
||||
0002d193f6c52811
|
||||
0010e810186cdc91
|
||||
001299ca51e856c2
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-8200d93ca492 2026-10-21T23:45:10+08:00
|
||||
# chain b-6d8251f74fab 2026-10-21T23:45:10+08:00 (list carried over from b-8200d93ca492: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
0002d193f6c52811 0:1872
|
||||
00088e149f8757ca 0:1872
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01002B00111A2000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "8200d93ca492"
|
||||
#define DMSH_CHAIN "6d8251f74fab"
|
||||
@@ -1,3 +1,3 @@
|
||||
# chain b-b94f1911fe8f 2026-10-22T08:50:37+08:00
|
||||
# chain b-e9a6b54a7284 2026-10-22T08:50:37+08:00 (list carried over from b-b94f1911fe8f: that chain change did not touch any c1 read)
|
||||
2b34d193d87075d5
|
||||
da9c1ded21a21fb9
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-b94f1911fe8f 2026-10-22T08:50:37+08:00
|
||||
# chain b-e9a6b54a7284 2026-10-22T08:50:37+08:00 (list carried over from b-b94f1911fe8f: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
02f3f795ba2726da 0:16
|
||||
1e5020d03a2d979c 0:16
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01002B30028F6000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "b94f1911fe8f"
|
||||
#define DMSH_CHAIN "e9a6b54a7284"
|
||||
@@ -1 +1 @@
|
||||
# chain b-85e8c98ac176 2026-10-22T00:21:36+08:00 (list carried over from b-adfdb13db222: that chain change did not touch any c1 read)
|
||||
# chain b-f64ba2b71fff 2026-10-22T00:21:36+08:00 (list carried over from b-85e8c98ac176: that chain change did not touch any c1 read)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-85e8c98ac176 2026-10-22T00:21:36+08:00 (list carried over from b-adfdb13db222: that chain change did not touch any uniform read)
|
||||
# chain b-f64ba2b71fff 2026-10-22T00:21:36+08:00 (list carried over from b-85e8c98ac176: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
01705ebebe3e302b 0:64 1:16 2:304
|
||||
020b72102b82b310 0:64
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010036E00FB20000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "85e8c98ac176"
|
||||
#define DMSH_CHAIN "f64ba2b71fff"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-7820419c3539 2026-10-21T23:27:35+08:00
|
||||
# chain b-bb5763db4c8d 2026-10-21T23:27:35+08:00 (list carried over from b-7820419c3539: that chain change did not touch any c1 read)
|
||||
1b5e5c3e653702d2
|
||||
1bfeb381d95d18c6
|
||||
280094f212199d77
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-7820419c3539 2026-10-21T23:27:35+08:00
|
||||
# chain b-bb5763db4c8d 2026-10-21T23:27:35+08:00 (list carried over from b-7820419c3539: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
01058b659b699495 0:288
|
||||
0117c03be720951e 0:560
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010038B015560000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "7820419c3539"
|
||||
#define DMSH_CHAIN "bb5763db4c8d"
|
||||
@@ -1 +1 @@
|
||||
# chain b-d3964897130f 2026-10-21T23:27:41+08:00
|
||||
# chain b-c5e13b82944d 2026-10-21T23:27:41+08:00 (list carried over from b-d3964897130f: that chain change did not touch any c1 read)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-d3964897130f 2026-10-21T23:27:41+08:00
|
||||
# chain b-c5e13b82944d 2026-10-21T23:27:41+08:00 (list carried over from b-d3964897130f: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
0040f948baa144f4 0:1904
|
||||
00870f86a49cf3b9 0:1904 12:64
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01003FC01670C000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "d3964897130f"
|
||||
#define DMSH_CHAIN "c5e13b82944d"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-3824e28513d2 2026-10-21T23:23:14+08:00 (list carried over from b-2eb33bdda057: that chain change did not touch any c1 read)
|
||||
# chain b-572a12a37414 2026-10-21T23:23:14+08:00 (list carried over from b-3824e28513d2: that chain change did not touch any c1 read)
|
||||
006a737523e70720
|
||||
00da789b4ca18c03
|
||||
00e5f2d3b5851fab
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01005270232F2000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "3824e28513d2"
|
||||
#define DMSH_CHAIN "572a12a37414"
|
||||
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100535012974000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "4e619a4691df"
|
||||
#define DMSH_CHAIN "810851ccb224"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-58aa432ce3c0 2026-10-22T08:26:19+08:00
|
||||
# chain b-7de5c5304538 2026-10-22T08:26:19+08:00 (list carried over from b-58aa432ce3c0: that chain change did not touch any c1 read)
|
||||
1a2b65cd05ff557c
|
||||
21c7ff0d9591a637
|
||||
33e4340e5bcbc5c6
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-58aa432ce3c0 2026-10-22T08:26:22+08:00
|
||||
# chain b-7de5c5304538 2026-10-22T08:26:22+08:00 (list carried over from b-58aa432ce3c0: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00388ae860457bd9 0:160
|
||||
00a24b2c0ac95e17 0:32
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010055D009F78000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "58aa432ce3c0"
|
||||
#define DMSH_CHAIN "7de5c5304538"
|
||||
@@ -1,3 +1,3 @@
|
||||
# chain b-cb0b0057e8d7 2026-10-22T00:45:45+08:00
|
||||
# chain b-34a53de6ceff 2026-10-22T00:45:45+08:00 (list carried over from b-cb0b0057e8d7: that chain change did not touch any c1 read)
|
||||
3a24a7c1351c6492
|
||||
5c116cabc36aeae3
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-cb0b0057e8d7 2026-10-22T00:45:51+08:00
|
||||
# chain b-34a53de6ceff 2026-10-22T00:45:51+08:00 (list carried over from b-cb0b0057e8d7: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
003b9b8d92206318 1:1616 2:48 4:832 6:624 s0
|
||||
005efd060ad46799 1:1616 4:832 6:592 s0
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100620012D6E000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "cb0b0057e8d7"
|
||||
#define DMSH_CHAIN "34a53de6ceff"
|
||||
@@ -1 +1 @@
|
||||
# chain b-d605d84674ef 2026-10-21T23:23:14+08:00 (list carried over from b-5f5ac45ebe51: that chain change did not touch any c1 read)
|
||||
# chain b-70bd2bcb3c30 2026-10-21T23:23:14+08:00 (list carried over from b-d605d84674ef: that chain change did not touch any c1 read)
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010069401ADB8000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "d605d84674ef"
|
||||
#define DMSH_CHAIN "70bd2bcb3c30"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-f9b2bbcfc576 2026-10-21T23:29:29+08:00
|
||||
# chain b-b6964c355af2 2026-10-21T23:29:29+08:00 (list carried over from b-f9b2bbcfc576: that chain change did not touch any c1 read)
|
||||
26c5fd7adbc12cc0
|
||||
67f23f4ad5925e06
|
||||
96a3e288731b2433
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-f9b2bbcfc576 2026-10-21T23:29:36+08:00
|
||||
# chain b-b6964c355af2 2026-10-21T23:29:36+08:00 (list carried over from b-f9b2bbcfc576: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00008ab75998b65a 0:16 2:16 3:16 5:16 6:16
|
||||
000be95364b95b90 0:416 1:880 4:816 6:16 s0 s1
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01006BB00C6F0000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "f9b2bbcfc576"
|
||||
#define DMSH_CHAIN "b6964c355af2"
|
||||
@@ -0,0 +1 @@
|
||||
# generated
|
||||
|
@@ -0,0 +1,12 @@
|
||||
# chain b-3882e2d3295f 2026-10-22T08:09:34+08:00
|
||||
026f2479adc13489
|
||||
10b8a90504eb68ad
|
||||
2a0fa3cfc6a8391f
|
||||
2f7bfe0d86ff032c
|
||||
71ca7603bf39e49d
|
||||
72f28e309fc62a57
|
||||
9eb8165e902fb288
|
||||
cd02a70fbc8fca77
|
||||
d924aa64f19fdcc7
|
||||
ecf3101594e73bb5
|
||||
f6f6a178466b083b
|
||||
|
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01006C300E9F0000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "3882e2d3295f"
|
||||
@@ -0,0 +1,44 @@
|
||||
# DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition: Square Enix, Unreal Engine 4.18 (legacy version 4 paks, LZ5-compressed), SDK 7.3.2, NVN, Square Enix's own sound driver
|
||||
# (SQEX::Sd, "SEAD") on the audio renderer, nn::movie for the movies (WebM: VP9 + Vorbis); update 1.0.4.
|
||||
# Started from the NSP script's provisional profile (dm/games/profile_guess.py) and brought up on the Mac (docs/agents)
|
||||
|
||||
# ---- Identity ----
|
||||
title_id 01006C300E9F0000
|
||||
program_id 01006C300E9F0000
|
||||
|
||||
# ---- App ----
|
||||
name DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition
|
||||
ipa DragonQuestXIS.ipa
|
||||
app_category public.app-category.role-playing-games
|
||||
|
||||
# ---- Versions ----
|
||||
build v262144 1.0.4 main=1719aabfa5eae42b132b65770b871288 subsdk0=93ff8252d975adf07230cb86c83301c5 sdk=fcf93a41b08c5193a1410d262602dc62
|
||||
package v262144
|
||||
shader_versions v262144
|
||||
|
||||
# ---- Files ----
|
||||
# subsdk0 is NintendoSDK_movie (main imports from it)
|
||||
modules main subsdk0 sdk
|
||||
romfs_marker JackGame/Content/Paks/pakchunk1-Switch.pak
|
||||
zip_store .pak:.webm:.metallib
|
||||
|
||||
# ---- Input ----
|
||||
# a 3D game: D-pad, right stick (camera) and L3 / R3 on the iPhone's virtual gamepad
|
||||
pad_layout full
|
||||
|
||||
# ---- Memory ----
|
||||
# Unreal Engine 4.18: nninitStartup takes one address region for the whole engine (16 GB by default), which a NumberLineAllocator (main+0x177c4d0) hands out; the game's argument
|
||||
# -SwitchVirtualAddressSpaceSize= caps it, in MB up to 4095 (main+0x177c440: atoi, and a larger number is taken as bytes; 4224 gave a 4 KB region and a crash in the allocator's
|
||||
# Compaction at boot). Two cuts on the SDK 7.3.2 build (dm/tools/patch_nso.py) put it 64 MB into the alias region: the node area 62 MB instead of 1 GB, and regions of 1 GB and more
|
||||
# 32 MB aligned instead of 1 GB. Engage's 5 GB block: 256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region (the game sets no heap)
|
||||
game_args -SwitchVirtualAddressSpaceSize=4064
|
||||
mem_reserve 0x140000000
|
||||
mem_alias 0x118000000
|
||||
mem_stack 0x10000000
|
||||
app_pool 0xCD500000
|
||||
|
||||
# ---- Shader split ----
|
||||
# The engine's global shaders (Engine/GlobalShaderCache-GLSL_SWITCH.bin), the engine's own materials and the game's. Unreal Engine 4.18 keeps every shader inline in the cooked packages,
|
||||
# zlib-compressed, in paks whose blocks are LZ5-compressed; the NSP script decodes them on the Mac with the game's own LZ5 (uepak.py, shader_split.py ue_legacy_jobs, DM_OODLE_SERVE)
|
||||
shader_groups GlobalShaderCache=global #Engine/=engine #JackGame/=game
|
||||
shader_note DRAGON QUEST XI S shaders
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 14.
|
@@ -0,0 +1,2 @@
|
||||
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
|
||||
# Provisional profile: empty until recorded on the Mac (DM_PSO_MANIFEST=off DM_PSO_RECORD).
|
||||
|
@@ -0,0 +1,235 @@
|
||||
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
|
||||
0087a5d5dd676b36 {"local":[64,1,1],"shared":1024}
|
||||
00cd7fcfb2a63c1f {"local":[16,16,1],"shared":0}
|
||||
00e98680af2c4258 {"local":[8,8,1],"shared":0}
|
||||
0174eccbefad6070 {"local":[8,8,1],"shared":0}
|
||||
0191c9971497582d {"local":[32,1,1],"shared":0}
|
||||
0206d7c4b15b8ad8 {"local":[8,8,1],"shared":0}
|
||||
020a84e294bcf6df {"local":[16,16,1],"shared":0}
|
||||
02b7665e9ec8a4d0 {"local":[128,1,1],"shared":9456}
|
||||
06f56a825a47e7f9 {"local":[8,8,1],"shared":0}
|
||||
06f807e73f6a8d74 {"local":[8,8,1],"shared":0}
|
||||
09df0d8130305a26 {"local":[8,8,1],"shared":0}
|
||||
0bf2633c1b7057b4 {"local":[8,8,1],"shared":0}
|
||||
0d3ec32c42e6ed02 {"local":[8,8,1],"shared":0}
|
||||
0f99f0f4da4e4be7 {"local":[8,8,1],"shared":0}
|
||||
1017b494e294dea0 {"local":[16,1,1],"shared":256}
|
||||
1276bc78d78f29be {"local":[8,8,1],"shared":0}
|
||||
1521aaeceb1e9e42 {"local":[32,1,1],"shared":0}
|
||||
15a22c9583ff17a9 {"local":[8,8,1],"shared":0}
|
||||
16bac67a232d8cbf {"local":[8,8,1],"shared":0}
|
||||
17fe7944e4fe5caa {"local":[1,1,1],"shared":0}
|
||||
18cb6ead3db49f3a {"local":[8,8,1],"shared":0}
|
||||
1a201a9c485b6af9 {"local":[8,8,1],"shared":0}
|
||||
1a6a483674bc54d4 {"local":[8,8,1],"shared":0}
|
||||
1a93171daf4e5510 {"local":[8,8,1],"shared":0}
|
||||
1baa65a1b85d099d {"local":[8,8,1],"shared":0}
|
||||
1cfa4d51e0415c32 {"local":[8,1,1],"shared":512}
|
||||
1dac1d09f64aaf12 {"local":[8,8,1],"shared":0}
|
||||
1def7905c1a1ec5d {"local":[8,8,1],"shared":0}
|
||||
1fbacc910596f9dd {"local":[8,8,1],"shared":0}
|
||||
1fe65bf4fbaebabf {"local":[8,8,1],"shared":0}
|
||||
213fcea0f568dcd0 {"local":[16,16,1],"shared":0}
|
||||
229b9c0e8d76c2ec {"local":[64,1,1],"shared":0}
|
||||
22d45abf1403c03d {"local":[32,1,1],"shared":0}
|
||||
23b6d6dd2e9cf667 {"local":[8,8,1],"shared":0}
|
||||
23c9f5b12579a86a {"local":[8,8,1],"shared":0}
|
||||
23f28dd141833f58 {"local":[8,8,1],"shared":0}
|
||||
25d3868b5f1bbb60 {"local":[8,8,1],"shared":0}
|
||||
26171286f4f8101a {"local":[8,8,1],"shared":0}
|
||||
26524270fd28eaf7 {"local":[8,8,1],"shared":0}
|
||||
276f5ef9c397a9bd {"local":[8,8,1],"shared":0}
|
||||
286df466c6e844ba {"local":[8,8,1],"shared":0}
|
||||
2a0fa3cfc6a8391f {"local":[8,8,8],"shared":10240}
|
||||
2a3f279629cbb560 {"local":[8,8,1],"shared":0}
|
||||
2ad2f6a3d6ea38b4 {"local":[64,1,1],"shared":2048}
|
||||
2b039c6219e44cfd {"local":[8,8,1],"shared":0}
|
||||
2bb29df48f1a28cf {"local":[8,8,1],"shared":0}
|
||||
2d1cce707faf1f0d {"local":[8,8,1],"shared":0}
|
||||
2ed397351be2d894 {"local":[8,4,1],"shared":8192}
|
||||
2f13c284faba7bc9 {"local":[8,8,1],"shared":0}
|
||||
2f4e7e3e31df5526 {"local":[8,8,1],"shared":0}
|
||||
30535e7c4ddb9e99 {"local":[512,1,1],"shared":32768}
|
||||
31df2a5d71e61795 {"local":[8,1,1],"shared":512}
|
||||
34d4f369e8c0800c {"local":[1024,1,1],"shared":16384}
|
||||
364e3ca611e4ae15 {"local":[8,8,1],"shared":0}
|
||||
3789652335105905 {"local":[64,1,1],"shared":2048}
|
||||
3793b51acdbf53ee {"local":[8,8,1],"shared":0}
|
||||
3ac970be7ecc64a4 {"local":[8,8,1],"shared":0}
|
||||
3b31cd3b491a9e47 {"local":[8,8,1],"shared":0}
|
||||
3bbe07a610ccd913 {"local":[512,1,1],"shared":32768}
|
||||
3e3355a9288015d4 {"local":[8,8,1],"shared":0}
|
||||
3e59a0da5337a2c7 {"local":[4,4,4],"shared":0}
|
||||
3ff7ccafbf0ba9d0 {"local":[8,8,1],"shared":0}
|
||||
402a3e04dc45ed9b {"local":[8,8,1],"shared":0}
|
||||
410a7bc63ed2e690 {"local":[8,8,1],"shared":0}
|
||||
41ed91d4d3903e41 {"local":[6,32,1],"shared":0}
|
||||
4244de8aba8beef4 {"local":[16,1,1],"shared":256}
|
||||
4303d8560a6ec306 {"local":[8,8,1],"shared":0}
|
||||
438e22346896f7f9 {"local":[8,8,1],"shared":0}
|
||||
4474fe9f55495405 {"local":[8,8,1],"shared":0}
|
||||
458e3dae42a42dc4 {"local":[8,8,1],"shared":0}
|
||||
473493742308c589 {"local":[8,8,1],"shared":0}
|
||||
48056ac64b2f69a9 {"local":[8,8,8],"shared":0}
|
||||
482c5f716661f590 {"local":[256,1,1],"shared":8192}
|
||||
49863c5255195788 {"local":[8,8,1],"shared":0}
|
||||
4b6f4083c2ed23d8 {"local":[8,8,1],"shared":0}
|
||||
4c6a69805cfa9062 {"local":[16,16,1],"shared":0}
|
||||
4cc3791edf16d929 {"local":[8,8,1],"shared":0}
|
||||
4e49b65f714378ba {"local":[64,1,1],"shared":1024}
|
||||
4ec07436d9fe4656 {"local":[16,16,1],"shared":14352}
|
||||
4f612b423e75ad97 {"local":[8,8,1],"shared":0}
|
||||
4faf4f12b6fdd2f5 {"local":[1,1,1],"shared":32}
|
||||
50163eec216dd927 {"local":[32,1,1],"shared":0}
|
||||
50694f117cbaa9db {"local":[8,8,1],"shared":0}
|
||||
52110e6df7486606 {"local":[64,1,1],"shared":0}
|
||||
55a8e21043017d1d {"local":[8,8,1],"shared":0}
|
||||
56f3380466745edc {"local":[8,8,1],"shared":0}
|
||||
57971461d6b20a35 {"local":[64,1,1],"shared":4976}
|
||||
595367eebfda0ab8 {"local":[1024,1,1],"shared":16384}
|
||||
598237c5f38bedbe {"local":[8,8,1],"shared":0}
|
||||
5ade564d7586fd08 {"local":[8,8,1],"shared":0}
|
||||
5b095cc820726ada {"local":[8,8,1],"shared":0}
|
||||
5ba044a0d6b6ab10 {"local":[4,1,1],"shared":128}
|
||||
5bdf05f6d1c85041 {"local":[8,8,1],"shared":0}
|
||||
5c1f592b6cef8892 {"local":[64,1,1],"shared":1024}
|
||||
5cde7967933904d5 {"local":[8,8,1],"shared":0}
|
||||
5eae74ce832b2243 {"local":[8,8,1],"shared":0}
|
||||
5f0e1b5bfc0006e9 {"local":[8,8,1],"shared":0}
|
||||
5f2d68d3e9d72e0d {"local":[8,8,1],"shared":0}
|
||||
5fc89c14c5f1690e {"local":[8,8,1],"shared":0}
|
||||
606ff0f0afa5651d {"local":[8,8,1],"shared":0}
|
||||
608c6a21735d2c77 {"local":[8,8,1],"shared":0}
|
||||
61d2f4e918fad5a0 {"local":[8,8,1],"shared":0}
|
||||
63c519bbdc30ee56 {"local":[16,16,1],"shared":0}
|
||||
64c5aecbb8471caa {"local":[1024,1,1],"shared":16384}
|
||||
6640c49b56dd7b24 {"local":[8,8,1],"shared":0}
|
||||
681487593c1a73c1 {"local":[8,8,1],"shared":0}
|
||||
6a0b9343b1f18419 {"local":[8,8,1],"shared":0}
|
||||
6aabc4fa397b3910 {"local":[64,1,1],"shared":0}
|
||||
6d8fe5292af40ba9 {"local":[8,8,1],"shared":0}
|
||||
6ec2365e8d3a144a {"local":[8,8,1],"shared":0}
|
||||
6ecf0b19a7fe0864 {"local":[16,16,1],"shared":0}
|
||||
6fbf746d8b388d63 {"local":[1,1,1],"shared":0}
|
||||
7189d29d3a02f1fb {"local":[512,1,1],"shared":0}
|
||||
723efd79f853a7ad {"local":[8,8,1],"shared":0}
|
||||
75bec860c88aee41 {"local":[64,1,1],"shared":4096}
|
||||
75ce37340dc04b55 {"local":[4,4,4],"shared":0}
|
||||
762fc5511501896a {"local":[8,8,1],"shared":0}
|
||||
763a3bb664156f24 {"local":[128,1,1],"shared":8848}
|
||||
7dd5219d5c107de3 {"local":[8,8,1],"shared":0}
|
||||
7e653cac19398e7b {"local":[8,8,1],"shared":0}
|
||||
7ecb76d8c4749807 {"local":[8,8,1],"shared":0}
|
||||
7fbc1cfbc7c33d4f {"local":[8,8,1],"shared":0}
|
||||
811a41951a8efca4 {"local":[8,8,1],"shared":0}
|
||||
82370579fdf301ba {"local":[8,8,1],"shared":0}
|
||||
84d22bce0f4fa3b6 {"local":[8,8,1],"shared":0}
|
||||
877aa6235a76c9df {"local":[256,1,1],"shared":8192}
|
||||
88043a45452cd943 {"local":[8,8,1],"shared":0}
|
||||
88e56e843a6de111 {"local":[8,8,1],"shared":0}
|
||||
89482c7a4425cdb6 {"local":[8,8,1],"shared":0}
|
||||
89bebaf83cefc192 {"local":[256,1,1],"shared":8192}
|
||||
89fac3751d1724e7 {"local":[16,1,1],"shared":256}
|
||||
8c0675ac7a5e136e {"local":[512,1,1],"shared":32768}
|
||||
8eefb81a9e8090ca {"local":[16,16,1],"shared":0}
|
||||
8f30d5c626ab731d {"local":[8,8,1],"shared":0}
|
||||
8f9e3746ee724775 {"local":[8,8,1],"shared":0}
|
||||
90654df14691d66a {"local":[4,1,1],"shared":128}
|
||||
911feb0b8cdc9040 {"local":[8,8,1],"shared":0}
|
||||
91c6229a3469bc0f {"local":[8,8,1],"shared":0}
|
||||
93e0c57694e95f2c {"local":[8,8,1],"shared":0}
|
||||
9419cf0bbfccc804 {"local":[1,1,1],"shared":32}
|
||||
9458208e1e8a730a {"local":[8,8,1],"shared":0}
|
||||
9611b00b14d63618 {"local":[8,8,1],"shared":0}
|
||||
9653e41ec35bf868 {"local":[8,8,1],"shared":0}
|
||||
987c00ea411d729b {"local":[8,8,1],"shared":0}
|
||||
991dc8911faa2061 {"local":[8,8,1],"shared":0}
|
||||
9a9f40ebf61ae513 {"local":[8,8,1],"shared":0}
|
||||
9b3cf668853c5d10 {"local":[8,8,1],"shared":0}
|
||||
9dd9ca710eb84614 {"local":[4,4,4],"shared":0}
|
||||
9eb8165e902fb288 {"local":[8,8,8],"shared":10240}
|
||||
9f0492cfb80b95b7 {"local":[8,8,1],"shared":0}
|
||||
9fa1479144834fae {"local":[8,8,1],"shared":0}
|
||||
a00a3b7d9513bfca {"local":[1,1,1],"shared":16}
|
||||
a194a4ff7a4c43df {"local":[8,8,1],"shared":0}
|
||||
a25961bbd8435d49 {"local":[8,8,1],"shared":0}
|
||||
a439b8c52e151e9f {"local":[8,8,1],"shared":0}
|
||||
a603ce5b905f1832 {"local":[64,1,1],"shared":4096}
|
||||
a667cfd7cb934fa7 {"local":[1024,1,1],"shared":28672}
|
||||
a6d4a5775677e7b5 {"local":[16,16,1],"shared":4096}
|
||||
a9b1db3b23e39c6d {"local":[8,8,1],"shared":0}
|
||||
aac4a89b41226971 {"local":[16,16,1],"shared":0}
|
||||
abec545c60079af0 {"local":[1,1,1],"shared":32}
|
||||
b099cf70b7c89163 {"local":[4,4,4],"shared":0}
|
||||
b185669da2842455 {"local":[1,1,1],"shared":16}
|
||||
b2677af6513015fe {"local":[8,8,1],"shared":0}
|
||||
b33527917f94b50a {"local":[16,16,1],"shared":0}
|
||||
b4c50cde2ad481c5 {"local":[32,1,1],"shared":0}
|
||||
b4f32c9e9553f026 {"local":[16,16,1],"shared":0}
|
||||
b743642e83a5eb20 {"local":[8,8,1],"shared":0}
|
||||
ba4b78b2eb32a0c5 {"local":[8,8,1],"shared":0}
|
||||
ba9db841905a59bc {"local":[8,8,1],"shared":0}
|
||||
bac444387a3907f5 {"local":[8,8,1],"shared":0}
|
||||
bbad198ecc4e3a82 {"local":[8,8,1],"shared":0}
|
||||
be23d0d42f23c3b1 {"local":[8,8,1],"shared":0}
|
||||
be7dd4ca65114b4b {"local":[1,1,1],"shared":16}
|
||||
bf89306798d13ab4 {"local":[8,8,1],"shared":0}
|
||||
c0ea710708fedcb4 {"local":[8,8,1],"shared":0}
|
||||
c128acdb547d891f {"local":[16,16,1],"shared":0}
|
||||
c188b8506f4903f6 {"local":[8,8,1],"shared":0}
|
||||
c41a2f1950012ace {"local":[16,16,1],"shared":0}
|
||||
c570f7c5014a2ea7 {"local":[8,8,1],"shared":0}
|
||||
c614b3eab375c142 {"local":[32,1,1],"shared":0}
|
||||
c64f9b1252ad23be {"local":[4,1,1],"shared":128}
|
||||
c727ba4792a83294 {"local":[64,1,1],"shared":2048}
|
||||
c87ac30163739922 {"local":[8,8,1],"shared":0}
|
||||
c9abbb797a9b8bd1 {"local":[8,8,1],"shared":0}
|
||||
ca094b31657cc32e {"local":[8,8,1],"shared":0}
|
||||
cabf3373febcb31f {"local":[1,1,1],"shared":64}
|
||||
cb7d4c4bd7157182 {"local":[8,8,1],"shared":0}
|
||||
cb8f2307777a6dd9 {"local":[8,8,1],"shared":0}
|
||||
cc4b51aac6aa6dbe {"local":[8,8,1],"shared":0}
|
||||
cd02a70fbc8fca77 {"local":[8,8,8],"shared":10240}
|
||||
cd2a012db40785d4 {"local":[8,8,1],"shared":0}
|
||||
cd85fa2a55796767 {"local":[8,8,1],"shared":0}
|
||||
cf729f57b7a60886 {"local":[16,16,1],"shared":0}
|
||||
d4549959cd4ba008 {"local":[16,16,1],"shared":0}
|
||||
d4946ab01838dee1 {"local":[8,8,1],"shared":0}
|
||||
d55307353a2015cf {"local":[64,1,1],"shared":0}
|
||||
d553c084a3a88967 {"local":[8,8,1],"shared":0}
|
||||
d924aa64f19fdcc7 {"local":[8,8,8],"shared":10240}
|
||||
d92fce3c937362f3 {"local":[8,8,1],"shared":0}
|
||||
dafa4cfbcf82172c {"local":[128,1,1],"shared":0}
|
||||
db585cbb7ecd1b62 {"local":[8,8,1],"shared":0}
|
||||
db5b4c6c9c07e27b {"local":[8,8,1],"shared":0}
|
||||
dbc69288f07b414e {"local":[16,16,1],"shared":10256}
|
||||
dbdb1feaf34f4cec {"local":[8,8,1],"shared":0}
|
||||
dc466f844976c769 {"local":[1,1,1],"shared":64}
|
||||
dcb9510423fc8375 {"local":[8,8,1],"shared":0}
|
||||
dd52d629b2136fc4 {"local":[1,1,1],"shared":64}
|
||||
e020ddc9b407b888 {"local":[8,8,1],"shared":0}
|
||||
e1176df8fdf33ca0 {"local":[64,1,1],"shared":2048}
|
||||
e1ea606230e38d87 {"local":[16,16,1],"shared":0}
|
||||
e36afc7974151310 {"local":[8,8,1],"shared":0}
|
||||
e4a5aeb290e809bc {"local":[8,1,1],"shared":512}
|
||||
e7156fc94f765163 {"local":[8,8,1],"shared":0}
|
||||
e71cf56a236b9fa0 {"local":[8,8,1],"shared":0}
|
||||
e9865d660417cd00 {"local":[8,8,1],"shared":0}
|
||||
ebab98ce568df297 {"local":[8,8,1],"shared":0}
|
||||
ecf3101594e73bb5 {"local":[8,8,8],"shared":10240}
|
||||
ee02a56b07e945a8 {"local":[8,8,1],"shared":0}
|
||||
f0d032c8ad18ae89 {"local":[8,8,1],"shared":0}
|
||||
f11beed3dae863dc {"local":[8,8,1],"shared":0}
|
||||
f3c93b8fc4681195 {"local":[8,8,1],"shared":0}
|
||||
f41325b313b6c8ca {"local":[4,4,4],"shared":0}
|
||||
f4fb6b44d265f4ee {"local":[1,32,1],"shared":0}
|
||||
f54e88673752a76c {"local":[8,8,1],"shared":0}
|
||||
f5dbba4e70bb07ff {"local":[4,4,4],"shared":0}
|
||||
f68890332940fc65 {"local":[8,8,1],"shared":0}
|
||||
f890db367b9622ea {"local":[8,8,1],"shared":0}
|
||||
f8a2f2a54101ea32 {"local":[6,32,1],"shared":0}
|
||||
f8a6006c4c9a445c {"local":[16,16,1],"shared":0}
|
||||
fb84b2ab912c5386 {"local":[64,1,1],"shared":4096}
|
||||
fdb487db6e313c01 {"local":[8,8,1],"shared":0}
|
||||
fdf5ee7d3ee9ca9f {"local":[16,16,1],"shared":0}
|
||||
fe95a416fa63fcd1 {"local":[8,8,1],"shared":0}
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 19.
|
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
# kind: evidence control-section key conflict rulings: empty
|
||||
dm_ctl_sha16 chosen_pc class candidates records
|
||||
|
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,3 @@
|
||||
# chain b-15547f29143a 2026-10-21T23:06:35+08:00
|
||||
# chain b-9f3544a56cf1 2026-10-21T23:06:35+08:00 (list carried over from b-15547f29143a: that chain change did not touch any c1 read)
|
||||
21fe759b8114941c
|
||||
60e2041ed375cae1
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-15547f29143a 2026-10-21T23:06:45+08:00
|
||||
# chain b-9f3544a56cf1 2026-10-21T23:06:45+08:00 (list carried over from b-15547f29143a: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
0002f3adb60ad088 5:304 6:1792 7:112 10:64 13:32
|
||||
0006cbc2ca63e38b 6:1520 12:432
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01007EF00011E000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "15547f29143a"
|
||||
#define DMSH_CHAIN "9f3544a56cf1"
|
||||
@@ -1 +1 @@
|
||||
# chain b-d6930bbe1baa 2026-10-22T00:21:36+08:00 (list carried over from b-cf4190f31748: that chain change did not touch any c1 read)
|
||||
# chain b-834c0b77a894 2026-10-22T00:21:36+08:00 (list carried over from b-d6930bbe1baa: that chain change did not touch any c1 read)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-d6930bbe1baa 2026-10-22T00:21:36+08:00 (list carried over from b-cf4190f31748: that chain change did not touch any uniform read)
|
||||
# chain b-834c0b77a894 2026-10-22T00:21:36+08:00 (list carried over from b-d6930bbe1baa: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00298d8d603fb418 0:16
|
||||
00ba482babf36d17 0:16
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01008BC024FDE000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "d6930bbe1baa"
|
||||
#define DMSH_CHAIN "834c0b77a894"
|
||||
@@ -1,3 +1,3 @@
|
||||
# chain b-1a9b40cf13d7 2026-10-21T23:23:22+08:00
|
||||
# chain b-ada2f5482c93 2026-10-21T23:23:22+08:00 (list carried over from b-1a9b40cf13d7: that chain change did not touch any c1 read)
|
||||
57eb9f0bae677756
|
||||
f826f2b59cfa39a8
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-1a9b40cf13d7 2026-10-21T23:23:23+08:00
|
||||
# chain b-ada2f5482c93 2026-10-21T23:23:23+08:00 (list carried over from b-1a9b40cf13d7: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
003b38fe9b3627ba 0:192 2:64
|
||||
00500b94cf88443d 0:1168
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010093801237C000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "1a9b40cf13d7"
|
||||
#define DMSH_CHAIN "ada2f5482c93"
|
||||
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010099C022B96000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "6c7c083b2aa7"
|
||||
#define DMSH_CHAIN "68309d2d7bdd"
|
||||
@@ -1,3 +1,3 @@
|
||||
# chain b-2542626d576f 2026-10-21T23:46:07+08:00 (list carried over from b-3f938d83f0c7: that chain change did not touch any c1 read)
|
||||
# chain b-b6b52a84ad2c 2026-10-21T23:46:07+08:00 (list carried over from b-2542626d576f: that chain change did not touch any c1 read)
|
||||
01a847b848df5e33
|
||||
5fb90d2733898803
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-2542626d576f 2026-10-21T23:46:44+08:00 (list carried over from b-3f938d83f0c7: that chain change did not touch any uniform read)
|
||||
# chain b-b6b52a84ad2c 2026-10-21T23:46:44+08:00 (list carried over from b-2542626d576f: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
000325b608b12e54 1:448 9:32 10:288
|
||||
000b783c5701c854 1:112
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01009BF0072D4000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "2542626d576f"
|
||||
#define DMSH_CHAIN "b6b52a84ad2c"
|
||||
@@ -1 +1 @@
|
||||
# chain b-6f84388c810c 2026-10-21T23:29:40+08:00
|
||||
# chain b-8f07d70199df 2026-10-21T23:29:40+08:00 (list carried over from b-6f84388c810c: that chain change did not touch any c1 read)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-6f84388c810c 2026-10-21T23:29:40+08:00
|
||||
# chain b-8f07d70199df 2026-10-21T23:29:40+08:00 (list carried over from b-6f84388c810c: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00b66ee69b7fde95 1:16 2:80
|
||||
02d134235f7f498b 0:128 1:16
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100A00019DE0000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "6f84388c810c"
|
||||
#define DMSH_CHAIN "8f07d70199df"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-08f1551e74a6 2026-10-21T23:29:58+08:00
|
||||
# chain b-ce3518fb269a 2026-10-21T23:29:58+08:00 (list carried over from b-08f1551e74a6: that chain change did not touch any c1 read)
|
||||
101c4d73dbb253e2
|
||||
a277d45a64c92ab6
|
||||
b14f0b0add21e60a
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-08f1551e74a6 2026-10-21T23:29:58+08:00
|
||||
# chain b-ce3518fb269a 2026-10-21T23:29:58+08:00 (list carried over from b-08f1551e74a6: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00336aabde0f0d1f 0:160
|
||||
006b3aaf17be0cf3 0:144
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100A5C00D162000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "08f1551e74a6"
|
||||
#define DMSH_CHAIN "ce3518fb269a"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-1e9d905b402b 2026-10-22T08:31:13+08:00
|
||||
# chain b-4e53dc950190 2026-10-22T08:31:13+08:00 (list carried over from b-1e9d905b402b: that chain change did not touch any c1 read)
|
||||
001275671d2c9985
|
||||
0021d75a567e2122
|
||||
00a98da1158c6f14
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-1e9d905b402b 2026-10-22T08:31:16+08:00
|
||||
# chain b-4e53dc950190 2026-10-22T08:31:16+08:00 (list carried over from b-1e9d905b402b: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
0005f969d0503763 2:160 3:16 4:96
|
||||
001275671d2c9985 0:160 3:128
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100A6301214E000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "1e9d905b402b"
|
||||
#define DMSH_CHAIN "4e53dc950190"
|
||||
@@ -1 +1 @@
|
||||
# chain b-0779e8984a06 2026-10-21T23:46:07+08:00 (list carried over from b-a2bf485acea0: that chain change did not touch any c1 read)
|
||||
# chain b-8435636c1fe2 2026-10-21T23:46:07+08:00 (list carried over from b-0779e8984a06: that chain change did not touch any c1 read)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-0779e8984a06 2026-10-21T23:46:44+08:00 (list carried over from b-a2bf485acea0: that chain change did not touch any uniform read)
|
||||
# chain b-8435636c1fe2 2026-10-21T23:46:44+08:00 (list carried over from b-0779e8984a06: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
004a663e75543071 0:192
|
||||
014cebfd5986868a 0:64
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100A83022A22000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "0779e8984a06"
|
||||
#define DMSH_CHAIN "8435636c1fe2"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-31f046bb1876 2026-10-21T22:57:30+08:00
|
||||
# chain b-e73db6780302 2026-10-21T22:57:30+08:00 (list carried over from b-31f046bb1876: that chain change did not touch any c1 read)
|
||||
00096443b71e7fef
|
||||
001540067ae4cdf5
|
||||
002e5538fc05267e
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-31f046bb1876 2026-10-21T22:58:18+08:00
|
||||
# chain b-e73db6780302 2026-10-21T22:58:18+08:00 (list carried over from b-31f046bb1876: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
000034e55812e369 0:64 1:16 2:16 3:48 4:32 s1 s2 s3 s4
|
||||
00066a38d5c8ec32 0:112
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100B04011742000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "31f046bb1876"
|
||||
#define DMSH_CHAIN "e73db6780302"
|
||||
@@ -1 +1 @@
|
||||
# chain b-4a9d20dadae5 2026-10-21T23:23:14+08:00 (list carried over from b-befa9a8c84ac: that chain change did not touch any c1 read)
|
||||
# chain b-2d235ff29ffb 2026-10-21T23:23:14+08:00 (list carried over from b-4a9d20dadae5: that chain change did not touch any c1 read)
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100D9F01D474000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "4a9d20dadae5"
|
||||
#define DMSH_CHAIN "2d235ff29ffb"
|
||||
@@ -1,2 +1,2 @@
|
||||
# chain b-59674bb2e85b 2026-10-22T01:20:27+08:00
|
||||
# chain b-117c46a619bc 2026-10-22T01:20:27+08:00 (list carried over from b-59674bb2e85b: that chain change did not touch any c1 read)
|
||||
c62d1fc4921c1377
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-59674bb2e85b 2026-10-22T01:20:28+08:00
|
||||
# chain b-117c46a619bc 2026-10-22T01:20:28+08:00 (list carried over from b-59674bb2e85b: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
00398cc3ae952e94 0:336
|
||||
007b5324a6b09547 1:144
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100E44016C22000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "59674bb2e85b"
|
||||
#define DMSH_CHAIN "117c46a619bc"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-1cc65a8faa4d 2026-10-21T23:19:16+08:00
|
||||
# chain b-b1873f7f20e2 2026-10-21T23:19:16+08:00 (list carried over from b-1cc65a8faa4d: that chain change did not touch any c1 read)
|
||||
35cbc686dd8461b8
|
||||
4872bec9a5567f8b
|
||||
759c14c6d029f531
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-1cc65a8faa4d 2026-10-21T23:19:34+08:00
|
||||
# chain b-b1873f7f20e2 2026-10-21T23:19:34+08:00 (list carried over from b-1cc65a8faa4d: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
0001a896bf9bd7cc 5:496 6:2256 12:432
|
||||
00027f7dc4646be2 5:496 6:2320 7:112 10:96 12:944 s4
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100F2C0115B6000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "1cc65a8faa4d"
|
||||
#define DMSH_CHAIN "b1873f7f20e2"
|
||||
@@ -1,4 +1,4 @@
|
||||
# chain b-975400df340b 2026-10-21T23:23:14+08:00 (list carried over from b-12de0799766c: that chain change did not touch any c1 read)
|
||||
# chain b-fecf6b2424cc 2026-10-21T23:23:14+08:00 (list carried over from b-975400df340b: that chain change did not touch any c1 read)
|
||||
000566ec19dc74f7
|
||||
000b591830b6fa73
|
||||
0027e56f73d8e10e
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100F43008C44000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "975400df340b"
|
||||
#define DMSH_CHAIN "fecf6b2424cc"
|
||||
@@ -1,2 +1,2 @@
|
||||
# chain b-3e79ab909aaf 2026-10-22T00:21:36+08:00 (list carried over from b-70a5a3b5dbd2: that chain change did not touch any c1 read)
|
||||
# chain b-f04564f60060 2026-10-22T00:21:36+08:00 (list carried over from b-3e79ab909aaf: that chain change did not touch any c1 read)
|
||||
e5ac49d4582a339c
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# chain b-3e79ab909aaf 2026-10-22T00:21:36+08:00 (list carried over from b-70a5a3b5dbd2: that chain change did not touch any uniform read)
|
||||
# chain b-f04564f60060 2026-10-22T00:21:36+08:00 (list carried over from b-3e79ab909aaf: that chain change did not touch any uniform read)
|
||||
# shader key, then reg:bytes for each uniform slot whose reads stay inside its first bytes, and s<reg> for each storage slot it never writes (cb_extent.py)
|
||||
008f89e94d37f17f 1:144
|
||||
00a30a0f46c385b7 1:512
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100F5D01DA3A000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "3e79ab909aaf"
|
||||
#define DMSH_CHAIN "f04564f60060"
|
||||
@@ -1,2 +1,2 @@
|
||||
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100FD8022DAA000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
|
||||
#define DMSH_CHAIN "a450cde97d1b"
|
||||
#define DMSH_CHAIN "fe1b5345e93c"
|
||||
@@ -164,19 +164,22 @@ GPU_DRIVERS = {'NintendoSDK_OpenGL', 'NintendoSDK_Vulkan'}
|
||||
|
||||
|
||||
# ---- Shader sources: decided from the game files at every build, not kept in the profile (scripts/build_ipa_from_nsp.py; shader_split.py reads what those steps write) ----
|
||||
def shader_sources(imports, glsl_src, glslc_mods, spirv, unreal_paks, trinity_packs=0):
|
||||
def shader_sources(imports, glsl_src, glslc_mods, spirv, unreal_paks, trinity_packs=0, legacy_paks=0):
|
||||
"""{name: (value, reason)}: graphics (vulkan when main imports Vulkan functions and not nvnBootstrapLoader, else nvn), vk_capture (a Vulkan game without SPIR-V files generates its shaders at
|
||||
run time), glslc_capture (main holds GLSL sources and calls glslcCompile in a module the game loads: it compiles some shaders at run time), ue_decode (Unreal paks keep the shaders Oodle-compressed
|
||||
in their shader code archives, which only the game's own Oodle decodes), oodle_packs (Trinity packs, Oodle-compressed file by file, which the shader split decodes with the game's own decoder in
|
||||
the Mac host's tool mode). Checked against every profile: Hades and Super Mario Galaxy Vulkan, Galaxy alone with no SPIR-V file, Three Houses alone compiling shaders (13 Sentinels holds 106 GLSL
|
||||
sources but does not import glslcCompile; Captain Toad: Treasure Tracker imports it with agl's GLSL sources, but no module defines it), Octopath Traveler 0 alone with Unreal paks, Pokémon Legends: Z-A alone with Trinity packs"""
|
||||
the Mac host's tool mode), ue_legacy (legacy Unreal paks, version 4: the shaders are inline in the cooked packages, which the shader split reads through the game's own decoder the same way).
|
||||
unreal_paks counts the version 11 paks, legacy_paks the older ones. Checked against every profile: Hades and Super Mario Galaxy Vulkan, Galaxy alone with no SPIR-V file, Three Houses alone compiling
|
||||
shaders (13 Sentinels holds 106 GLSL sources but does not import glslcCompile; Captain Toad: Treasure Tracker imports it with agl's GLSL sources, but no module defines it), Octopath Traveler 0
|
||||
alone with version 11 Unreal paks, DRAGON QUEST XI S alone with legacy ones, Pokémon Legends: Z-A alone with Trinity packs"""
|
||||
vk = sum(1 for s in imports if re.match(r'vk[A-Z]', s)); vulkan = bool(vk) and 'nvnBootstrapLoader' not in imports
|
||||
return {'graphics': ('vulkan', f'main imports {vk} Vulkan functions and not nvnBootstrapLoader: the host\'s Vulkan layer (dm/gfx/vk/hle_vk.m)') if vulkan else ('nvn', 'main imports nvnBootstrapLoader'),
|
||||
'vk_capture': (vulkan and not spirv, 'no SPIR-V file in the RomFS: the game generates its shaders at run time (Super Mario Galaxy), captured by booting it on the Mac'),
|
||||
'glslc_capture': (not vulkan and bool(glsl_src) and 'glslcCompile' in imports and bool(glslc_mods),
|
||||
f'main holds {glsl_src} GLSL sources and calls glslcCompile in {", ".join(glslc_mods) or "no module it loads"}: shaders compiled at run time (Fire Emblem: Three Houses), captured by booting it on the Mac'),
|
||||
'ue_decode': (not vulkan and bool(unreal_paks), f'{unreal_paks} Unreal pak(s): the shaders are Oodle-compressed in their shader code archives (Octopath Traveler 0), decoded by booting the game on the Mac'),
|
||||
'oodle_packs': (not vulkan and bool(trinity_packs), f'{trinity_packs} Trinity pack file(s) (ONEPACK, arc/data.trpfs: Pokémon Legends: Z-A), Oodle-compressed: the shader split decodes them with the game\'s own decoder in the Mac host (dm/host/hle_oodle.c DM_OODLE_SERVE)')}
|
||||
'oodle_packs': (not vulkan and bool(trinity_packs), f'{trinity_packs} Trinity pack file(s) (ONEPACK, arc/data.trpfs: Pokémon Legends: Z-A), Oodle-compressed: the shader split decodes them with the game\'s own decoder in the Mac host (dm/host/hle_oodle.c DM_OODLE_SERVE)'),
|
||||
'ue_legacy': (not vulkan and bool(legacy_paks), f'{legacy_paks} legacy Unreal pak(s) (version 4, UE 4.18: DRAGON QUEST XI S): the shaders are inline in the cooked packages, compressed in the pak; the shader split decodes them with the game\'s own decoder in the Mac host (dm/host/hle_oodle.c DM_OODLE_SERVE)')}
|
||||
|
||||
|
||||
def build_sources(exefs, romfs, modules):
|
||||
@@ -194,7 +197,7 @@ def build_sources(exefs, romfs, modules):
|
||||
for n in names:
|
||||
if n.endswith('.trpfs'):
|
||||
with open(os.path.join(d, n), 'rb') as f: trinity += f.read(8) == b'ONEPACK\0'
|
||||
return shader_sources(imports, glsl_src, glslc_mods, spirv, len(uepak.unreal_paks(str(romfs))), trinity)
|
||||
return shader_sources(imports, glsl_src, glslc_mods, spirv, len(uepak.unreal_paks(str(romfs), legacy=False)), trinity, len(uepak.unreal_paks(str(romfs), legacy=True)))
|
||||
|
||||
|
||||
def va_heap(sysres, imports): # virtual address memory (an NPDM system resource) and a heap: the alias / heap split is a starting point the heap check corrects (Wonder, Tears of the Kingdom; Engage's pool is its own measurement)
|
||||
|
||||
@@ -381,6 +381,13 @@ NVN(nvnTextureInitialize, NVNboolean, NVNtexture *t, const NVNtextureBuilder *b)
|
||||
nvn_tex_layout(&o->d, &o->L);
|
||||
o->mem = s->pool && s->pool->mem ? s->pool->mem + s->off : NULL;
|
||||
o->serial = nvn_new_serial(); mtl_tex_init(o);
|
||||
{ // DM_NVN_TEXINFO=WxH also logs every texture of that size as it is initialized (render targets included): which ones share storage, and how their descriptions differ
|
||||
static int ti = -1; static uint32_t tw, th;
|
||||
if (ti < 0) { const char *e = getenv("DM_NVN_TEXINFO"); ti = e && sscanf(e, "%ux%u", &tw, &th) == 2; }
|
||||
if (ti && o->d.width == tw && o->d.height == th) { static uint64_t n; if (n++ < 200) LOG("[nvn] TEXINFO init ser=%llu mem=%p pool=%p(mem %p size %#llx flags %#x) off=%#llx target=%d format=%#x flags=%#x samples=%d %ux%ux%u levels=%u stride=%u size=%#llx",
|
||||
(unsigned long long)o->serial, (void *)o->mem, (void *)o->pool, o->pool ? (void *)o->pool->mem : NULL, o->pool ? (unsigned long long)o->pool->size : 0ull, o->pool ? o->pool->flags : 0,
|
||||
(unsigned long long)o->off, o->d.target, o->d.format, o->d.flags, o->d.samples, o->d.width, o->d.height, o->d.depth, o->d.levels, o->d.stride, (unsigned long long)o->L.size); }
|
||||
}
|
||||
// See SetPackagedTextureData. Linear textures are not re-uploaded as packaged: they are created on the pool buffer (tex_linear), whatever the pool's CPU access, and read what was copied in
|
||||
if (s->pkg_data && o->mem) {
|
||||
memcpy(o->mem, s->pkg_data, o->L.size);
|
||||
@@ -403,6 +410,8 @@ NVN(nvnTextureGetLevels, int, const NVNtexture *t) { return (int)CTX(t)->d.level
|
||||
NVN(nvnTextureGetSamples, int, const NVNtexture *t) { return CTX(t)->d.samples; }
|
||||
NVN(nvnTextureGetMemoryPool, NVNmemoryPool *, const NVNtexture *t) { return (NVNmemoryPool *)CTX(t)->pool; }
|
||||
NVN(nvnTextureGetMemoryOffset, ptrdiff_t, const NVNtexture *t) { return (ptrdiff_t)CTX(t)->off; }
|
||||
// The GPU address of the texture's storage (DRAGON QUEST XI S, Unreal Engine 4.18, asks for it at start): pool address + offset, the same address space as nvnMemoryPoolGetBufferAddress
|
||||
NVN(nvnTextureGetTextureAddress, NVNtextureAddress, const NVNtexture *t) { const tex_t *o = CTX(t); return o->pool ? (NVNtextureAddress)(o->pool->mem + o->off) : 0; }
|
||||
// The size of a texture's ZCull save area (SaveZCullData / RestoreZCullData). The host does no ZCull and never touches the area, but games allocate it from their own pools, and NVN
|
||||
// does not report 0. Cuphead (Unity 2017.4) allocates one per depth target (main+0x1948060) from its pool allocator, which gives a zero-size request the offset of a live block; its
|
||||
// map of allocations then loses that block (the zero-size one's size overwrites it, and freeing the zero-size one deletes it), and the block's own free later pushes a garbage-sized
|
||||
@@ -1973,6 +1982,9 @@ NVN_NOPROTO(nvnWindowSetNumActiveTextures, void, NVNwindow *w, int n) { (void)w;
|
||||
NVN(nvnCommandBufferGetCommandMemoryUsed, size_t, const NVNcommandBuffer *c) { const cmd_t *m = (const cmd_t *)c;
|
||||
return m->begin ? (size_t)m->used : 0; }
|
||||
NVN(nvnCommandBufferGetControlMemoryUsed, size_t, const NVNcommandBuffer *c) { (void)c; return 0; }
|
||||
// Size and free bytes of the command memory the game last added (DRAGON QUEST XI S, Unreal Engine 4.18): as in NVN, size = used + free; free is what is left in the current segment
|
||||
NVN(nvnCommandBufferGetCommandMemoryFree, size_t, const NVNcommandBuffer *c) { const cmd_t *m = (const cmd_t *)c; return m->cur && m->end > m->cur ? (size_t)(m->end - m->cur) : 0; }
|
||||
NVN(nvnCommandBufferGetCommandMemorySize, size_t, const NVNcommandBuffer *c) { return h_nvnCommandBufferGetCommandMemoryUsed(c) + h_nvnCommandBufferGetCommandMemoryFree(c); }
|
||||
NVN_NOPROTO(nvnCommandBufferSetCommandMemoryCallbackEnabled, void, NVNcommandBuffer *c, uint32_t on) { (void)c; (void)on; }
|
||||
|
||||
// nvnProgramSetShadersExt (NVN 55): each stage is 0x18 bytes = NVNshaderData{data, control} + one 8-byte extension field (filled from SMBW main 0x1074ff0).
|
||||
|
||||
+53
-11
@@ -1329,7 +1329,9 @@ static MTLTextureDescriptor *tex_desc(const tex_t *t, int packaged) {
|
||||
// Cube textures are created as 6-layer 2D arrays and sampled through cube / cube array views made by tex_fit: Metal only allows cube array views of a 2D array base texture (not of a cube base).
|
||||
// The Legend of Zelda: Breath of the Wild has 1512 shaders that sample the environment light probes as a cube array while a single cube texture is bound (texture ID 1064 onwards): with mismatched types the reads are undefined, and every lit surface came out black
|
||||
case 8: d.textureType = MTLTextureType2DArray; d.arrayLength = 6; break;
|
||||
case 9: d.textureType = MTLTextureTypeCubeArray; d.arrayLength = t->d.depth; break;
|
||||
// Cube arrays likewise, as a 2D array of all their faces: NVN gives a cube array's depth in layer-faces (a multiple of 6), where a Metal cube array's arrayLength counts cubes.
|
||||
// Created as a cube array with arrayLength = depth, DRAGON QUEST XI S's 510-face one (85 cubes, made when its prologue movie ends) asked for 510 cubes, past Metal's 341, and Metal aborted the process
|
||||
case 9: d.textureType = MTLTextureType2DArray; d.arrayLength = t->d.depth; break;
|
||||
case 10: RED_ONCE(1, "buffer textures (TARGET_BUFFER) not implemented"); return nil;
|
||||
default: d.textureType = MTLTextureType2D; break;
|
||||
}
|
||||
@@ -1593,8 +1595,7 @@ static id<MTLTexture> pkg_upload_(const tex_t *t, uint64_t *stamp, id<MTLTexture
|
||||
if (sparse) nvn_sparse_protect(t->pool, (uint64_t)(uintptr_t)t->mem, t->L.size); else nvn_dirty_protect(t->mem, t->L.size);
|
||||
*stamp = nvn_dirty_now(); }
|
||||
int bw, bh, bpb = nvn_fmt_bpb(t->d.format, &bw, &bh);
|
||||
unsigned layers = (t->d.target == 4 || t->d.target == 9) ? t->d.depth : t->d.target == 8 ? 6 : t->d.target == 3 ? t->d.height : 1;
|
||||
if (t->d.target == 9) layers *= 6;
|
||||
unsigned layers = (t->d.target == 4 || t->d.target == 9) ? t->d.depth : t->d.target == 8 ? 6 : t->d.target == 3 ? t->d.height : 1; // a cube array's depth counts layer-faces (tex_desc)
|
||||
size_t total = 0;
|
||||
for (unsigned l = 0; l < d.mipmapLevelCount; l++) {
|
||||
uint32_t w = MAX(t->d.width >> l, 1u), h = MAX(t->d.height >> l, 1u), z = t->d.target == 2 ? MAX(t->d.depth >> l, 1u) : 1;
|
||||
@@ -2662,6 +2663,7 @@ static void shmap_load(void) {
|
||||
}
|
||||
|
||||
@interface NvShader : NSObject { @public id<MTLFunction> fn, tf_fn; uint16_t tf_stride[4]; uint32_t cb_ext[16], sb_ro; nvn_refl rf; id<MTLArgumentEncoder> ae; NSUInteger arg_len; int stage, range_ro, afr, layer_out; const char *builtin; uint64_t logged; char key[24]; id<MTLBuffer> c1buf; uint64_t c1_gpu; size_t c1off;
|
||||
int gs_loc, lay_loc; char gs_comp, lay_comp; // a slice-writing geometry shader (no function: the layer comes from input gs_loc.gs_comp) / a vertex shader with layer entry points writing output lay_loc.lay_comp (dmsh_build.py gs_slice, vs_layer_entry); -1 = none
|
||||
// The argument buffer encoded last time
|
||||
id<MTLBuffer> ab_buf; uint64_t ab_gpu; size_t ab_off; uint64_t ab_pass; uint32_t ab_binst; // ab_pass: serial of the render pass that encoded this argument block - reused only within the same pass (the arena holding a block from another pass / another submission may already have been reclaimed and rewritten)
|
||||
id<MTLLibrary> bflib; int bflib_tried; NSMutableDictionary<NSString *, id<MTLFunction>> *bffn; // bflib: readable-tile version <key>_bf (merge B step 2)
|
||||
@@ -2790,15 +2792,24 @@ void mtl_set_depth_zero(int on) {
|
||||
int n = atomic_load(&g_vs_loaded);
|
||||
if (n) MLOG("[RED] depth mode changed after %d vertex shaders were loaded: they keep the old convention", n);
|
||||
}
|
||||
static NvShader *shader_load(NSString *pc, int stage) {
|
||||
// layer: a vertex shader's layer entry points (entry_layer / entry_layer_z, which also write the layer) instead of main0 / main0_z, for a program whose geometry shader is a slice writer (prog_fold_gs)
|
||||
static NvShader *shader_load(NSString *pc, int stage, int layer) {
|
||||
NSError *e = nil;
|
||||
NSString *lp = [NSString stringWithFormat:@"%@/%@.metallib", g_msl_dir, pc];
|
||||
id<MTLLibrary> lib = lib_from_file(lp, &e);
|
||||
NSData *jd = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/%@.json", g_msl_dir, pc]];
|
||||
NSDictionary *j = jd ? [NSJSONSerialization JSONObjectWithData:jd options:0 error:nil] : nil;
|
||||
if (j && stage == 2 && [j[@"gs_slice"] isKindOfClass:[NSDictionary class]] && [j[@"chain"] isEqual:@DMSH_CHAIN]) { // a slice-writing geometry shader has no library: a stub prog_fold_gs replaces
|
||||
NvShader *o = [NvShader new]; o->stage = 2; o->lay_loc = -1; strlcpy(o->key, pc.UTF8String, sizeof o->key);
|
||||
o->gs_loc = [j[@"gs_slice"][@"loc"] intValue]; o->gs_comp = [[j[@"gs_slice"][@"comp"] description] UTF8String][0];
|
||||
return o;
|
||||
}
|
||||
if (stage == 2 && !j) return nil; // any other geometry shader has no library (no Metal counterpart): the caller's red line says so
|
||||
NSString *lp = [NSString stringWithFormat:@"%@/%@.metallib", g_msl_dir, pc];
|
||||
id<MTLLibrary> lib = lib_from_file(lp, &e);
|
||||
if (!lib || !j) { MLOG("[RED] failed to load shader %s: %s", pc.UTF8String, e ? e.localizedDescription.UTF8String : "no reflection json"); return nil; }
|
||||
if (![j[@"chain"] isEqual:@DMSH_CHAIN]) { MLOG("[RED] shader %s has translation chain version %s, but this host expects %s", pc.UTF8String, [[j[@"chain"] description] UTF8String], DMSH_CHAIN); return nil; }
|
||||
NvShader *o = [NvShader new];
|
||||
if (layer && !j[@"entry_layer"]) { MLOG("[RED] vertex shader %s has no layer entry points", pc.UTF8String); return nil; }
|
||||
NvShader *o = [NvShader new]; o->gs_loc = o->lay_loc = -1;
|
||||
if (stage == 0 && [j[@"layer_from"] isKindOfClass:[NSDictionary class]]) { o->lay_loc = [j[@"layer_from"][@"loc"] intValue]; o->lay_comp = [[j[@"layer_from"][@"comp"] description] UTF8String][0]; }
|
||||
char err[160];
|
||||
if (nvn_refl_from_json(j, &o->rf, err, sizeof err)) { MLOG("[RED] shader %s reflection conversion failed: %s", pc.UTF8String, err); return nil; }
|
||||
if (o->rf.dyn && !DM_GAME_TEX_DYN_SLOTS) { // dynamic texture handles (TEXD / SAMPD) only where the profile says the game picks them from c2's tables, as before otherwise
|
||||
@@ -2808,9 +2819,12 @@ static NvShader *shader_load(NSString *pc, int stage) {
|
||||
if (stage == 0) {
|
||||
atomic_fetch_add(&g_vs_loaded, 1);
|
||||
if (atomic_load(&g_depth_zero)) { if (j[@"entry_z"]) entry = j[@"entry_z"]; else RED_ONCE(85, "vertex shader %s has no near=0 entry (entry_z): depth stays in the GL convention", pc.UTF8String); }
|
||||
if (layer) entry = atomic_load(&g_depth_zero) ? j[@"entry_layer_z"] : j[@"entry_layer"];
|
||||
}
|
||||
o->fn = [lib newFunctionWithName:entry]; o->stage = stage; strlcpy(o->key, pc.UTF8String, sizeof o->key); o->ventry = entry;
|
||||
if (stage == 0) o->layer_out = t_lib_layer; // writes the layer (render_target_array_index, named in the library's metadata): its passes are layered and its pipelines get an input class (pso_desc)
|
||||
// writes the layer (render_target_array_index, named in the library's metadata): its passes are layered and its pipelines get an input class (pso_desc). A library with layer entry points
|
||||
// names it for those alone: its main0 / main0_z write no layer
|
||||
if (stage == 0) o->layer_out = layer || (t_lib_layer && !j[@"entry_layer"]);
|
||||
// A function that declares function constants cannot go into a pipeline until it is specialized, even when every constant is optional. SPIRV-Cross declares one for shaders that read a
|
||||
// texel buffer (spvLinearTextureAlignmentOverride, used only through is_function_constant_defined; one of Pokémon Legends: Z-A's imgui pixel shaders, 2bd5037003980716): an empty set of
|
||||
// values keeps its default. Without it the first pipeline with that shader failed Metal's validation (`fragmentFunction main0 cannot be used to build a pipeline state`)
|
||||
@@ -2843,7 +2857,7 @@ static inline uint64_t cb_len(NvShader *sh, int reg, uint64_t size) { uint32_t e
|
||||
|
||||
static int stage_of(NSString *stage) {
|
||||
if ([stage isEqualToString:@"vertex"]) return 0; if ([stage isEqualToString:@"pixel"]) return 1; if ([stage isEqualToString:@"compute"]) return 5;
|
||||
if ([stage isEqualToString:@"hull"]) return 3; if ([stage isEqualToString:@"domain"]) return 4; return -1;
|
||||
if ([stage isEqualToString:@"hull"]) return 3; if ([stage isEqualToString:@"domain"]) return 4; if ([stage isEqualToString:@"geometry"]) return 2; return -1;
|
||||
}
|
||||
#define NVN_RT_RANGE_RO_IMPL 1
|
||||
// Private continuation accepts only B reflection that is explicitly RO; a missing rw on sbuf does not let the default 0 count as proof; does not change the existing reflection / translation chain.
|
||||
@@ -2872,7 +2886,7 @@ static NvShader *shader_get(NSString *pc, int stage) {
|
||||
if (!g_shcache) g_shcache = [NSMutableDictionary dictionary];
|
||||
NvShader *o = g_shcache[pc];
|
||||
if (o) return o;
|
||||
uint64_t bt = bp_now(); o = shader_load(pc, stage);
|
||||
uint64_t bt = bp_now(); o = shader_load(pc, stage, 0);
|
||||
if (!o) return nil;
|
||||
g_shcache[pc] = o;
|
||||
pso_record("S\t%s\t%d\n", pc.UTF8String, stage);
|
||||
@@ -2880,6 +2894,31 @@ static NvShader *shader_get(NSString *pc, int stage) {
|
||||
g_bp_lib_n++; g_bp_lib_ns += bp_now() - bt;
|
||||
return o;
|
||||
}
|
||||
// The layer entry points of vertex shader vs as a shader of their own (cached beside it as <key>#l)
|
||||
static NvShader *shader_get_layer(NvShader *vs) {
|
||||
NSString *pc = @(vs->key), *ck = [pc stringByAppendingString:@"#l"];
|
||||
NvShader *o = g_shcache[ck];
|
||||
if (!o && (o = shader_load(pc, 0, 1))) g_shcache[ck] = o;
|
||||
return o;
|
||||
}
|
||||
// A program whose geometry shader is a slice writer (a stub with no function, shader_load) draws with its vertex shader's layer entry points instead, which write the layer the geometry
|
||||
// shader would have picked: the pass is layered (layer_out) and the geometry stage goes. Metal has no geometry stage. Unreal Engine binds each stage from a program of its own (DRAGON QUEST XI S:
|
||||
// stage masks 1, 2, 4, 8, 16, an empty program for the stages a draw does not use), so a vertex shader is often combined with the previous draw's geometry shader for a moment before that
|
||||
// stage is rebound: a stub that does not fit its vertex shader stays in the program, and only a draw with it gets a red line (draw_gs_check)
|
||||
static void prog_fold_gs(NvProg *o) {
|
||||
NvShader *g = o->sh[2];
|
||||
if (!g || g->fn || !o->sh[0]) return;
|
||||
NvShader *v = o->sh[0], *l = v->lay_loc == g->gs_loc && v->lay_comp == g->gs_comp ? shader_get_layer(v) : nil;
|
||||
if (!l) return;
|
||||
o->sh[0] = l; o->sh[2] = nil;
|
||||
static int once; if (!once++) MLOG("geometry shader %s writes only the layer: vertex shader %s draws with its layer entry points instead (layered pass)", g->key, v->key);
|
||||
}
|
||||
// A draw whose geometry stage has no function (a slice writer that does not fit the vertex shader, prog_fold_gs) runs without it, reaching only the first layer, as before
|
||||
static void draw_gs_check(NvProg *pr) {
|
||||
NvShader *g = pr->sh[2]; static int n;
|
||||
if (g && !g->fn && n++ < 8) MLOG("[RED] geometry shader %s picks the layer from input %d.%c, but vertex shader %s has no layer entry points for it (layer_from %d.%c): the draw reaches only the first layer",
|
||||
g->key, g->gs_loc, g->gs_comp, pr->sh[0] ? pr->sh[0]->key : "-", pr->sh[0] ? pr->sh[0]->lay_loc : -1, pr->sh[0] && pr->sh[0]->lay_loc >= 0 ? pr->sh[0]->lay_comp : '-');
|
||||
}
|
||||
static id<MTLLibrary> g_builtin_lib;
|
||||
static NSMutableDictionary<NSString *, NvShader *> *g_builtin;
|
||||
static NvShader *builtin_get(const char *ctl) {
|
||||
@@ -2928,6 +2967,7 @@ static __unsafe_unretained NvProg *prog_get(const prog_t *cp) { // Called only
|
||||
NvShader *sh = st >= 0 ? shader_get([k stringByAppendingString:@"_b"], st) : nil;
|
||||
if (sh) o->sh[st] = sh; else MLOG("[RED] shader %s (%s) failed to load: build the shaders with dm/gfx/tools/dmsh_build.py first", k.UTF8String, stage.UTF8String);
|
||||
}
|
||||
prog_fold_gs(o); // a slice-writing geometry shader: the vertex shader's layer entry points instead
|
||||
if (p->serial) serial_put(p->serial, o);
|
||||
return o;
|
||||
}
|
||||
@@ -3442,6 +3482,7 @@ static const prog_t *bind_stages(const prog_t *p, int stages) {
|
||||
if (cp[i].serial) serial_del(cp[i].serial);
|
||||
NvProg *o = [NvProg new];
|
||||
for (int st = 0; st < 5; st++) { ck[i][st] = S.bst[st]; cs[i][st] = S.bst[st] ? S.bst[st]->serial : 0; if (S.bst[st]) { __unsafe_unretained NvProg *src = prog_get(S.bst[st]); o->sh[st] = src ? src->sh[st] : nil; } }
|
||||
prog_fold_gs(o); // the geometry stage came from another program (see prog_fold_gs)
|
||||
memset(&cp[i], 0, sizeof cp[i]); cp[i].magic = MAGIC('P', 'R', 'O', 'G'); cp[i].serial = nvn_new_serial(); serial_put(cp[i].serial, o);
|
||||
{ static int once; if (!once++) MLOG("per-stage program binding: first combined program (vertex %s fragment %s)", o->sh[0] ? o->sh[0]->key : "-", o->sh[1] ? o->sh[1]->key : "-"); }
|
||||
return &cp[i];
|
||||
@@ -3943,7 +3984,7 @@ static NSString *warm_key(const char *s, size_t n) {
|
||||
}
|
||||
static NvShader *warm_shader(NSMutableDictionary<NSString *, NvShader *> *dict, NSString *key, int stage) {
|
||||
NvShader *sh = dict[key];
|
||||
if (!sh) { sh = shader_load(key, stage); if (sh) dict[key] = sh; }
|
||||
if (!sh) { sh = shader_load(key, stage, 0); if (sh) dict[key] = sh; }
|
||||
return sh;
|
||||
}
|
||||
static void warm_enqueue(WarmItem *item) {
|
||||
@@ -6294,6 +6335,7 @@ static void draw(const p_draw *dw, const p_draw_idx *di) {
|
||||
g_mdxc.next = NULL;
|
||||
if (!S.prog) { RED_ONCE(18, "no program bound before the draw"); PD("Z %llu draw skipped: no program bound", g_stat_pass + !g_enc); return; }
|
||||
__unsafe_unretained NvProg *pr = prog_get(S.prog);
|
||||
draw_gs_check(pr); // a geometry stage left with no function (see prog_fold_gs)
|
||||
if (S.rast_discard) { tf_draw(pr, dw, di); return; } // the vertex stage only: transform feedback capture, or nothing (tf_draw)
|
||||
if (g_nubd && g_enc && ubd_conflict(pr, dw, di)) { g_ubd_conflicts++; rp_end(); } // land the deferred inline updates before this draw (see ubd_conflict)
|
||||
if (zskip_can_draw(pr)) { // Zero-source skip: the output is always 0, writing 0 over 0, so this draw changes no result (see zskip_can_draw)
|
||||
|
||||
@@ -51,7 +51,7 @@ static void shape(const nvn_texdesc *d, unsigned *dpl, int *layered, uint32_t *w
|
||||
case 2: *dpl = 3; break; // 3D
|
||||
case 4: case 6: *dpl = 2; *layered = 1; break; // 2D_ARRAY / MS_ARRAY
|
||||
case 8: *dpl = 2; *z = 6; *layered = 1; break; // CUBEMAP
|
||||
case 9: *dpl = 2; *z = d->depth * 6; *layered = 1; break; // CUBEMAP_ARRAY
|
||||
case 9: *dpl = 2; *layered = 1; break; // CUBEMAP_ARRAY: the depth counts layer-faces, as in OpenGL (DRAGON QUEST XI S: 510 for 85 cubes)
|
||||
default: *dpl = 2; *z = 1; break; // 2D / MS / RECTANGLE
|
||||
}
|
||||
if (*z == 0) *z = 1;
|
||||
|
||||
@@ -39,6 +39,12 @@ static NVNqueue *g_sq; static NVNcommandHandle g_sh; static NVNT_nvnQueueSubmitC
|
||||
static void *susp_thr(void *a) { (void)a; pthread_mutex_lock(&g_smu); atomic_store(&g_sstarted, 1); pthread_cond_broadcast(&g_scv); pthread_mutex_unlock(&g_smu); g_sub(g_sq, 1, &g_sh); atomic_store(&g_sdone, 1); return NULL; }
|
||||
static int wait_started(void) { struct timespec dl; clock_gettime(CLOCK_REALTIME, &dl); dl.tv_sec += 10; pthread_mutex_lock(&g_smu); while (!atomic_load(&g_sstarted) && pthread_cond_timedwait(&g_scv, &g_smu, &dl) == 0) {} int ok = atomic_load(&g_sstarted); pthread_mutex_unlock(&g_smu); return ok; }
|
||||
|
||||
static size_t tex_size3(int target, int fmt, int w, int h, int depth) { // a layered or 3D texture's storage size
|
||||
NVNtextureBuilder tb;
|
||||
P(nvnTextureBuilderSetDevice)(&tb, &dev); P(nvnTextureBuilderSetDefaults)(&tb); P(nvnTextureBuilderSetTarget)(&tb, target);
|
||||
P(nvnTextureBuilderSetSize3D)(&tb, w, h, depth); P(nvnTextureBuilderSetFormat)(&tb, fmt);
|
||||
return P(nvnTextureBuilderGetStorageSize)(&tb);
|
||||
}
|
||||
static size_t tex_size(int fmt, int w, int h, int flags, size_t *align) {
|
||||
NVNtextureBuilder tb;
|
||||
P(nvnTextureBuilderSetDevice)(&tb, &dev); P(nvnTextureBuilderSetDefaults)(&tb);
|
||||
@@ -85,6 +91,9 @@ int main(void) {
|
||||
// LINEAR 100×100 RGBA8: row pitch 400 aligned to 32 = 416; 416×100 = 41600, aligned to 32
|
||||
EXPECT("linear 100x100 RGBA8 size", 41600, tex_size(NVN_FORMAT_RGBA8, 100, 100, NVN_TEXTURE_FLAGS_LINEAR, &al));
|
||||
EXPECT("linear alignment", 32, al);
|
||||
// A cube array's depth counts layer-faces, as in OpenGL: 12 = two cubes, the size of a 12-layer 2D array (it used to be read as 12 cubes, six times the storage)
|
||||
EXPECT("64x64 RGBA8 cube array of depth 12 = 2D array of 12 layers", tex_size3(NVN_TEXTURE_TARGET_2D_ARRAY, NVN_FORMAT_RGBA8, 64, 64, 12),
|
||||
tex_size3(NVN_TEXTURE_TARGET_CUBEMAP_ARRAY, NVN_FORMAT_RGBA8, 64, 64, 12));
|
||||
|
||||
// A channel change on the same storage must survive the game's descriptor
|
||||
// deduplication. Exercise the public builder/init/compare API, including
|
||||
|
||||
@@ -543,6 +543,62 @@ def vs_layer(src):
|
||||
src = src[:i] + re.sub(r'\n float gl_PointSize \[\[point_size\]\];', '', src[i:j], count=1) + src[j:]
|
||||
return re.sub(r'\n out\.gl_PointSize = [^;\n]*;', '', src)
|
||||
|
||||
def gs_slice(spv):
|
||||
"""A geometry shader that only picks the layer: triangles in, at most 3 vertices out, each vertex copying its input position and the input components it passes on, and gl_Layer taking the
|
||||
bits of one input component (Unreal Engine 4.18's FWriteToSliceGS, which writes one slice of a volume texture per instance: DRAGON QUEST XI S 0e7f58cd1f602f6d builds the 32x32x32 colour
|
||||
grading LUT the tone mapper reads, and without it the screen came out a flat blue). Metal has no geometry stage, so the host drops such a shader and draws with the vertex shader's
|
||||
layer entry points instead (vs_layer_entry), which write that component as the layer. Read from the SPIR-V as GLSL (SPIRV-Cross writes no MSL for a geometry stage).
|
||||
→ {'loc': input location, 'comp': component} or None (any other geometry shader: one-pass cube shadows emit six transformed triangles)"""
|
||||
r = subprocess.run(['spirv-cross', spv, '--version', '450'], capture_output=True, text=True)
|
||||
g = r.stdout
|
||||
if r.returncode or 'layout(triangles) in;' not in g or 'layout(max_vertices = 3, triangle_strip) out;' not in g or g.count('EmitVertex();') != 3 or 'EndPrimitive' in g: return None
|
||||
ins = {m.group(2): int(m.group(1)) for m in re.finditer(r'layout\(location = (\d+)\) in vec4 (\w+)\[3\];', g)}
|
||||
outs = {m.group(2): int(m.group(1)) for m in re.finditer(r'layout\(location = (\d+)\) out vec4 (\w+);', g)}
|
||||
body = g[g.find('void main()'):]
|
||||
var, layer = {}, set()
|
||||
for st in (s.strip() for s in re.split(r'[;{}]', body[body.find('{') + 1:])):
|
||||
if not st or st == 'if (false)' or re.fullmatch(r'int \w+ = -?\d+|\w+ = int\(0x80000000\)|int \w+ = \w+|EmitVertex\(\)', st): continue
|
||||
m = re.fullmatch(r'float (\w+) = (\w+)\[\w+\]\.([xyzw])', st)
|
||||
if m and m.group(2) in ins: var[m.group(1)] = (ins[m.group(2)], m.group(3)); continue
|
||||
m = re.fullmatch(r'float (\w+) = gl_in\[\w+\]\.gl_Position\.([xyzw])', st)
|
||||
if m: var[m.group(1)] = ('pos', m.group(2)); continue
|
||||
m = re.fullmatch(r'gl_Position\.([xyzw]) = (\w+)', st)
|
||||
if m and var.get(m.group(2)) == ('pos', m.group(1)): continue
|
||||
m = re.fullmatch(r'(\w+)\.([xyzw]) = (\w+)', st)
|
||||
if m and m.group(1) in outs and var.get(m.group(3)) == (outs[m.group(1)], m.group(2)): continue
|
||||
m = re.fullmatch(r'gl_Layer = floatBitsToInt\((\w+)\)', st)
|
||||
if m and m.group(1) in var and var[m.group(1)][0] != 'pos': layer.add(var[m.group(1)]); continue
|
||||
return None # anything else: not a plain slice writer
|
||||
if len(layer) != 1 or g.count('gl_Layer =') != 3: return None
|
||||
(loc, comp), = layer
|
||||
return {'loc': loc, 'comp': comp}
|
||||
|
||||
def vs_layer_entry(full):
|
||||
"""Layer entry points for a vertex shader that writes integer bits taken from the instance index into an output component (Unreal Engine 4.18's WriteToSliceMainVS: the slice = instance −
|
||||
base instance + its first slice, DRAGON QUEST XI S 7ceb7898171a9ae9). main0_l and main0_lz are main0 and main0_z writing that component as the layer (render_target_array_index), without
|
||||
the point size (see vs_layer); the host draws with them when the program's geometry shader is a slice writer reading that component (gs_slice). → (source with the two entry points added,
|
||||
{'loc', 'comp'}), or (source, None) when no output qualifies"""
|
||||
i = full.find('struct main0_out\n{\n'); j = full.find('\n};', i)
|
||||
if i < 0 or j < 0 or 'gl_InstanceIndex' not in full or '[[render_target_array_index]]' in full[i:j]: return full, None
|
||||
locs = {m.group(1): int(m.group(2)) for m in re.finditer(r'\n float4 (\w+) \[\[user\(locn(\d+)\)\]\];', full[i:j])}
|
||||
m = next((m for m in re.finditer(r'\n out\.(\w+)\.([xyzw]) = as_type<float>\(', full) if m.group(1) in locs), None)
|
||||
if not m: return full, None
|
||||
name, comp = m.group(1), m.group(2)
|
||||
st = re.sub(r'\n float gl_PointSize \[\[point_size\]\];', '', full[i:j + 3]).replace('struct main0_out', 'struct main0l_out', 1)
|
||||
st = st[:-3] + '\n uint gl_Layer [[render_target_array_index]];\n};'
|
||||
add = '\n' + st + '\n'
|
||||
for ep, new in (('main0', 'main0_l'), ('main0_z', 'main0_lz')):
|
||||
a = re.search(rf'\bvertex\s+main0_out\s+{ep}\s*\(', full)
|
||||
if not a: return full, None
|
||||
b, depth = full.find('{', a.end()), 0
|
||||
for e in range(b, len(full)):
|
||||
depth += full[e] == '{'; depth -= full[e] == '}'
|
||||
if not depth: break
|
||||
fn = full[a.start():e + 1].replace('main0_out', 'main0l_out').replace(f' {ep}(', f' {new}(', 1)
|
||||
fn = re.sub(r'\n out\.gl_PointSize = [^;\n]*;', '', fn)
|
||||
add += '\n' + fn.replace('return out;', f'out.gl_Layer = as_type<uint>(out.{name}.{comp});\n return out;') + '\n'
|
||||
return full + add, {'loc': locs[name], 'comp': comp}
|
||||
|
||||
def vs_depth(src):
|
||||
"""Two entry points for the depth convention: the translator's vertex shader leaves z unchanged (NVN's near=0 convention); the main function is copied here - main0 gets the GL convention's (near=-w) z' = z/2 + w/2 before every return out
|
||||
(bit-identical to the formula Ryujinx used to emit in the translator), main0_z stays as is. The convention a game sets is runtime state (nvnDeviceSetDepthMode); the host picks the entry point by it, with nothing to configure per game.
|
||||
@@ -607,6 +663,13 @@ def _ep_drop(x, ids): # OpEntryPoint without the given interface IDs: model, f
|
||||
def finish(key, w, j, c1):
|
||||
name, k = loc[key]
|
||||
msl = os.path.join(w, key + '.metal'); lib = os.path.join(out, key + '_b.metallib')
|
||||
if j['stage'] == 'gs': # a geometry shader that only picks the layer is folded into the vertex shader by the host (see gs_slice); any other has no Metal counterpart and fails below
|
||||
s = gs_slice(os.path.join(w, key + '.spv'))
|
||||
if s:
|
||||
refl = {'key': key + '_b', 'route': 'B', 'chain': CHAIN, 'dm_ctl': key, 'source': f'{name}#{k}', 'pc_paired': pc_of[key], 'stage': 'gs', 'gs_slice': s,
|
||||
'notes': [], 'drv_reads': '', 'drv_unfillable': [], 'bindings': []} # no library: the host draws with the vertex shader's layer entry points
|
||||
write_refl(key, refl)
|
||||
return 'ok', '', refl
|
||||
if j['stage'] == 'vs': spv_single_lane(os.path.join(w, key + '.spv')) # one-lane subgroups (see spv_single_lane)
|
||||
r = subprocess.run(['spirv-cross', *XC, os.path.join(w, key + '.spv'), '--output', msl], capture_output=True, text=True)
|
||||
if r.returncode: return 'MSL failed', r.stderr.strip()[:160], None
|
||||
@@ -627,14 +690,20 @@ def finish(key, w, j, c1):
|
||||
if j['stage'] == 'vs' and PAIRS.get(key): # outputs the pixel shader reads but the vertex shader does not write get (0,0,0,1) (see vs_pad)
|
||||
src, pad = vs_pad(key, src)
|
||||
if pad: open(msl, 'w').write(src)
|
||||
lay = None
|
||||
if j['stage'] == 'vs': # the two depth-convention entry points (see vs_depth); the analysis below uses the source with only main0
|
||||
src, full = vs_depth(src)
|
||||
if j.get('tf'): full += vs_tf(src, j['tf']) # transform feedback capture (see vs_tf)
|
||||
plain = full
|
||||
full, lay = vs_layer_entry(full) # layer entry points for a slice-writing geometry shader (see vs_layer_entry)
|
||||
open(msl, 'w').write(full)
|
||||
res = json.load(open(os.path.join(w, key + '.res.json')))
|
||||
try: MTL.compile('mac', msl, lib) # MSL identical to one compiled before takes that library (dmsh_mtlcache.py)
|
||||
except subprocess.CalledProcessError as e:
|
||||
return 'metal failed', str(e)[:160], None
|
||||
if not lay: return 'metal failed', str(e)[:160], None
|
||||
open(msl, 'w').write(plain); lay = None # the layer entry points are an extra: a library that does not build with them is built without
|
||||
try: MTL.compile('mac', msl, lib)
|
||||
except subprocess.CalledProcessError as e: return 'metal failed', str(e)[:160], None
|
||||
clone(msl, os.path.join(out, key + '_b.metal'))
|
||||
if IOSOUT: # iOS: the same MSL goes through iOS's single compile convention (dm/ios/tools/ios_metal.py) to produce an iphoneos metallib
|
||||
try: MTL.compile('ios', msl, os.path.join(IOSOUT, key + '_b.metallib'))
|
||||
@@ -644,6 +713,7 @@ def finish(key, w, j, c1):
|
||||
refl = {'key': key + '_b', 'route': 'B', 'chain': CHAIN, 'dm_ctl': key, 'source': f'{name}#{k}', 'pc_paired': pc_of[key], 'stage': j['stage'],
|
||||
'entry': entry.group(2) if entry else 'main0', 'msl_entry_kind': entry.group(1) if entry else None, **({'entry_z': 'main0_z'} if j['stage'] == 'vs' else {}),
|
||||
**({'entry_tf': 'main0_tf', 'tf_strides': [e[2] if e else 0 for e in j['tf']]} if j.get('tf') else {}),
|
||||
**({'entry_layer': 'main0_l', 'entry_layer_z': 'main0_lz', 'layer_from': lay} if lay else {}),
|
||||
'threadgroup_size': j.get('local'), 'c1': c1.hex(), 'notes': res['notes'] + (['not in the answer table, queries use defaults'] if j.get('no_facts') else []), 'drv_reads': dr, 'drv_unfillable': unfillable(dr),
|
||||
'bindings': reflect(key, j['stage'], src, res),
|
||||
'vertex_inputs': [{'location': int(m.group(2)), 'msl_type': m.group(1)} for m in re.finditer(r'(\w+)\s+\w+\s*\[\[attribute\((\d+)\)\]\]', src)],
|
||||
|
||||
@@ -486,13 +486,14 @@ def trpfs_jobs(files, parts=64):
|
||||
|
||||
_OODLE = None
|
||||
def oodle(comp, n):
|
||||
"""The game's own OodleLZ_Decompress (Trinity's packs are Oodle Mermaid, and Oodle is linked into main): one Mac host per worker process in its tool mode (dm/host/hle_oodle.c, DM_OODLE_SERVE),
|
||||
fed over a pipe. DM_OODLE_HOST = the host, DM_OODLE_MODULES = the rewritten modules it loads (the NSP script sets both for a game with Trinity packs, profile_guess.py shader_sources)"""
|
||||
"""The game's own decoder: OodleLZ_Decompress (Trinity's packs are Oodle Mermaid, and Oodle is linked into main), or LZ5_decompress_safe (DRAGON QUEST XI S's legacy Unreal paks):
|
||||
one Mac host per worker process in its tool mode (dm/host/hle_oodle.c, DM_OODLE_SERVE), fed over a pipe. DM_OODLE_HOST = the host, DM_OODLE_MODULES = the rewritten modules it loads
|
||||
(the NSP script sets both for a game with Trinity packs or legacy Unreal paks, profile_guess.py shader_sources)"""
|
||||
global _OODLE
|
||||
if _OODLE is None:
|
||||
import tempfile
|
||||
host, mods = os.environ.get('DM_OODLE_HOST'), os.environ.get('DM_OODLE_MODULES')
|
||||
if not host or not mods: red('Oodle-compressed packs: set DM_OODLE_HOST (the Mac host, make -C dm/host GAME=<id> dmhost) and DM_OODLE_MODULES (the rewritten modules) to decode them with the game\'s own decoder')
|
||||
if not host or not mods: red('Oodle- or LZ5-compressed game files: set DM_OODLE_HOST (the Mac host, make -C dm/host GAME=<id> dmhost) and DM_OODLE_MODULES (the rewritten modules) to decode them with the game\'s own decoder')
|
||||
err = tempfile.TemporaryFile()
|
||||
_OODLE = (subprocess.Popen([host, mods], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=err, env=dict(os.environ, DM_OODLE_SERVE='1')), err)
|
||||
p, err = _OODLE
|
||||
@@ -501,7 +502,7 @@ def oodle(comp, n):
|
||||
got = struct.unpack('<Q', p.stdout.read(8))[0]
|
||||
except (BrokenPipeError, struct.error): got = None
|
||||
if got != n:
|
||||
err.seek(0); red(f'the Oodle decoder in the Mac host failed on a {len(comp)} → {n} byte stream; ---- host output ----\n' + err.read().decode(errors='replace')[-3000:])
|
||||
err.seek(0); red(f'the game\'s decoder in the Mac host failed on a {len(comp)} → {n} byte stream; ---- host output ----\n' + err.read().decode(errors='replace')[-3000:])
|
||||
return p.stdout.read(n)
|
||||
|
||||
def trinity_shaders(path, rel, packs, out):
|
||||
@@ -519,6 +520,61 @@ def trinity_shaders(path, rel, packs, out):
|
||||
else: red(f'/{rel}#{name}: file {k} has compression {comp} ({sz} → {n} bytes), which is not known')
|
||||
scan_blob(data, f'/{rel}#{name}#{struct.unpack_from("<Q", d, hs[0] + 8 * k)[0]:016x}', out)
|
||||
|
||||
UE_LEGACY = 'UE legacy pak'
|
||||
def ue_legacy_jobs(files, parts=64):
|
||||
"""Legacy Unreal paks (version 4, DRAGON QUEST XI S: pakchunk0-Switch.pak and pakchunk1-Switch.pak, 13.8 GB, and the update's *_0_P.pak): the engine mounts them all as one view, and a patch pak
|
||||
(name ending in _P.pak) wins over the others for the same path. Only the newest copy of each path is scanned. → ([(pak path, rel, (UE_LEGACY, names)), …] in about equal raw sizes, the pak paths taken)"""
|
||||
def legacy(p):
|
||||
try:
|
||||
with open(p, 'rb') as f: return uepak.pak_version(f) in uepak.LEGACY
|
||||
except (OSError, ValueError, struct.error): return False
|
||||
paks = [(p, r) for p, r in files if p.endswith('.pak') and legacy(p)]
|
||||
if not paks: return [], set()
|
||||
view = {}
|
||||
for p, r in sorted(paks, key=lambda x: (x[1].endswith('_P.pak'), x[1])): # patch paks last, so they replace; one view per pak folder (one per version given)
|
||||
pk = uepak.Pak(p)
|
||||
for name, e in pk.files.items(): view[(os.path.dirname(p), os.path.normpath(pk.mount + name))] = (p, r, name, e['raw'])
|
||||
jobs, cur, size, step = [], collections.defaultdict(list), 0, sum(v[3] for v in view.values()) / parts
|
||||
for p, r, name, raw in sorted(view.values(), key=lambda x: (x[1], x[2])):
|
||||
cur[(p, r)].append(name); size += raw
|
||||
if size >= step: jobs += [(p_, r_, (UE_LEGACY, tuple(ns))) for (p_, r_), ns in cur.items()]; cur, size = collections.defaultdict(list), 0
|
||||
jobs += [(p_, r_, (UE_LEGACY, tuple(ns))) for (p_, r_), ns in cur.items()]
|
||||
return jobs, {p for p, _ in paks}
|
||||
|
||||
_PAKS = {}
|
||||
def ue_legacy_shaders(path, rel, names, out):
|
||||
"""Files of a legacy Unreal pak (ue_legacy_jobs), each decoded block by block with the game's own decoder (DRAGON QUEST XI S: LZ5) and scanned for the shader records the engine keeps inline"""
|
||||
pk = _PAKS.get(path) or _PAKS.setdefault(path, uepak.Pak(path))
|
||||
for name in names:
|
||||
d = pk.read(name, decode=oodle)
|
||||
ue_inline_shaders(d, f'/{rel}#{re.sub(r"^(\.\./)+", "", os.path.normpath(pk.mount + name))}', out)
|
||||
|
||||
ZLIB_HEAD = re.compile(b'\x78[\x01\x5e\x9c\xda]')
|
||||
def ue_inline_shaders(d, label, out):
|
||||
"""Shaders kept inline in cooked Unreal packages (UE 4.18, DRAGON QUEST XI S: each material's shader map in its .uexp, 38 to 87 shaders in the ones looked at, and the engine's global shaders in
|
||||
Engine/GlobalShaderCache-GLSL_SWITCH.bin, 945): each shader's code is a byte array (i32 length, then the bytes) holding one zlib stream, which inflates to an LSLG record as in the shader code
|
||||
archives (uepak.lslg). Found by the zlib header and the length in front of it: a stream that inflates whole from exactly that many bytes and starts with LSLG (every such stream in the files
|
||||
looked at). A record stored without compression (LSLG right after its length) is taken too. The engine combines vertex and pixel shaders per draw (separable_pairs)"""
|
||||
import zlib
|
||||
def take(s, at):
|
||||
rec = uepak.lslg(s)
|
||||
if not rec: red(f'{label}: the shader record at {at:#x} is not an LSLG record of the known layout')
|
||||
tag, ctl, code = rec; st = UE_TAG.get(tag)
|
||||
if not st: red(f'{label}: the shader record at {at:#x} has stage tag {tag}')
|
||||
why = check(label, st, ctl, code)
|
||||
if why: red(f'{label}: shader at {at:#x} ({st}): {why}')
|
||||
out.append((f'{label}#UE{at:x}', st, ctl, code))
|
||||
for m in ZLIB_HEAD.finditer(d):
|
||||
i = m.start(); n = int.from_bytes(d[i - 4:i], 'little') if i >= 4 else 0
|
||||
if not 2 <= n <= len(d) - i: continue
|
||||
z = zlib.decompressobj()
|
||||
try: s = z.decompress(d[i:i + n])
|
||||
except zlib.error: continue
|
||||
if z.eof and not z.unused_data and s[:4] == b'LSLG': take(s, i)
|
||||
for m in re.finditer(b'LSLG', d):
|
||||
i = m.start(); n = int.from_bytes(d[i - 4:i], 'little') if i >= 4 else 0
|
||||
if 2 <= n <= len(d) - i and uepak.lslg(d[i:i + n]): take(d[i:i + n], i)
|
||||
|
||||
def yaz0(s):
|
||||
"""Yaz0 decompression (Super Mario 3D World + Bowser's Fury: almost all of RomFS is .szs = SARC wrapped in Yaz0). Pure Python runs at about 20–45 MB/s per core, under a minute for the whole 4.7 GB RomFS in parallel, and needs no extra dependency.
|
||||
Group header bits: 1 = one literal byte; 0 = back-reference, 2 bytes {length high nibble, 12-bit distance} (distance + 1); length 0 reads one more byte + 0x12, else + 2. Overlapping copies are expanded by their period"""
|
||||
@@ -714,6 +770,9 @@ def scan_file(args):
|
||||
if len(args) > 2 and head[:8] == ONEPACK: # a range of Trinity packs (trpfs_jobs)
|
||||
trinity_shaders(path, rel, args[2], found)
|
||||
return rel, uniq_keys(found), var_pairs(found), trees
|
||||
if len(args) > 2 and args[2][0] == UE_LEGACY: # files of a legacy Unreal pak (ue_legacy_jobs)
|
||||
ue_legacy_shaders(path, rel, args[2][1], found)
|
||||
return rel, uniq_keys(found), var_pairs(found), trees
|
||||
if len(args) > 2 and head[:4] == KPKA: # entries of an RE Engine PAK (kpka_jobs)
|
||||
kpka_shaders(path, rel, args[2], found)
|
||||
return rel, uniq_keys(found), var_pairs(found), trees
|
||||
@@ -789,7 +848,7 @@ def main():
|
||||
# Unreal Engine shader code archives decoded on the Mac (uepak.py): <version dir>/ue_shaders, or DM_UE_SHADERS; needed when the RomFS has Unreal paks
|
||||
ued = os.environ.get('DM_UE_SHADERS') or os.path.join(os.path.dirname(os.path.abspath(romfs)), 'ue_shaders')
|
||||
names = sorted(f for f in os.listdir(ued) if f.endswith('.ushaderbytecode')) if os.path.isdir(ued) else []
|
||||
if not names and uepak.unreal_paks(romfs): red(f'the RomFS has Unreal paks, but there are no decoded shader code archives in {ued} (the NSP script writes them)')
|
||||
if not names and uepak.unreal_paks(romfs, legacy=False): red(f'the RomFS has Unreal paks, but there are no decoded shader code archives in {ued} (the NSP script writes them)')
|
||||
for nm in names: found += ue_shaders(open(os.path.join(ued, nm), 'rb').read(), f'/ue_shaders/{nm}')
|
||||
results.append(('main', {skey(c, k): (w, s, c, k) for w, s, c, k in found}, var_pairs(found), {}))
|
||||
zdicts = {k: v for r in romfs_v for k, v in zs_dicts(r).items()} # Tears of the Kingdom's zstd dictionaries (zs_dicts)
|
||||
@@ -799,8 +858,9 @@ def main():
|
||||
big = [j for p, r in files for j in (linkdata_jobs(p, r) or [])] # Three Houses' 6.9 GB DATA1.bin: one job per range of entries, not one read of the whole file
|
||||
pak_jobs, paks = kpka_jobs(files) # Monster Hunter Rise's PAKs: the newest copy of each path, in ranges of entries
|
||||
tr_jobs, trp = trpfs_jobs(files) # Pokémon Legends: Z-A's Trinity packs (data.trpfs and its index data.trpfd), decoded by the game's own Oodle in the Mac host
|
||||
big += pak_jobs + tr_jobs
|
||||
files = [(p, r) for p, r in files if not linkdata_jobs(p, r) and p not in paks and p not in trp]
|
||||
ue_jobs, uep = ue_legacy_jobs(files) # DRAGON QUEST XI S's legacy Unreal paks, decoded by the game's own LZ5 in the Mac host
|
||||
big += pak_jobs + tr_jobs + ue_jobs
|
||||
files = [(p, r) for p, r in files if not linkdata_jobs(p, r) and p not in paks and p not in trp and p not in uep]
|
||||
with cf.ProcessPoolExecutor(J, initializer=_pool_init, initargs=(common, {}, zdicts)) as ex:
|
||||
results += list(ex.map(scan_file, big)) + list(ex.map(scan_file, files, chunksize=16))
|
||||
trees, defer = {}, []
|
||||
|
||||
+58
-20
@@ -13,6 +13,12 @@
|
||||
# Encoded entry: u32 bits (0-5 compression block size >> 11, 0x3f = a u32 follows; 6-21 block count; 22 encrypted; 23-28 compression method, 1-based; 29 stored size fits 32 bits; 30 raw size fits
|
||||
# 32 bits; 31 offset fits 32 bits), then offset, raw size, stored size (compressed entries only) and, for several blocks or an encrypted one, each block's u32 stored size. The data starts after
|
||||
# the FPakEntry the pak repeats in front of it (53 bytes, + 4 + 16 per block when compressed); blocks follow each other, 16-aligned when encrypted.
|
||||
# Older paks (version 4, UE 4.18: DRAGON QUEST XI S, pakchunk0-Switch.pak 6.6 GB / 264,844 files and pakchunk1-Switch.pak 7.2 GB / 147,824 files, the update's *_0_P.pak beside them) keep the
|
||||
# legacy layout. Footer (the last 0x2d bytes): encrypted index flag (u8), magic, version, index offset, index size, SHA-1. Index: mount point, entry count, then per file its name (relative to the
|
||||
# mount point) and its FPakEntry: offset, stored size, raw size (i64 each), compression flags (i32), SHA-1 (20), for a compressed file the block count (i32) and each block's start and end
|
||||
# (i64, from the file start before version 5, from the entry after), the encrypted flag (u8) and the compression block size (u32). The FPakEntry is repeated in front of the data.
|
||||
# The compression flags are no names: 1 zlib, 2 gzip, 4 custom; DRAGON QUEST XI S's engine uses 3, which is LZ5 2.0 (main exports LZ5_*; every block starts with its level, 0x2d = 45), and only the
|
||||
# game's own decoder reads it (hle_oodle.c, through a decode function given to Pak.read).
|
||||
# Shader code archive (version 2): shader map hashes (20 bytes each), shader hashes (20 each), shader map entries {u32 first index, u32 shader count, u32 first preload, u32 preload count}, shader
|
||||
# entries {u64 offset, u32 size, u32 raw size, u8 frequency} (17 bytes, packed), preload entries (16 each), shader indices (u32): each array is a u32 count and its elements; the code follows,
|
||||
# offsets relative to its start. A shader whose size differs from its raw size is compressed.
|
||||
@@ -58,19 +64,32 @@ class R:
|
||||
if n < 0: return self.raw(-2 * n).decode('utf-16-le')[:-1]
|
||||
return self.raw(n)[:-1].decode('utf-8', 'replace') if n else ''
|
||||
|
||||
LEGACY = (4,) # legacy pak versions read (the per-file index); 11 has the encoded one
|
||||
LEGACY_METHODS = {1: 'Zlib', 2: 'Gzip', 3: 'LZ5', 4: 'Custom'} # compression flags of a legacy pak (3: DRAGON QUEST XI S's engine, see the header)
|
||||
|
||||
def pak_version(f):
|
||||
"""The version of a pak: a legacy footer is the last 0x2d bytes (magic 0x2c from the end); version 11's is 0xdd bytes, the magic 0xc8 from the end"""
|
||||
for back in (0x2c, 0xc8):
|
||||
f.seek(-back, 2); m, ver = struct.unpack('<II', f.read(8))
|
||||
if m == PAK_MAGIC: return ver
|
||||
raise ValueError('no pak footer')
|
||||
|
||||
def footer(f):
|
||||
f.seek(-0xdd, 2); ft = f.read(0xdd)
|
||||
k = ft.find(struct.pack('<I', PAK_MAGIC))
|
||||
if k < 1: raise ValueError('no pak footer')
|
||||
ver, ioff, isz = struct.unpack_from('<IQQ', ft, k + 4)
|
||||
if ver != 11: raise ValueError(f'pak version {ver}, only 11 is read')
|
||||
"""(encrypted index, index offset, index size, compression method names by number from 1, version)"""
|
||||
ver = pak_version(f)
|
||||
if ver in LEGACY:
|
||||
f.seek(-0x2d, 2); ft = f.read(0x2d); ioff, isz = struct.unpack_from('<QQ', ft, 9)
|
||||
return ft[0], ioff, isz, [LEGACY_METHODS.get(i, f'flags {i}') for i in range(1, 8)], ver
|
||||
if ver != 11: raise ValueError(f'pak version {ver}, only {", ".join(map(str, LEGACY))} and 11 are read')
|
||||
f.seek(-0xdd, 2); ft = f.read(0xdd); k = 0xdd - 0xc8
|
||||
ioff, isz = struct.unpack_from('<QQ', ft, k + 8)
|
||||
methods = [ft[k + 44 + 32 * i:k + 76 + 32 * i].rstrip(b'\0').decode('latin1') for i in range(5)]
|
||||
return ft[k - 1], ioff, isz, methods
|
||||
return ft[k - 1], ioff, isz, methods, ver
|
||||
|
||||
def find_key(img, lo, hi, pak):
|
||||
"""The AES key among main's 32-byte windows (read-only and data segments, 4-aligned, at least 20 distinct bytes): the one that decrypts the index start to a short mount point"""
|
||||
with open(pak, 'rb') as f:
|
||||
_, ioff, _, _ = footer(f); f.seek(ioff); blk = f.read(16)
|
||||
_, ioff, _, _, _ = footer(f); f.seek(ioff); blk = f.read(16)
|
||||
for a in range(lo, hi - 32, 4):
|
||||
w = img[a:a + 32]
|
||||
if len(set(w)) < 20: continue
|
||||
@@ -81,10 +100,21 @@ def find_key(img, lo, hi, pak):
|
||||
class Pak:
|
||||
def __init__(self, path, key=None):
|
||||
self.path, self.key = path, key; self.f = open(path, 'rb')
|
||||
enc, ioff, isz, self.methods = footer(self.f)
|
||||
enc, ioff, isz, self.methods, self.version = footer(self.f)
|
||||
if enc and not key: raise ValueError(f'{path}: the index is encrypted and no key was given')
|
||||
r = R(self.read_at(ioff, isz, enc))
|
||||
self.mount = r.fstr(); self.count = r.take('<i'); r.take('<Q')
|
||||
self.mount = r.fstr(); self.count = r.take('<i')
|
||||
if self.version in LEGACY: # one FPakEntry per file, kept as parsed
|
||||
self.files = {}
|
||||
for _ in range(self.count):
|
||||
name = r.fstr(); off, size, raw, meth = r.take('<qqqi'); r.raw(20)
|
||||
blocks = [r.take('<qq') for _ in range(r.take('<i'))] if meth else []
|
||||
e, bsz = r.take('<BI'); head = 53 + (4 + 16 * len(blocks) if meth else 0)
|
||||
if self.version < 5: blocks = [(s - off, t - s) for s, t in blocks]
|
||||
else: blocks = [(s, t - s) for s, t in blocks]
|
||||
self.files[name] = dict(off=off, raw=raw, size=size, method=self.methods[meth - 1] if meth else '', encrypted=e, block_size=bsz, blocks=blocks or [(head, size)])
|
||||
return
|
||||
r.take('<Q')
|
||||
if r.take('<i'): r.take('<QQ'); r.raw(20) # path hash index (not needed with the full directory index)
|
||||
if not r.take('<i'): raise ValueError(f'{path}: no full directory index')
|
||||
doff, dsz = r.take('<QQ'); r.raw(20)
|
||||
@@ -98,6 +128,7 @@ class Pak:
|
||||
self.f.seek(off); b = self.f.read((n + 15) & ~15 if enc else n)
|
||||
return aes_decrypt(self.key, b)[:n] if enc else b
|
||||
def entry(self, path):
|
||||
if self.version in LEGACY: return self.files[path]
|
||||
r = R(self.enc, self.files[path]); v = r.take('<I')
|
||||
bsz = (v & 0x3f) << 11
|
||||
if v & 0x3f == 0x3f: bsz = r.take('<I')
|
||||
@@ -109,11 +140,16 @@ class Pak:
|
||||
else:
|
||||
for _ in range(nblk): s = r.take('<I'); blocks.append((cur, s)); cur += (s + 15) & ~15 if enc else s
|
||||
return dict(off=off, raw=raw, size=size, method=self.methods[meth - 1] if meth else '', encrypted=enc, block_size=bsz, blocks=blocks)
|
||||
def read(self, path):
|
||||
"""A stored file's bytes (compressed files need the Oodle job)"""
|
||||
def read(self, path, decode=None):
|
||||
"""A file's bytes. A compressed one needs decode(block bytes, raw length) → bytes, block by block (each block holds block_size raw bytes, the last one the rest):
|
||||
the game's own decoder in the Mac host (shader_split.py oodle); Octopath Traveler 0's archives go through the Oodle job instead"""
|
||||
e = self.entry(path)
|
||||
if e['method']: raise ValueError(f'{path} is {e["method"]}-compressed in the pak')
|
||||
return self.read_at(e['off'] + 53, e['size'], e['encrypted'])
|
||||
if not e['method']: return self.read_at(e['off'] + 53, e['size'], e['encrypted'])
|
||||
if not decode: raise ValueError(f'{path} is {e["method"]}-compressed in the pak')
|
||||
out, left = [], e['raw']
|
||||
for s, n in e['blocks']:
|
||||
k = min(left, e['block_size']); out.append(decode(self.read_at(e['off'] + s, n, e['encrypted']), k)); left -= k
|
||||
return b''.join(out)
|
||||
|
||||
def archive(b):
|
||||
"""A shader code archive's tables: {'maps': [(first index, count)], 'shaders': [(offset, size, raw size, frequency)], 'indices': [...], 'code': start of the code}"""
|
||||
@@ -142,21 +178,22 @@ def paks(romfs):
|
||||
"""Every pak under the RomFS"""
|
||||
return sorted(os.path.join(dp, f) for dp, _, fs in os.walk(romfs) for f in fs if f.endswith('.pak'))
|
||||
|
||||
def unreal_paks(romfs):
|
||||
"""The paks under the RomFS that Unreal wrote (its magic in the footer, any version): a game whose shaders the NSP script decodes (decode_ue_shaders) and shader_split.py reads"""
|
||||
def unreal_paks(romfs, legacy=None):
|
||||
"""The paks under the RomFS that Unreal wrote (its magic in the footer; legacy None = any version, True = the legacy versions, False = the others): with version 11 the NSP script decodes
|
||||
the shader code archives (decode_ue_shaders); legacy paks keep the shaders inline in the cooked packages, which shader_split.py reads through the game's decoder (ue_legacy_jobs)"""
|
||||
out = []
|
||||
for p in paks(romfs):
|
||||
try:
|
||||
with open(p, 'rb') as f: f.seek(-0xdd, 2); ft = f.read(0xdd)
|
||||
except OSError: continue # shorter than a footer
|
||||
if ft.find(struct.pack('<I', PAK_MAGIC)) >= 1: out.append(p)
|
||||
with open(p, 'rb') as f: ver = pak_version(f)
|
||||
except (OSError, ValueError, struct.error): continue # shorter than a footer, or no Unreal pak
|
||||
if legacy is None or (ver in LEGACY) == legacy: out.append(p)
|
||||
return out
|
||||
|
||||
def job(romfs, main_nso, work):
|
||||
"""The shader code archives of every pak under the RomFS into <work>/archives, and one Oodle job (<work>/oodle.job) of every compressed shader in them; <work>/job.json lists them"""
|
||||
os.makedirs(os.path.join(work, 'archives'), exist_ok=True)
|
||||
img, lo, hi = nso_image(main_nso); key = None; listing, recs = [], []
|
||||
for pak in paks(romfs):
|
||||
for pak in unreal_paks(romfs, legacy=False):
|
||||
with open(pak, 'rb') as f: enc = footer(f)[0]
|
||||
if enc and key is None:
|
||||
key = find_key(img, lo, hi, pak)
|
||||
@@ -214,7 +251,8 @@ if __name__ == '__main__':
|
||||
f.write('path\traw\tstored\tmethod\tencrypted\tblocks\n')
|
||||
for path in sorted(p.files):
|
||||
e = p.entry(path); f.write(f"{path}\t{e['raw']}\t{e['size']}\t{e['method'] or 'none'}\t{e['encrypted']}\t{len(e['blocks'])}\n")
|
||||
print(f'pak version 11, mount {p.mount}, {len(p.files)} files, compression {[m for m in p.methods if m]}')
|
||||
used = sorted({p.entry(x)['method'] for x in p.files} - {''})
|
||||
print(f'pak version {p.version}, mount {p.mount}, {len(p.files)} files, compression {used}')
|
||||
elif cmd == 'job':
|
||||
listing, n = job(*sys.argv[2:5])
|
||||
for x in listing: print(f"{x['name']}: {x['shaders']} shaders, {x['compressed']} compressed")
|
||||
|
||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user