Last updated: 2026-07-27 (Release 1.1.0; rolling v1.1.1-dev / experimental v1.1.1-dev-EXP73; HEAD = dev; BETA-12-PLAY is ARCHIVAL/frozen). For current Settings behavior, Known Issues, Preservation Contracts, Behavioral Invariants, and Hardware Status, see STATE.md (canonical). Where dated historical sections below differ from STATE.md, STATE.md wins.
Current technical map of POPSLoader source files, their responsibility, and key
entry points. POPSLoader is a PS1-game launcher built on the Enceladus runtime:
EE C/C++, an embedded Lua application (bin/POPSLDR/*.lua compiled into the EE
ELF via bin2c), embedded IOP IRX modules, and a BRAM child ELF-loader. Every
technical claim below cites path:line against this worktree.
Scope note: this file documents what is actually present and wired in the
devrolling state (the active branch; BETA-12-PLAY is frozen). Where a component is on disk but unused (dead or dormant), that is called out explicitly rather than omitted. Several files an external audit flagged for removal — the 3D render pipeline (commit a56441c),md5, and the orphaned SMB /strUtilssource (commit f83dbbb) — have since been removed from the tree and are documented as removed below. (The old orphansrc/luaSMB.cppis gone; SMB (v1) is now a real, wired feature — a C binding inside theSystem.*surface plus the GSMBNET scene — documented below. Implemented this cycle, CI+Rolling green, validating on hardware.)
_ps2sdk_memory_init()(main.cpp:619) runs BEFOREmain()inside newlib's memory-init hook. Gated on-DRESET_IOP(set in Makefile:34, applied Makefile:59-61). It performsSifExitRpc -> SifInitRpc(0) -> fileXioExit -> while(!SifIopReset) -> while(!SifIopSync) -> SifInitRpc(0)(main.cpp:658-663) to recover from "polluted parent" launchers (wLaunchELF off non-HDD devices) whose live fileXio modules would otherwise hang a plainSifIopReset(ps2sdk #425). Anyone readingmain()top-down will miss this reset.detectBootDeviceHintFromArgv0()(main.cpp:134) derives an advisory pre-Lua boot-device hint fromargv[0]. HDD variantshdd/pfs/ata/apaall map to"HDD"(main.cpp:155-160); bothmassandusbmap to"USB"(main.cpp:139-144) — the MX4SIO-vs-USB disambiguation happens later in Lua.parseLaunchArgs()(main.cpp:198) parses NHDDL-style args:-page=/-mode=(-modeis a pure alias, both writelaunch_arg_page, main.cpp:220-226),-game=,-debug.main()(main.cpp:439) parses launch args, installs SBV patches, then loads the embedded IRX stack viaSifExecModuleBuffer(LOAD_IRX/LoadIrxChecked wrappers, main.cpp:388-410). Boot IRX order is fixed and partly conditional: iomanX (main.cpp:470) -> fileXio + fileXioInit (main.cpp:475-477, gated on iomanX) -> sio2man (main.cpp:488) -> mmceman ONLY if hint == MMCE (main.cpp:504-538; LoadIrxChecked call at main.cpp:507) -> mcman/mcserv (main.cpp:545-546) -> initMC (main.cpp:547) -> padman (main.cpp:548) -> libsd (main.cpp:550) -> usbd (main.cpp:554) -> ds34usb/ds34bt (main.cpp:558-559) -> audsrv (main.cpp:563). Device-specific stacks (BDM/usbmass/mx4sio/cdfs/HDD) are NOT loaded here; they are lazy-loaded on demand fromluasystem.cpp/luaHDD.cpp.- After IRX bring-up
main()sets boot path/app dir, inits gsKit + pad, then enters a loop running embeddedboot.luaviarunScript.
g_embedded_lua_assets(luaplayer.cpp) is the embedded script table:boot.lua,system.lua,ui.lua,images.lua(pops_profiles.luawas removed 2026-07-13 with the profile-preset system).runScript()(luaplayer.cpp:254) creates the Lua state, installs the embedded searcher (InstallEmbeddedLuaSearcher, luaplayer.cpp:272), disables on-disk script loaders (DisableLuaFilesystemScriptLoaders, luaplayer.cpp:273 — nilsdofile/loadfile, clearspackage.path/cpath), registers all Lua module bindings (luaplayer.cpp:282-288), then loads the requested embedded script buffer. A missing embedded asset is a hard FATAL, not a disk fallback (luaplayer.cpp:294-302).- Registered binding modules (luaplayer.cpp:282-288):
luaGraphics_init,luaControls_init,luaScreen_init,luaTimer_init,luaSystem_init,luaSound_init,luaHDD_init. NOTE: there is no separateluaSMB_init— the SMB (v1) network client lives inside theSystem.*surface (initSMB/connectSMB/disconnectSMB, registered byluaSystem_init; the Lua-sidePLDR.InitSMBPopsRootwraps them), not a standalone module. The old orphansrc/luaSMB.cpp(commit f83dbbb) is unrelated and removed (see Orphaned/dead-on-disk).
- Lazy IRX loaders (Layer C):
EnsureBDM/EnsureBDMFatFs/EnsureUsbMasschain (luasystem.cpp:80-120),EnsureMmceman(luasystem.cpp:141). - Mass-backend classification:
FetchBdmList/ClassifyMassBackend(luasystem.cpp:184-217), driver-name lookupGetMassMountDriverNameBySlot(luasystem.cpp:312). - MX4SIO init enforces USB mass first:
lua_mx4sio_init(luasystem.cpp:1298) callsEnsureUsbMass()(luasystem.cpp:1316) before loadingmx4sio_bd.irx(luasystem.cpp:1318). - External-ELF launch bindings:
lua_loadELF(luasystem.cpp:974),lua_loadELFWithPartition(luasystem.cpp:1020, requiresreboot_iop != 0and anhdd?:PART:partition context, luasystem.cpp:1033-1037),lua_loadELFRebootIOP(luasystem.cpp:1068). Keep-PFS mask bindinglua_set_exec_keep_pfs_mask(luasystem.cpp:945). - Launch-arg binding
lua_getLaunchArgs(luasystem.cpp:1211) and boot-hint bindinglua_getBootDeviceHint(luasystem.cpp:1227). - SMB (v1) network client (Path B = OPL's netman recipe; implemented this cycle,
CI+Rolling green, validating on hardware). Lazy net stack
EnsureNet(luasystem.cpp:1412): brings up dev9 once via the sharedEnsureDev9/g_dev9_loadedguard, then loadsnetman+smap+ps2ips+smbmanandps2ipand callsNetManInit— NEVER at boot, only on a menu/settings action. Bindings registered in theSystem.*table (luasystem.cpp:1776-1779):initSMB(lua_smb_init),connectSMB(lua_smb_connect, luasystem.cpp:1578),disconnectSMB(lua_smb_disconnect, luasystem.cpp:1724 —CLOSESHARE+LOGOFF, also torn down on a failed connect so no half-open session lingers). Connect drives theps2smb.hdevctls (LOGON/ECHO/OPENSHARE) onsmb0:. A blank Share field triggersSMB_DEVCTL_GETSHARELIST(luasystem.cpp:1686-1701) to enumerate the server's shares for the in-UI picker. EE links-lnetman-lps2ip-lps2ips. NetBIOS is NOT supported (deferred:nbns.irxis OPL-custom, not stock ps2sdk — address type must be IP). lua_rename(luasystem.cpp:753) is a non-atomic copy+delete, but the safe-promote fix has landed in this worktree: it calls the sharedcopy_file_contents(luasystem.cpp:714) and onlyremove()s the source if the copy returned 0 (luasystem.cpp:760-762).copy_file_contentsreturns -1 on an open/short-write/mid-stream-read error, so a failed copy no longer deletes the source. (There is no longer a separatelua_movefile; onlylua_renameis registered, luasystem.cpp:1330.copyFileis historical — the comment at luasystem.cpp:713 notes the shared helper was "historically used by copyFile".) Not on the launch path.
Load_HDD_IRX(luaHDD.cpp:120, exposed asHDD.Initializeat luaHDD.cpp:173) callsEnsureAtaBdm()(dev9 -> bdm -> bdmfs_fatfs ->ata_bd, i.e. ps2atad built withATA_ENABLE_BDM=1so one atad instance serves APA/PFS and exFAT), then ps2hdd-osd -> ps2fs, with HDD args-o 4 -n 20and PFS args-m 4 -o 10 -n 40. Plainps2atadis no longer loaded. There ARE settles now:sleep(1)before theata_bdload andsleep(1)after it (luasystem.cppEnsureAtaBdmModulesInner), matching NHDDL/wLaunchELF ordering.GetHDDStatusviaHDIOC_STATUS(luaHDD.cpp:93-96); on-demand mountMountPart/mnt(luaHDD.cpp:21-92) producingpfs%d:/mount points.HDD.MountPartitionreturns(ok, rc)— the second value is the rawfileXioMountrc (2026-07-09 diagnostics;mntno longer collapses failures to a constant-4).HDD.ListPartitions(2026-07-09) — read-only APA-table enumerator:fileXioDopen("hdd0:")+fileXioDread, keeping main-partition records with the PFS format magic (the OPL / wLaunchELF technique; constants mirror ps2sdklibhdd.h). Names are snapshotted into a static buffer and the dir fd closed BEFORE any Lua-heap call so an allocation longjmp can't leak a ps2hdd slot. Returns the name array, ornil, rcbefore ps2hdd loads. Consumed byPLDR.HDD.DiscoverPartitionGames(partition-installedPP./__.games).
embedded_get()(embed_assets.cpp:195) normalizes paths (stripsembed:/, embed_assets.cpp:205;./, embed_assets.cpp:209; leading/, embed_assets.cpp:212) and resolves against the staticg_embedded_assetstable (embed_assets.cpp:93-181) viaembedded_find(embed_assets.cpp:184). There is NO icon/MISSING.pngfallback — a missing key returns 0 and the Lua caller decides. Each asset appears TWICE in the table: under its bare name and under aPOPSLDR/IMG/-prefixed key (mirror tables at embed_assets.cpp:94-138 and 141-181).default.pngis an OPTIONAL legacy cover override, declared and added only inside#ifdef HAVE_ASSET_DEFAULT_PNG(extern embed_assets.cpp:56-59; entries embed_assets.cpp:117 & 164); the cover box does NOT depend on it. The newcover_default.png/cover_missing.pngplaceholders are MANDATORY embeds (extern embed_assets.cpp:60-63; entries embed_assets.cpp:119-120 & 166-167).
src/graphics.cpp/src/luagraphics.cpp— gsKit 2D drawing +Graphics.*Lua bindings (luaGraphics_init, luagraphics.cpp). Image load/draw, theGraphics.loadImageEmbeddedpath used byimages.lua. Also hosts the overscan (CRT inset) transform (graphics.cpp:1128-1166):g_overscanpermille (graphics.cpp:1135),set_overscan/get_overscan(graphics.cpp:1155, 1163; clamped 0..200),recompute_overscan(graphics.cpp:1140), and theOVX()/OVY()inline scalers (graphics.cpp:1165-1166) that every gsKit draw site routes through. The math is OPLrmSetOverscanexactly (margin = W*permille/2000 per edge, scale = 1 - permille/1000); at permille 0 the transform is the IDENTITY so the default render is unchanged, andrecompute_overscanis re-run on a screen-dim change (graphics.cpp:1339).src/fntsys.cpp/src/atlas.cpp— TrueType font system and glyph atlas.src/luaScreen.cpp(luaScreen_init) —Screen.*flip/clear bindings, plus the overscan Lua surfaceScreen.setOverscan(permille)/Screen.getOverscan()(lua_set_overscanluaScreen.cpp:64 /lua_get_overscanluaScreen.cpp:71; registered luaScreen.cpp:162-163) wrapping graphics.cpp'sset_overscan/get_overscan.src/luacontrols.cpp(luaControls_init) — pad input, registered under the globalPads(NOTControls;Pads_functionsluacontrols.cpp:273,lua_setglobal "Pads"luacontrols.cpp:289).Pads.getMode()(lua_getmodeluacontrols.cpp:70; registered luacontrols.cpp:278) returns the LIVE negotiated controller mode viapadInfoMode(port,0,PAD_MODECURID,0)(luacontrols.cpp:78) — high nibble 0x5 analog / 0x7 DualShock / 0x4 digital / 0 no-data. The pre-existingPads.getType()(lua_gettypeluacontrols.cpp:9; registered luacontrols.cpp:277) readsPAD_MODETABLE(a capability-table entry, luacontrols.cpp:17) and is NOT the live mode —ui.lua's stick-fold gate must usegetMode.getLeftStick/getRightStick(lua_getleftluacontrols.cpp:82 /lua_getrightluacontrols.cpp:119) are hardened: zero-init + neutral (0,0) default, and they only report a non-neutral axis when a pad read actually returned data, so an unread/failed frame can't inject a phantom -127. ThePAD_ANALOG/PAD_DUALSHOCKglobals the gate compares against are exported here (luacontrols.cpp:343, 346).src/pad.cpp— low-level pad init/read.src/sound.cpp/src/luasound.cpp(luaSound_init) — audsrv-backed audio.src/luatimer.cpp(luaTimer_init) —Timer.*bindings.src/system.cpp— small EE system helpers.
The legacy 3D render pipeline (src/render.cpp, src/calc_3d.cpp, src/gsKit3d_sup.cpp, src/luaRender.cpp and the Render/Lights/Camera Lua bindings, plus the -lmath3d link) was dead at the application level and has been removed from the tree (commit a56441c). These files are no longer on disk, no longer in the Makefile object lists, and luaRender_init is no longer called from luaplayer.cpp.
src/luaSMB.cpp(the old orphan SMB network-share logon helpers) was orphan source — never in the Makefile object lists and never initialized — and was DELETED in commit f83dbbb (2026-06-13). It no longer exists in the tree. This dead file is NOT the current SMB feature: SMB (v1) was re-implemented this cycle as a live C binding inside theSystem.*surface (luasystem.cpp,EnsureNet/lua_smb_connect) plus Lua wiring (GSMBNET scene, OPT7) — see the luasystem.cpp SMB notes above and Feature Surface below.
All bin/POPSLDR/*.lua are bin2c'd into the EE ELF at build time; the on-card
copies are not read at runtime. Editing them requires a rebuild.
- Owns device resolution, settings persistence, game-list building, the HDD
cache, and the launch engine.
requires ui/images. - Boot-device classification:
ResolveBootContext(system.lua:1849) /DetectBootDevice(system.lua:1983), prefix rules underResolveBootContext.mass:/is disambiguated via the BDM driver name (classify_mass_boot, system.lua:1866;sdc/mx4=> MX4SIO). - Launch-arg ingest:
NormalizeLaunchPage(system.lua;ata*->EXFAT,hdd*/apa*/pfs*->HDD, barebdma->no-op page value),PLDR.LAUNCH_ARGS, carousel page auto-navpage_to_opt(MMCE=1/MX4SIO=2/EXFAT=3/ATA=3/HDD=4/USB=5/SMB=7). - Settings:
EncodeSettings(24 keys + appended SMB block: POPSTARTER_PATH (""= Automatic; the legacy PROFILE/POPSTARTER_MODE keys are no longer written, and on load a legacy PROFILE=N pick migrates into POPSTARTER_PATH — profiles dropped 2026-07-13), BDMA, BDMA_ADAPTIVE, DKWDRV_PATH, STRICT_HDD_PREEXEC_GATE, VIDEO_STANDARD, HIDE_TEXT, KEYBOARD_LAYOUT, LANGUAGE, BOOT_PAGE, MULTIDISC_COLLAPSE, GLOBAL_HIDE, POPSTARTER_MC_FOLDER, HIDDEN_DEVICES, SHOW_DETAILS, DETAILS_ALIGN, ART_LOCATION, HDD_FS, COVER_ART, GAMELIST_CACHE, BOOT_SOUND, OVERSCAN, SMB_MODULES — the SMB connection block is appended after these by SmbAppendLines),LoadSettingsNonFatal(system.lua:3650, normalizes CRLF before parsing — a Notepad-edited sidecar used to silently revert most settings),SaveSettingsAtomic(system.lua:3598, retries the MC fallback once when a non-MC sidecar write fails) ->WriteAtomic(system.lua:2793),CommitSettingsChanges(transactional, system.lua:3912). Per-device sidecar.pldrsat APP_DIR for every device; HDD installs now persist on the HDD boot partition via thePLDR.HDD.EnsureBootPartitionWritableRW mount take-over (system.lua:2159) — nomc0:carve-out.mc0:/POPSTARTER/.pldrsremains only a legacy fallback. SeeSTATE.md > Settings (single-device parity). - Game-list builders:
GetPS1GameLists(system.lua:4529, MMCE/MX4SIO, bare basenames),BuildMassGameListByType(system.lua:4593, USB,root|name),HDD.BuildGameList(system.lua:4720,partition|relpath). HDD cache (CreateCachesystem.lua:4880 /ReadCachesystem.lua:4927) is gated on thePLDR.GAMELIST_CACHEsetting (opt-in, default OFF; default set at system.lua:3672; runtime gate checks live in the cache save/load helpers).USECACHE(system.lua:2048) is a dead legacy flag. The sameGAMELIST_CACHEgate covers the USB/MMCE/MX4SIO list cache (SaveGameListCachesystem.lua:4816; there is no separateReadGameListCachefunction — the HDD reader isPLDR.HDD.ReadCachesystem.lua:4927). - Launch engine:
LaunchEngine(system.lua:5436),RunPOPStarterGame(system.lua:5653),BuildPopstarterLaunchCommand(system.lua:5631, sets per-devicereboot_iop). HDD pre-exec gateValidateHddPopstarterExecGate(system.lua:1655). Keep-PFS-mask prepPrepareForExternalELFLaunch(local def system.lua:1120, PLDR wrapper system.lua:1793). - POPSTARTER.ELF resolution
PLDR.ResolveLaunchPopstarterPath(system.lua) is PER DEVICE, existence-gated at each step so a device with no copy falls through. REMOVABLE (USB / exFAT-ATA / MX4SIO / MMCE), in order:- the explicit user-configured ABSOLUTE path (the "POPSTARTER Path" setting)
when it resolves; 2. the game's own
<device>:/POPS/POPSTARTER.ELFwhen it exists (lets a per-device build — e.g. a USB-delay POPSTARTER dropped in the USB drive'sPOPSfolder — be used WITHOUT forcing it); 3.POPSTARTER.ELFin the launcher's own folder (cwd,ResolveCwdSidecarPopstarter); 4. themc0:/mc1:/POPSTARTERfallback net. INTERNAL-PFS HDD (APA): 1. custom; 2.hdd0:__common/POPS/POPSTARTER.ELF(resolved through the partition machinery that preserves the D-10/D-15 partition-context
- embedded-loader handoff); 3. cwd /
boot-sidecar; 4. mc net. The 16-preset profile system was removed
2026-07-13 (R3Z3N review):
PLDR.POPSTARTER_PATHis the single value,""= Automatic (the ladder), a set path = step-1 custom with silent fallback; the ladder itself is unchanged. Validating on hardware.
- the explicit user-configured ABSOLUTE path (the "POPSTARTER Path" setting)
when it resolves; 2. the game's own
- Startup ordering at module end:
LoadSettingsNonFatal->AutoInitStartupBackends(system.lua:3920) ->SurfaceLaunchArgsDebug->AutoLaunchFromLaunchArgs(system.lua:6139), then the single render loop (system.lua:6364-6376; dispatch body 6365-6375 per-scenePlay()+UI.flip()).
- Defines one
UItable literal (ui.lua:446) andreturn UI(ui.lua:4991). Contains all scenes, the scene/transition state machine, notification queue, busy overlay, cover-art cache, path-editor keyboard, modals, and input layer. It has NO main loop — the loop lives at the bottom of system.lua. - Scenes enum
UI.SCENES(ui.lua:448-458): GUSBFAT=1, GSMB=3 (the MMCE list page — despite the name, NOT the network-SMB page), GMX4SIO=4, GHDD=5 (GAPAHDD aliases 5), GBDMHDD=6, GSMBNET=7 (the live SMB (v1) network page), MMAIN=8, MPROFILE=9, CREDITS=10. SMB (v1) now has its OWN dedicated scene (GSMBNET=7); it does NOT reuse GSMB=3. - Main menu carousel
UI.MainMenu(table ui.lua:3976, opts ui.lua:3978; the CONFIRM/Play dispatch is the MainMenuPlayhandler at ui.lua:3997, OPT switch ~4227-4404). Game listUI.GameList(table ui.lua:2324), launch triggerLaunchSelectedGame(ui.lua:2750). Settings pageUI.ProfileQuery(table ui.lua:2976,Playui.lua:2979). DKWDRV/BOOT.ELF/exit handoffsOpenDKWDRV(ui.lua:1371),LaunchBootElf(ui.lua:1592),ConfirmExit. - Input layer
UI.Pad.Listen(ui.lua:4440) — folds the LEFT ANALOG STICK into the d-pad bits and resolves nav. Both timing concerns here are now FRAME-COUNTED, not wall-clock (Timer.getTime()reads microseconds on PS2, so the old_msgates fired every frame):- Nav auto-repeat
resolve_nav(ui.lua:4583):nav_fps= 50 when SCR.Y>=512 else 60 (ui.lua:4580);NAV_DELAY_FRAMES = ceil(nav_fps*0.6),NAV_RATE_FRAMES = ceil(nav_fps*0.2)(ui.lua:4581-4582); a per-directionUI.Pad.NavHoldFramescounter (ui.lua:4430) ticks once per frame. Press fires immediately; only UP/DOWN repeat (~0.6 s delay then ~0.2 s, ~5/s) — LEFT/RIGHT stay edge-only (ui.lua:4607-4610). - Stick→d-pad fold (ui.lua:4469-4511) is GATED on
Pads.getMode()reportingPAD_ANALOG/PAD_DUALSHOCK(ui.lua:4470-4475) plus a per-axis hysteresis latch (StickV/StickH, assert |v|>64, release <40; ui.lua:4479-4503). On a digital/non-analog pad the fold is skipped and any latch is dropped (ui.lua:4506-4511), so stale analog bytes can't inject a phantom PAD_UP/LEFT.
- Nav auto-repeat
- Cover-art preview box (game-list render, ui.lua:2440-2591) layers two
placeholder assets and NO longer draws a "Cover disabled" text label: no live
cover →
IMG.cover_default(ui.lua:2582-2583); preview ENABLED but the game has no cover →cover_defaultwithIMG.cover_missingoverlaid (ui.lua:2585-2586); a LIVE cover uses its own COVER_W inset (ui.lua:2573-2576). The default, the missing overlay, andIMG.frameall share the frame's aspect-corrected, right-anchored rect (frame_x/draw_y/frame_w/frame_h, ui.lua:2572-2590) so they register with the jewel-case window on both NTSC and PAL. - Right-stick description scroll (ui.lua:2669-2700) is FRAME-COUNTED too via
UI.GameList.DescScrollFrames(ui.lua:2334): step everyceil(_secs*fps)frames (ui.lua:2684-2687), fixed at the Fast pace (_secs= 0.15, ~7 lines/sec; the Fast/Medium/Slow "Description scroll speed" setting was removed). - Cover-art LRU
CoverCache(ui.lua:305-374), candidate builderBuildCoverCandidates(ui.lua:190). On removable devices the cover is ONE path with an EXACT filename (EXP71):BuildCoverCandidatesreturns a single candidate<device-root>/ART/<gamefilename>_COV.png. The Cover/details folder setting was removed in EXP35 (PLDR.ART_LOCATIONstill parses but is INERT), there is noPOPS/ARTmode, no beside-the-.vcdfallback, and no disc-marker-stripped family name — so multi-disc games need art per disc. HDD/PFS is fixed tohdd0:__common/POPS/ART/<gamefilename>_COV.pngviaPLDR.ResolveHddPartitionReadablePath. The.txtdetails sidecar does NOT ride the cover candidates: removable devices compute<device-root>/ART/<gamefilename>.txt(no_COV) and hand it to the resident cover worker (Graphics.coverLoadTextPath), and HDD resolves it separately.GetOrLoadloads viaGraphics.loadImage/fopendirectly with NOdoesFileExist/open()pre-probe -- a redundant-syscall cleanup, since ps2sdk routesopenandfopenthrough the same libcglue_open(nested reads work; OPL readsmass:/ART/the same way). A missingPOPS/ARTcover is a filename/location issue, and the list view's "No cover. Looked for: " caption and itslast_cover_probestate were REMOVED (EXP42) -- a missing cover just draws the placeholder. Cover drawing is gated by the persistedCOVER_ARTsetting (UI.CoverPreviewEnabled, applied viaUI.SetCoverPreview); the Square toggle is gone. - WRITE-GUARD GOTCHA:
__newindexmetatables onUI.MainMenuandUI(ui.lua:4957 & 4979) silently DROP writes toUI.MainMenu.OPT(unlessCarousel.allowOptWrite, ui.lua:4960) andUI.CURSCENE(unlessTransition.allowSceneWrite, ui.lua:4981). Build-info display readsBUILD_INFO.txt(LoadBuildInfo, ui.lua:4687).
IMG_REGISTRATIONS(images.lua:11-37): 25{key, filename}pairs (device icons, backgrounds, splash layers, button/d-pad glyphs,frame, the optional legacydefault, and the cover placeholderscover_default+cover_missing; the oldmissingkey is GONE).IMG_SOURCESmaps each key to its bare filename (images.lua:39-44); thecover_default/cover_missingcovers are consumed by the ui.lua preview box viaIMG.cover_default/IMG.cover_missing. LazyIMGmetatable (__index, images.lua:51) fetches each PNG by that filename throughSystem.getEmbeddedAsset->Graphics.loadImageEmbedded(images.lua:56-59) and caches it.IMG_FALLBACKS(images.lua:46) is now an EMPTY table — the olddefault -> missingfallback was removed withMISSING.png, so an unresolved key just returns nil (marked inIMG_FAILED). This is UI chrome, NOT per-game box art (game covers areUI.CoverCachein ui.lua).
etc/boot.lua(HDD-boot branch etc/boot.lua:37) mounts the HDD boot partition topfs1:(warning "NEVER USE IT FOR ANYTHING ELSE", boot.lua:48), normalizes cwd topfs1:(boot.lua:63-64), loads fonts, thenrequire("system")(boot.lua:181).System.sleep(2)(boot.lua:47) is SECONDS, not ms (the binding calls Csleep, luasystem.cpp:774-780), so it is a full 2-second HDD settle. CI requires this file end with a0x0Anewline.etc/update_lua_globals.sh— dev helper for syncing Lua globals.
src/elf_loader/src/elf.c— the EE-side parent loader. Central reboot/HDD routing forkLoadELFFromFileExecPS2RebootIOPWithPartition(elf.c:618): HDD partition AND filename both HDD-backed ->ExecuteHddBackedViaEmbeddedLoader(elf.c:648); resolved-path/partition HDD-backed -> same (elf.c:656); else directSifLoadElf(elf.c:661) -> unmount-pfs (keep-mask) -> SifIopReset -> reload rom0:SIO2MAN/MCMAN/MCSERV -> ExecPS2. BOOT.ELF and DKWDRV-HDD special-cases inLoadELFFromFileWithPartition(elf.c:481, BOOT.ELF mc-case elf.c:499, DKWDRV elf.c:505/519).ExecuteViaEmbeddedLoader(elf.c:397) writes BRAM metadata (magicPOPL, addr0x00083C00; defines elf.c:159-160) andExecPS2s the child.src/elf_loader/src/loader/src/loader.c— the BRAM child loader.main()(loader.c:280) reads metadata at0x00083C00(loader.c:144-145) and branches three ways beforeExecPS2: filexio-direct (loader.c:373-379); the HDD-partition-context branch for D-10 (loader.c:381-403 — unmounts the pfs prefix, thenSifExitRpc+SifExitCmdat loader.c:396-397); and the generic/empty-context branch for BOOT.ELF/DKWDRV (loader.c:404-427 —SifExitRpconly at loader.c:404, INTENTIONALLY omitsSifExitCmd, comment loader.c:405; the comment marks that omission as the historical D-15-pass vs D-10-fail difference). Do not addSifExitCmdto the generic branch.src/elf_loader/loader.c— committed ~6.5 MB bin2c blob of the builtloader.elf(symbolloader_elf), regenerated bymake elfloader.src/elf_loader/Makefile/src/elf_loader/src/loader/Makefile— the nested two-stage loader build (stage 1 buildsloader.elfinto BIOS memory; stage 2 bin2c-embeds it and archiveslibcustom-elf-loader.a).
iop/bdm_query/bdm_query.c— in-tree IOP RPC helper (id0xB0D10B00defined bdm_query.c:11, registered bdm_query.c:76; handler bdm_query.c:36) enumerating live block devices viabdm_get_bd(); the EE side classifies each backend by driver-name substring. Built from source (Makefile:241-245).iop/embed/— pinned/in-tree IRX blobs bin2c'd into the ELF:bdm.irx,bdmfs_fatfs.irx,bdmfs_vfat.irx,mx4sio_bd.irx(+mx4sio_bd_mini.irx), plus thePS2SDK_MX4SIOandBDMASSAULT_MX4SIOsource pins. The activemx4sio_bd.irxis pinned fromiop/embed/PS2SDK_MX4SIO(Makefile:247-251). Other IRX (iomanX, fileXio, sio2man, mcman, mcserv, padman, libsd, usbd, audsrv, usbmass_bd, cdfs, ps2dev9, ps2atad, ps2hdd-osd, ps2fs, mmceman, ata_bd) resolve from$(PS2SDK)/iop/irx/(vpath Makefile:216, object list Makefile:88-93).
modules/ds34usb/andmodules/ds34bt/— DS3/DS4 USB and Bluetooth support, built as EE static libs (EXT_LIBS, Makefile:72) and as IRX (ds34usb.o/ds34bt.o, Makefile:85).modules/pademu/— pad-emulation IOP module sources (ds34bt/ds34usb/pademu).modules/Rules.bin.make— shared module build rules.
POPSTARTER.ELF(the PS1 emulator front-end launched per game),PATCH_5.BIN,boot.adp,APPINFO.PBT,title.cfg, MC icon set (icon.sys/list.icn/copy.icn/del.icnplus.bdmavariants), theIMG/source PNGs, and device-variant IRX (usbd.irx.{usbexfat,mx4sio,mmce},usbhdfsd.irx.{usbexfat,mx4sio,mmce}).
Makefile— builds the EE ELF, bin2c-embeds all Lua/PNG/IRX assets (BIN2S = $(PS2SDK)/bin/bin2c, Makefile:67;EMBEDDED_RSCMakefile:96-105), links the child-loader lib, strips, and runsps2-packerto producebin/POPSLOADER.ELF(Makefile:118-120).make elfloader(Makefile:257-262) force-regenerates the child loader.RESET_IOP = 1(Makefile:34) compiles in the pre-main IOP reset.default.pngis the only OPTIONAL embed: it is added toOPTIONAL_EMBEDDED_RSCand defines-DHAVE_ASSET_DEFAULT_PNG=1ONLY when the file is present in the checkout (wildcard guard, Makefile:72-75); everything else (incl. the newcover_default.png/cover_missing.png, BIN2S rules Makefile:179-182) is mandatory.- EMBED MECHANISM (adding/removing an embedded asset is THREE coordinated places,
NOT an auto-glob, and they must be kept in sync MANUALLY — there is no
machine-checked embed manifest): (a)
Makefile— aBIN2SPNG rule plus the.oinEMBEDDED_RSC; (b)src/embed_assets.cpp— anexterndeclaration plus anASSET_ENTRYin BOTH the bare-name and thePOPSLDR/IMG/-prefixed mirror tables; (c)bin/POPSLDR/images.luaIMG_REGISTRATIONS(looked up by the bare filename). TheMISSING.pngremoval (−62 KB ELF) touched all three plus the ui.lua draw path and was the reference example of this dance. .github/workflows/compilation.yml— CI on all branches/tags/PRs/dispatch: runs the now-LIVE embedded-Lua syntax gate (apk add lua5.4+luac5.4 -ponbin/POPSLDR/*.lua+etc/boot.lua, hard-fail on syntax error — it used to silently no-op because the pinned ps2dev image had noluac; catches SYNTAX only, so runtime nil-global / type / load-order errors stay invisible), generatesBUILD_INFO.txt, runsmake clean elfloader all, enforces embed-identity gates (three string markers in the ELF + loader.c parity), and packages the strict-verifiedPOPSLOADER.zipinstall bundle as an artifact (no GitHub release). The redistributablePOPSTARTER.ELFships inPS1_POPSLOADER/next toPOPSLOADER.ELF(compilation.yml:161) AND inPOPS/(compilation.yml:171), both on the manifest's required-file list. SeeSTATE.md > CI / release..github/workflows/rolling-release.yml— on push to dev (rolling-release.yml:6) and PR events: bundles the ELF + full git-tracked source and force-updates therolling-releaseprerelease via the GitHub API. The redistributablePOPSTARTER.ELFnow ships at the ZIP ROOT next toPOPSLOADER.ELF(rolling-release.yml:182) AND inPOPS/(rolling-release.yml:192);POPS/PATCH_5.BINand aPOPSTARTER/pack folder also ship at the root (rolling-release.yml:173). (POPS engine binaries remain NON-redistributable — users supply their own.).github/workflows/opencode.yml—/occomment bot (DeepSeek). Not part of release packaging or runtime behavior.
Dispatch in the MainMenu Play handler (ui.lua:3997), OPT switch ~ui.lua:4227-4404.
MMCE(OPT1): implemented.MX4SIO(OPT2): implemented.HDD (PFS)(OPT4): implemented (routes to scene GHDD=5).USB(OPT5): implemented.Disc (DKWDRV)(OPT8): implemented.HDD (exFAT)(OPT3): implemented — scans the exFAT internal drive as amass:backend (BDMAata) viaInitATAPopsRoot+GetPS1GameLists-> scene GBDMHDD=6 (ui.lua:4349). Classified by exact ioctl driver-nameata. Validating on hardware.i.Link(OPT6): NOT implemented (ui.lua:4462).SMB (v1)(OPT7): implemented — routes to scene GSMBNET=7. SMB / Network settings (server IP, share, user/password, IP assignment DHCP-or-static, port, games path/cwd, link mode; IP addressing only) plus an "SMB modules" install toggle that copies the POPStarter in-game SMB streaming pack (6 IRX: poweroff/ps2dev9/ps2ip/ps2smap/smbman/SMSUTILS) intomc:/POPSTARTERand generatesIPCONFIG.DAT+SMBCONFIG.DATfrom the settings (backfilled on every settings save while the pack is installed). CONNECT is LAZY (net stack- share open only on entering the SMB page or a settings action, never at
boot) via the luasystem.cpp
EnsureNet/connectSMBbindings; BROWSE scans the share's POPS folder and lists VCDs; a blank Share field opens an in-UI picker driven byGETSHARELIST; LAUNCH hands off to POPStarter with argv0 selectorsmb:/POPS/SB.<name>.ELF(POPStarter streams the VCD from its ownsmb:/POPSmount viamc:/POPSTARTER/SMBCONFIG.DAT); DISCONNECT (CLOSESHARE+LOGOFF) on leaving the page. NetBIOS is deferred (address type must be IP). Implemented this cycle, CI+Rolling green, validating on hardware — the exact argv0 device prefix POPStarter accepts (fallbacksmass:/POPS/SB.<name>.ELFthenmass:/SB.<name>.ELF), the connect handshake, and theGETSHARELISTDMA are the hardware-only unknowns. See the luasystem.cpp SMB notes.
- share open only on entering the SMB page or a settings action, never at
boot) via the luasystem.cpp
- D-10 (HDD POPSTARTER + HDD game): the BRAM child-loader route via the
HDD-partition-context branch (loader.c:381-403, SifExitRpc+SifExitCmd at
396-397) — unmount the pfs prefix, then
SifExitRpc+SifExitCmd. (The generic branch loader.c:404-427, which omitsSifExitCmd, is the BOOT.ELF/DKWDRV path, not D-10.) - D-15 (non-HDD POPSTARTER + HDD game): keep-PFS mask preserves the boot
partition's
pfs1:slot (elf.c keep-mask, system.lua:1120). - DKWDRV from HDD inherits the same embedded-loader path as POPSTARTER (elf.c:628-644); the V3 direct-reset route black-screened on hardware.
- BRAM metadata struct (
partition_context[128],load_path[256], magicPOPL) must stay byte-identical between writer (elf.c) and reader (loader.c). - Settings sidecar (single-device parity): USB/MC/MMCE/MX4SIO and HDD all use
the per-device
.pldrssidecar. HDD installs persist on the HDD boot partition via thePLDR.HDD.EnsureBootPartitionWritableRW mount take-over (system.lua:2159) — there is nomc0:HDD carve-out. That take-over is now load-bearing for HDD settings save and HDD in-app.hide; don't regress it. (Supersedes the old HDD-to-MC exception.) SeeSTATE.md > Preservation ContractsandSTATE.md > Settings (single-device parity).
Settle note:
Load_HDD_IRXroutes throughEnsureAtaBdm(), which applies a 1-second settle before loadingata_bdand another after it (seeEnsureAtaBdmModulesInnerinsrc/luasystem.cpp). The older "no cold-dev9 settle / fix lives on a separate branch" note is superseded — rolling publishes fromdev, which is this branch.
- Settings persistence/apply:
bin/POPSLDR/system.lua,bin/POPSLDR/ui.lua. - Device detection/classification:
bin/POPSLDR/system.lua,src/luasystem.cpp,iop/bdm_query/bdm_query.c. - Launch handoff/argv/path:
bin/POPSLDR/system.lua,src/luasystem.cpp,src/elf_loader/src/elf.c,src/elf_loader/src/loader/src/loader.c. - HDD bring-up/mount:
src/luaHDD.cpp,bin/POPSLDR/system.lua. - Embedded asset add/resolve:
Makefile(bin2c rule +EMBEDDED_RSC),src/embed_assets.cpp, plus the consuming Lua table. - Packaging/release:
Makefile,.github/workflows/compilation.yml,.github/workflows/rolling-release.yml.