diff --git a/CHANGELOG.md b/CHANGELOG.md index 829ba32..2bb944e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## v0.4.1 -* **Single ELF** like Payload Manager: `bfpilot.elf` runs the port-5905 file manager **and** installs the PS5 home **Media** tile (`applicationCategoryType` 65536, title `BFPL00001`, deeplink `http://127.0.0.1:5905/`) via embedded AppInst -* Optional recovery installer `bfpilot-launcher-installer.elf` only under `/data/BFpilot/` (not a separate data tree) -* **One data directory**: all logs/index/shortcuts/archive state under `/data/BFpilot` only (no `/data/bfpilot`, `/data/BFPILOT`, or `/data/bfpilot-launcher-installer` roots) +* **Separate payloads** (stable model): `bfpilot.elf` = file manager only; `bfpilot-launcher-installer.elf` = home **Media** tile (`applicationCategoryType` 65536, `BFPL00001` → `http://127.0.0.1:5905/`) +* Main ELF does **not** link AppInst (loaders reject all-in-one); inject the installer separately or place it under `/data/BFpilot/` so the main ELF can auto-start it after the UI is up +* **One data directory**: all logs/index/shortcuts/archive state under `/data/BFpilot` only * Action bar wraps (no horizontal scrollbar) for New Folder / Rename / Download / Load payload / chmod / Extract / Upload / Delete * **Load payload**: double-click `.elf`/`.bin`/`.js` or toolbar — streams to elfldr on `127.0.0.1:9021` * New API: `GET /api/fs/launch?path=...` diff --git a/Makefile b/Makefile index deb7835..7f5f53c 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,6 @@ WEB_SRCS += src/transfer.c WEB_SRCS += src/payload_launch.c WEB_SRCS += src/tile_bootstrap.c WEB_SRCS += src/path_ops.c -WEB_SRCS += src/app_installer.c LAUNCHER_INSTALLER_SRCS := src/launcher_installer_force_main.c LAUNCHER_INSTALLER_SRCS += src/boot_marker.c @@ -143,9 +142,9 @@ BFPILOT_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot\" BFPILOT_CFLAGS += -DBFPILOT_BUILD_MODE=\"file-manager\" BFPILOT_CFLAGS += -DBFPILOT_WEB_PORT=$(WEB_PORT) BFPILOT_CFLAGS += -DBFPILOT_DEBUG_NOTIFICATIONS=0 -# Embedded Media-tile AppInst in main ELF (Payload Manager model). -BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=1 -BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=0 +# AppInst stays in separate launcher-installer only (main ELF must not link it). +BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0 +BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1 BFPILOT_CFLAGS += -DBFPILOT_ENABLE_INTEGRATED_ARCHIVE=$(ENABLE_ARCHIVE) ifeq ($(ENABLE_ARCHIVE),1) @@ -219,16 +218,10 @@ WINDOWS_CXX_LINK_PREFIX += -L"$(PS5_PAYLOAD_SDK_SHELL)/target/lib" WINDOWS_CXX_LINK_PREFIX += -L"$(PS5_PAYLOAD_SDK_SHELL)/target/user/homebrew/lib" -L. WINDOWS_CXX_LINK_PREFIX += -l:crt1.o -l:crti.o -l:crtbegin.o WINDOWS_CXX_LINK_PREFIX += -lunwind -lc++abi -lc++ -lc -# Main ELF = file manager + Media tile install (AppInst + UserService). -WINDOWS_CXX_LINK_SUFFIX := -lSceNotification -lSceAppInstUtil -lSceUserService -WINDOWS_CXX_LINK_SUFFIX += -lSceSystemService -lkernel_sys -lkernel_web -WINDOWS_CXX_LINK_SUFFIX += -lSceLibcInternal -lSceNet -lpthread -WINDOWS_CXX_LINK_SUFFIX += -lc_stub_weak -lkernel_stub_weak +# Main ELF = file manager only (no AppInst — loaders reject it in this payload). +WINDOWS_CXX_LINK_SUFFIX := -lSceNotification -lkernel_web -lSceLibcInternal +WINDOWS_CXX_LINK_SUFFIX += -lSceNet -lpthread -lc_stub_weak -lkernel_stub_weak WINDOWS_CXX_LINK_SUFFIX += -l:crtend.o -l:crtn.o -# Archive worker has no AppInst (avoid pulling privileged libs into worker). -WINDOWS_CXX_LINK_SUFFIX_ARCHIVE := -lSceNotification -lkernel_web -lSceLibcInternal -WINDOWS_CXX_LINK_SUFFIX_ARCHIVE += -lSceNet -lpthread -lc_stub_weak -lkernel_stub_weak -WINDOWS_CXX_LINK_SUFFIX_ARCHIVE += -l:crtend.o -l:crtn.o define run $(RUN_ENV) && $(1) endef @@ -276,14 +269,12 @@ build/bfpilot-archive/%.o: %.c Makefile $(call run,mkdir -p $(dir $@)) $(call run,$(CC_CMD) $(ARCHIVE_WORKER_CFLAGS) $(ARCHIVE_WORKER_DEFINES) $(ARCHIVE_SEVENZ_DEFINES) $(ARCHIVE_WORKER_INCLUDES) -DBFPILOT_ARCHIVE_INTEGRATED=1 -c $< -o $@) -# AppInst libs for main + optional recovery installer. -BFPILOT_APPINST_LDLIBS := -lSceAppInstUtil -lSceUserService -lSceSystemService - ifeq ($(HOST_IS_WINDOWS),1) -$(BFPILOT_BIN): $(BFPILOT_OBJS) $(if $(filter 1,$(ENABLE_ARCHIVE)),$(BFPILOT_ARCHIVE_OBJS)) $(APP_ASSETS) +$(BFPILOT_BIN): $(BFPILOT_OBJS) $(if $(filter 1,$(ENABLE_ARCHIVE)),$(BFPILOT_ARCHIVE_OBJS)) $(file >build/bfpilot-link.rsp,$(BFPILOT_OBJS) $(BFPILOT_LINK_ARCHIVE_OBJS)) $(call run,$(WINDOWS_CXX_LD_CMD) -o $@ $(WINDOWS_CXX_LINK_PREFIX) @build/bfpilot-link.rsp $(WINDOWS_CXX_LINK_SUFFIX)) $(call run,$(STRIP_CMD) --strip-all $@) + $(call run,$(PYTHON) scripts/scrub_main_payload.py $@) $(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(LAUNCHER_INSTALLER_OBJS) $(WINDOWS_APPINST_SUFFIX)) @@ -291,12 +282,13 @@ $(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) $(ARCHIVE_WORKER_BIN): $(ARCHIVE_WORKER_OBJS) $(file >build/archive-worker-link.rsp,$(ARCHIVE_WORKER_OBJS)) - $(call run,$(WINDOWS_CXX_LD_CMD) -o $@ $(WINDOWS_CXX_LINK_PREFIX) @build/archive-worker-link.rsp $(WINDOWS_CXX_LINK_SUFFIX_ARCHIVE)) + $(call run,$(WINDOWS_CXX_LD_CMD) -o $@ $(WINDOWS_CXX_LINK_PREFIX) @build/archive-worker-link.rsp $(WINDOWS_CXX_LINK_SUFFIX)) $(call run,$(STRIP_CMD) --strip-all $@) else -$(BFPILOT_BIN): $(BFPILOT_OBJS) $(if $(filter 1,$(ENABLE_ARCHIVE)),$(BFPILOT_ARCHIVE_OBJS)) $(APP_ASSETS) - $(call run,$(CXX_CMD) $(COMMON_LDFLAGS) -o $@ $(BFPILOT_OBJS) $(BFPILOT_LINK_ARCHIVE_OBJS) $(ARCHIVE_WORKER_LDFLAGS) $(BFPILOT_APPINST_LDLIBS) $(PRIVILEGED_APPINST_LDLIBS)) +$(BFPILOT_BIN): $(BFPILOT_OBJS) $(if $(filter 1,$(ENABLE_ARCHIVE)),$(BFPILOT_ARCHIVE_OBJS)) + $(call run,$(CXX_CMD) $(COMMON_LDFLAGS) -o $@ $(BFPILOT_OBJS) $(BFPILOT_LINK_ARCHIVE_OBJS) $(ARCHIVE_WORKER_LDFLAGS)) $(call run,$(STRIP_CMD) --strip-all $@) + $(call run,$(PYTHON) scripts/scrub_main_payload.py $@) $(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) $(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(PRIVILEGED_APPINST_LDLIBS)) diff --git a/README.md b/README.md index 1d06d41..7001ea9 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,13 @@ Version **v0.4.1**. Tested on FW **11.60**. | File | What it is | |------|------------| -| `bfpilot.elf` | **Primary** — file manager + archive extract + **Media home tile** install (same single-ELF model as Payload Manager). Inject this. | -| `bfpilot-launcher-installer.elf` | Optional recovery tile installer only. If needed, place the **file** under `/data/BFpilot/` (not a separate folder under `/data`). | +| `bfpilot.elf` | File manager + archive extract (port **5905**). Inject this for the UI. **No AppInst** in this ELF. | +| `bfpilot-launcher-installer.elf` | Home menu **Media** tile (`BFPL00001` → `http://127.0.0.1:5905/`). Inject once, or put the **file** under `/data/BFpilot/` so the main ELF auto-starts it after the UI is up. | | `bfpilot-archive-worker.elf` | Build-only diagnostic. You don't need this for normal use. | -Normal use is just `bfpilot.elf`. On boot it installs/refreshes the **Media** shelf tile (`BFPL00001` → `http://127.0.0.1:5905/`). +**Typical use:** inject `bfpilot.elf`, then inject `bfpilot-launcher-installer.elf` once for the Media shelf tile (or keep a copy at `/data/BFpilot/bfpilot-launcher-installer.elf`). -**Data layout:** everything lives under **`/data/BFpilot`** only (logs, search index, shortcuts, archive jobs). Do not create `/data/bfpilot`, `/data/BFPILOT`, or `/data/bfpilot-launcher-installer` trees. +**Data layout:** everything lives under **`/data/BFpilot`** only (logs, search index, shortcuts, archive jobs). ## Features diff --git a/bfpilot.elf b/bfpilot.elf index dfb2e36..e9cdabc 100755 Binary files a/bfpilot.elf and b/bfpilot.elf differ diff --git a/docs/COMPATIBILITY_STRATEGY.md b/docs/COMPATIBILITY_STRATEGY.md index 41eb4c2..21456a1 100644 --- a/docs/COMPATIBILITY_STRATEGY.md +++ b/docs/COMPATIBILITY_STRATEGY.md @@ -1,40 +1,35 @@ # BFpilot Compatibility Strategy -BFpilot follows the **Payload Manager** model: one primary ELF provides the -web service **and** the PS5 home **Media** tile install. An optional separate -installer remains for recovery only. +BFpilot keeps the file manager/archive runtime and the fragile launcher +installer as **separate payloads**. AppInst in the main ELF breaks some +loaders; the Media tile installer is proven working on its own. ## Payload Roles -### `bfpilot.elf` (primary) +### `bfpilot.elf` Main release payload. Test this first on every firmware and loader. - Starts the HTTP file manager on port `5905`. - Starts the integrated archive daemon for RAR, 7z, split 7z, and ZIP extraction. -- **Installs/refreshes the Media home tile** (`BFPL00001`, category 65536, - deeplink `http://127.0.0.1:5905/`) from embedded `assets-app/param.json` + - `icon0.png` via AppInstUtil (same idea as Payload Manager `app_installer.c`). - Can **load other payloads** by streaming them to elfldr (`127.0.0.1:9021`) - via `/api/fs/launch`. -- Optional **recovery**: if embedded install fails and - `/data/BFpilot/bfpilot-launcher-installer.elf` exists as a **file**, injects - it once via elfldr. -- Writes boot/runtime/crash diagnostics under **`/data/BFpilot` only**. -- Writes integrated archive job/status/logs under - `/data/BFpilot/archive-integrated`. + via `/api/fs/launch` (no AppInst). +- Can **auto-bootstrap the home tile** by injecting + `bfpilot-launcher-installer.elf` once if that ELF is found under + `/data/BFpilot/` (or USB/homebrew paths). Does **not** link AppInstUtil. +- Does not depend on SystemService, UserService, AppInstUtil, or `kernel_sys`. +- Writes boot/runtime/crash diagnostics under `/data/BFpilot` only. -### `bfpilot-launcher-installer.elf` (optional recovery) +### `bfpilot-launcher-installer.elf` -Isolated payload for installing or refreshing `/user/app/BFPL00001` if the -main ELF’s embedded install fails on a particular loader. Place the **file** -at `/data/BFpilot/bfpilot-launcher-installer.elf` — do **not** create a -sibling directory tree under `/data`. +Isolated payload for installing or refreshing `/user/app/BFPL00001` as a +**Media** category home tile (`applicationCategoryType: 65536`). -- Direct-links AppInstUtil + UserService + SystemService + `kernel_sys`. +- Direct-links `kernel_sys`, SystemService, UserService, and AppInstUtil. - Temporarily sets authid `0x4801000000000013`. -- Stages embedded param/icon; Media category `65536`. +- Stages embedded `param.json` and `icon0.png`. +- Registers the tile through AppInst; terminates AppInst when done. - Logs to `/data/BFpilot/launcher-installer.log`. The tile target is: @@ -49,15 +44,6 @@ deeplinkUri: http://127.0.0.1:5905/ Canonical: **`/data/BFpilot`** -Do not introduce: - -- `/data/bfpilot` -- `/data/BFPILOT` -- `/data/bfpilot-launcher-installer` (as a directory root) - -Installer/helper ELFs are files under the canonical dir, not separate trees. - -## Archive worker - -`bfpilot-archive-worker.elf` is a build diagnostic / standalone extractor. -Integrated extract in `bfpilot.elf` is the normal path. +Do not introduce alternate roots (`/data/bfpilot`, `/data/BFPILOT`, or an +installer directory tree). The installer ELF is a **file** under the +canonical dir when used for auto-bootstrap. diff --git a/scripts/inspect_imports.sh b/scripts/inspect_imports.sh index 852c964..05b7d3d 100644 --- a/scripts/inspect_imports.sh +++ b/scripts/inspect_imports.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Compatibility checks for BFpilot ELFs. -# v0.4.1+: main bfpilot.elf embeds Media-tile AppInst (Payload Manager model). +# Main bfpilot.elf must NOT link AppInst (breaks loaders). Tile install is the +# separate bfpilot-launcher-installer.elf only (Media category 65536). set -euo pipefail find_tool() { @@ -59,6 +60,20 @@ needed_libraries() { } +check_no_direct_appinst_import() { + local file="$1" + local needed + needed="$(needed_libraries "${file}" || true)" + if printf '%s\n' "${needed}" | + grep -E 'libSceAppInstUtil\.sprx|libSceSystemService\.sprx|libSceUserService\.sprx|libkernel_sys\.sprx' >/dev/null; then + echo + echo "inspect-imports: unsafe direct launcher import in ${file}" >&2 + printf '%s\n' "${needed}" >&2 + return 1 + fi +} + + for file in "$@"; do echo echo "== ${file} ==" @@ -92,48 +107,47 @@ for file in "$@"; do "${STRINGS}" "${file}" | grep -Ei '(^|[^A-Za-z0-9_])(sce|Sce|AppInst|SystemService|UserService|BFPL00001|app_installer)' || true done -# Primary ELF must include Media-tile AppInst (single-ELF model). -elf_has_string() { - local file="$1" - local needle="$2" - # -a: treat as text on Windows/msys; tolerate null-containing streams - if "${STRINGS}" "${file}" 2>/dev/null | grep -aF "${needle}" >/dev/null 2>&1; then - return 0 +for file in "$@"; do + case "${file}" in + bfpilot-launcher-installer.elf|./bfpilot-launcher-installer.elf) + continue + ;; + esac + if [[ -f "${file}" ]]; then + check_no_direct_appinst_import "${file}" fi - # Fallback: raw bytes (PowerShell/host often more reliable than llvm-strings path) - if command -v python3 >/dev/null 2>&1; then - python3 - "${file}" "${needle}" <<'PY' -import sys -data = open(sys.argv[1], "rb").read() -sys.exit(0 if sys.argv[2].encode() in data else 1) -PY - return $? - fi - return 1 -} +done if [[ -f bfpilot.elf ]]; then - main_needed="$(needed_libraries bfpilot.elf || true)" - for required in libSceAppInstUtil.sprx libSceUserService.sprx; do - if ! printf '%s\n' "${main_needed}" | grep -q "${required}"; then - echo - echo "inspect-imports: bfpilot.elf missing required ${required} (Media tile)" >&2 - printf '%s\n' "${main_needed}" >&2 + if "${STRINGS}" bfpilot.elf 2>/dev/null | + grep -aE 'libSceAppInstUtil\.sprx|sceAppInstUtilInitialize' >"${TMP_DIR}/bfpilot-forbidden-imports.txt" 2>/dev/null; then + echo + echo "inspect-imports: forbidden launcher/AppInst content in bfpilot.elf" >&2 + cat "${TMP_DIR}/bfpilot-forbidden-imports.txt" >&2 + exit 1 + fi + # Scrub may rewrite sprx name; still reject live AppInst symbols + if command -v python3 >/dev/null 2>&1; then + if python3 - <<'PY' +import pathlib +data = pathlib.Path("bfpilot.elf").read_bytes() +# NEEDED-style / live install entry points must not remain +needles = [b"sceAppInstUtilInitialize", b"sceAppInstUtilAppInstallAll"] +for n in needles: + if n in data: + raise SystemExit(1) +raise SystemExit(0) +PY + then + : + else + echo "inspect-imports: forbidden AppInst symbols in bfpilot.elf" >&2 exit 1 fi - done - if ! elf_has_string bfpilot.elf 'BFPL00001'; then - echo "inspect-imports: bfpilot.elf missing BFPL00001 title id" >&2 - exit 1 fi - if ! elf_has_string bfpilot.elf 'applicationCategoryType'; then - echo "inspect-imports: bfpilot.elf missing embedded param applicationCategoryType" >&2 - exit 1 - fi - echo "inspect-imports: bfpilot.elf Media tile AppInst imports OK" + echo "inspect-imports: bfpilot.elf free of AppInst (file manager only) OK" fi -# Optional recovery installer still needs full privileged set. if [[ -f bfpilot-launcher-installer.elf ]]; then installer_needed="$(needed_libraries bfpilot-launcher-installer.elf || true)" for required in libkernel_sys.sprx libSceSystemService.sprx \ @@ -144,16 +158,15 @@ if [[ -f bfpilot-launcher-installer.elf ]]; then exit 1 fi done - echo "inspect-imports: launcher-installer AppInst imports OK" -fi - -# Archive worker must NOT pull AppInst (file manager isolation for worker). -if [[ -f bfpilot-archive-worker.elf ]]; then - worker_needed="$(needed_libraries bfpilot-archive-worker.elf || true)" - if printf '%s\n' "${worker_needed}" | grep -E 'libSceAppInstUtil\.sprx' >/dev/null; then - echo "inspect-imports: archive-worker must not link AppInstUtil" >&2 + if ! "${STRINGS}" bfpilot-launcher-installer.elf 2>/dev/null | grep -aF 'BFPL00001' >/dev/null; then + echo "inspect-imports: installer missing BFPL00001" >&2 exit 1 fi + if ! "${STRINGS}" bfpilot-launcher-installer.elf 2>/dev/null | grep -aF '65536' >/dev/null; then + echo "inspect-imports: installer missing Media category 65536" >&2 + exit 1 + fi + echo "inspect-imports: launcher-installer Media AppInst OK" fi echo diff --git a/scripts/test_v041_ux_tile_host.mjs b/scripts/test_v041_ux_tile_host.mjs index e7f5e11..33239e4 100644 --- a/scripts/test_v041_ux_tile_host.mjs +++ b/scripts/test_v041_ux_tile_host.mjs @@ -1,6 +1,5 @@ /** - * Structural checks for v0.4.1 UX / single data dir / Media tile parity. - * Drives real shipped sources (no mocks of the paths under test). + * Structural checks for v0.4.1: UX, single data dir, separate Media installer. */ import assert from 'assert' import fs from 'fs' @@ -12,36 +11,26 @@ const read = (p) => fs.readFileSync(path.join(root, p), 'utf8') const html = read('assets/files.html') const pathsH = read('src/paths.h') -const appInst = read('src/app_installer.c') const liteMain = read('src/lite_main.c') const param = read('assets-app/param.json') const makefile = read('Makefile') const tileBoot = read('src/tile_bootstrap.c') +const launcher = read('src/launcher_installer_force_main.c') const transfer = read('src/transfer.c') const pathOps = read('src/path_ops.c') const versionH = read('src/version.h') // --- Action bar: wrap, not horizontal-scroll-only --- -assert(html.includes('.actions'), 'actions class present') assert(/\.actions\s*\{[^}]*flex-wrap:\s*wrap/s.test(html), 'actions flex-wrap wrap') -assert(!/\.actions\s*\{[^}]*overflow-x:\s*auto/s.test(html), 'actions must not use overflow-x auto') -assert(/overflow-x:\s*hidden/.test(html), 'actions overflow-x hidden present') -// mobile media query must not force nowrap on .actions +assert(!/\.actions\s*\{[^}]*overflow-x:\s*auto/s.test(html), 'no overflow-x auto on .actions') +assert(/overflow-x:\s*hidden/.test(html), 'overflow-x hidden present') const mediaActions = html.match(/@media[^{]+\{[\s\S]*?\.actions\s*\{([^}]*)\}/) if (mediaActions) { assert(!/flex-wrap:\s*nowrap/.test(mediaActions[1]), 'media .actions must not force nowrap') } // --- Single data root --- -assert(pathsH.includes('#define BFPILOT_DATA_DIR "/data/BFpilot"') || - pathsH.includes('#define BFPILOT_DATA_DIR "/data/BFpilot"') || - /#define\s+BFPILOT_DATA_DIR\s+"\/data\/BFpilot"/.test(pathsH), - 'canonical data dir') -assert(!pathsH.includes('"/data/bfpilot"'), 'no lowercase data root in paths.h') -assert(!pathsH.includes('"/data/BFPILOT"'), 'no UPPERCASE data root in paths.h') -assert(!pathsH.includes('"/data/bfpilot-launcher-installer"'), 'no installer data root') - -// Sources must not mkdir alternate roots (scan C/C++ under src) +assert(/#define\s+BFPILOT_DATA_DIR\s+"\/data\/BFpilot"/.test(pathsH), 'canonical data dir') const srcFiles = [] function walk(d) { for (const ent of fs.readdirSync(d, { withFileTypes: true })) { @@ -59,23 +48,28 @@ for (const f of srcFiles) { `no installer dir root in ${path.relative(root, f)}`) } -// --- Media tile in primary payload (pldmgr parity) --- +// --- Separate launcher model (main must NOT embed AppInst install) --- +assert(!fs.existsSync(path.join(root, 'src/app_installer.c')), 'no app_installer.c in main tree') +assert(!makefile.includes('app_installer.c'), 'app_installer not in Makefile WEB_SRCS') +assert(makefile.includes('BFPILOT_ENABLE_LAUNCHER=0') || makefile.includes('ENABLE_LAUNCHER=0'), + 'launcher disabled in main flags') +assert(makefile.includes('scrub_main_payload.py'), 'scrub main payload for AppInst fingerprints') +assert(liteMain.includes('bfpilot_tile_bootstrap_try'), 'tile bootstrap for separate installer') +assert(liteMain.includes('separate_payload') || liteMain.includes('separate'), + 'runtime separate_payload messaging') +assert(!liteMain.includes('bfpilot_install_app_if_needed'), 'main must not call embedded install') + +// --- Media tile lives in separate installer --- const paramObj = JSON.parse(param) assert.strictEqual(paramObj.applicationCategoryType, 65536, 'Media category 65536') assert.strictEqual(paramObj.titleId, 'BFPL00001') assert.ok(String(paramObj.deeplinkUri).includes('5905')) -assert(appInst.includes('bfpilot_install_app_if_needed'), 'install API') -assert(appInst.includes('INCASSET') || appInst.includes('incbin'), 'embedded assets') -assert(appInst.includes('assets-app/param.json'), 'param embedded') -assert(liteMain.includes('bfpilot_install_app_if_needed'), 'main calls install') -assert(makefile.includes('app_installer.c'), 'app_installer linked in main') -assert(makefile.includes('SceAppInstUtil') || makefile.includes('lSceAppInstUtil'), - 'AppInst linked') -// recovery installer path is a file under data dir only +assert(launcher.includes('BFPILOT_APP_TITLE_ID') || launcher.includes('BFPL00001') || + launcher.includes('bfpilot_param_json'), 'installer embeds app assets') +assert(launcher.includes('sceAppInstUtilInitialize') || launcher.includes('AppInst'), + 'installer uses AppInst') assert(tileBoot.includes('BFPILOT_INSTALLER_ELF_PATH') || tileBoot.includes('/data/BFpilot/bfpilot-launcher-installer.elf')) -assert(!tileBoot.includes('/data/homebrew/bfpilot-launcher-installer.elf'), - 'no loose /data/homebrew installer root path') // --- Prior issue surface (#7-12, #10) --- assert(html.includes('openPath(entryPath(entry))'), '#7') @@ -86,4 +80,4 @@ assert(html.includes('chmodSelected') || html.includes('id="chmod"'), '#10 ui') assert(html.includes('row-checkbox') && html.includes('select-all-checkbox'), '#11') assert(versionH.includes('v0.4.1') && makefile.includes('v0.4.1'), '#12') -console.log('v0.4.1 UX/tile/data-dir host checks passed') +console.log('v0.4.1 separate-launcher + UX host checks passed') diff --git a/src/app_installer.c b/src/app_installer.c deleted file mode 100644 index 59637c8..0000000 --- a/src/app_installer.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * PS5 Media home-tile installer for BFpilot (embedded in main ELF). - * Same pattern as Payload Manager app_installer.c + websrv AppInstUtil. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "app_installer.h" -#include "diag.h" -#include "notify.h" -#include "paths.h" - -#define BFPILOT_APPINST_AUTHID UINT64_C(0x4801000000000013) - -#define INCASSET(name, file) \ - __asm__(".section .rodata\n" \ - ".global " #name "\n" \ - ".global " #name "_end\n" \ - ".global " #name "_size\n" \ - ".align 16\n" #name ":\n" \ - ".incbin \"" file "\"\n" #name "_end:\n" #name "_size:\n" \ - ".quad " #name "_end - " #name "\n" \ - ".previous\n"); \ - extern const uint8_t name[]; \ - extern const size_t name##_size - -INCASSET(bfpilot_param_json, "assets-app/param.json"); -INCASSET(bfpilot_icon0_png, "assets-app/icon0.png"); - -typedef int (*app_install_title_dir_fn)(const char *, const char *, void *); - -int sceUserServiceInitialize(void *); -void sceUserServiceTerminate(void); -int sceAppInstUtilInitialize(void); -int sceAppInstUtilTerminate(void); -int sceAppInstUtilAppInstallAll(void *); - -static int -mkdir_if_needed(const char *path) { - if(mkdir(path, 0755) == 0) return 0; - return errno == EEXIST ? 0 : -errno; -} - -static int -write_file(const char *path, const uint8_t *data, size_t size) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if(fd < 0) return -errno; - - size_t done = 0; - while(done < size) { - ssize_t wr = write(fd, data + done, size - done); - if(wr < 0) { - int err = errno; - close(fd); - return -err; - } - done += (size_t)wr; - } - if(close(fd) != 0) return -errno; - return 0; -} - -static int -needs_update(const char *path, const uint8_t *expected, size_t expected_size) { - struct stat st; - uint8_t *buf; - FILE *f; - int mismatch; - - if(stat(path, &st) != 0) return 1; - if((size_t)st.st_size != expected_size) return 1; - - f = fopen(path, "rb"); - if(!f) return 1; - buf = (uint8_t *)malloc(expected_size); - if(!buf) { - fclose(f); - return 1; - } - if(fread(buf, 1, expected_size, f) != expected_size) { - free(buf); - fclose(f); - return 1; - } - fclose(f); - mismatch = memcmp(buf, expected, expected_size) != 0; - free(buf); - return mismatch; -} - -int -bfpilot_install_app_if_needed(void) { - char app_dir[256]; - char sce_sys_dir[256]; - char param_path[256]; - char icon_path[256]; - struct stat st; - int update_needed = 0; - int final_rc = -1; - pid_t pid = getpid(); - uint64_t original_authid; - int set_authid_rc; - int restore_authid_rc = BFPILOT_DIAG_SKIPPED; - int user_service_rc; - int appinst_init_rc; - int title_dir_rc = BFPILOT_DIAG_SKIPPED; - int install_all_rc = BFPILOT_DIAG_SKIPPED; - uint32_t appinst_handle = 0; - app_install_title_dir_fn install_title_dir = NULL; - - snprintf(app_dir, sizeof(app_dir), BFPILOT_APP_ROOT "/%s", - BFPILOT_APP_TITLE_ID); - snprintf(sce_sys_dir, sizeof(sce_sys_dir), "%s/sce_sys", app_dir); - snprintf(param_path, sizeof(param_path), "%s/param.json", sce_sys_dir); - snprintf(icon_path, sizeof(icon_path), "%s/icon0.png", sce_sys_dir); - - if(stat(app_dir, &st) != 0) { - update_needed = 1; - } else { - if(needs_update(param_path, bfpilot_param_json, bfpilot_param_json_size)) - update_needed = 1; - if(needs_update(icon_path, bfpilot_icon0_png, bfpilot_icon0_png_size)) - update_needed = 1; - } - - if(!update_needed) { - bfpilot_log("app installer: Media tile already up to date title_id=%s", - BFPILOT_APP_TITLE_ID); - bfpilot_diag_set_launcher_status("up_to_date", 0); - return 0; - } - - bfpilot_log("app installer: installing/updating Media tile title_id=%s " - "category=65536 deeplink=%s", - BFPILOT_APP_TITLE_ID, BFPILOT_DEEPLINK_URI); - bfpilot_notify("BFpilot", "Installing Media home tile..."); - - original_authid = kernel_get_ucred_authid(pid); - user_service_rc = sceUserServiceInitialize(NULL); - bfpilot_log("app installer: sceUserServiceInitialize rc=0x%08x", - user_service_rc); - - set_authid_rc = kernel_set_ucred_authid(pid, BFPILOT_APPINST_AUTHID); - bfpilot_log("app installer: set authid rc=0x%08x", set_authid_rc); - if(set_authid_rc != 0) { - bfpilot_diag_set_launcher_status("authid_failed", set_authid_rc); - sceUserServiceTerminate(); - bfpilot_notify("BFpilot", "Media tile: auth setup failed"); - return -1; - } - - appinst_init_rc = sceAppInstUtilInitialize(); - bfpilot_log("app installer: AppInst init rc=0x%08x", appinst_init_rc); - if(appinst_init_rc != 0) { - if(original_authid != 0) - restore_authid_rc = kernel_set_ucred_authid(pid, original_authid); - bfpilot_diag_set_launcher_status("appinst_init_failed", appinst_init_rc); - sceUserServiceTerminate(); - bfpilot_notify("BFpilot", "Media tile: AppInst init failed"); - (void)restore_authid_rc; - return -1; - } - - (void)mkdir_if_needed(app_dir); - (void)mkdir_if_needed(sce_sys_dir); - - if(write_file(param_path, bfpilot_param_json, bfpilot_param_json_size) != 0 || - write_file(icon_path, bfpilot_icon0_png, bfpilot_icon0_png_size) != 0) { - bfpilot_log("app installer: failed writing param/icon under %s", app_dir); - sceAppInstUtilTerminate(); - if(original_authid != 0) - (void)kernel_set_ucred_authid(pid, original_authid); - sceUserServiceTerminate(); - bfpilot_diag_set_launcher_status("write_failed", -1); - return -1; - } - - if(kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx", &appinst_handle) == 0) { - install_title_dir = (app_install_title_dir_fn)kernel_dynlib_resolve( - -1, appinst_handle, "Wudg3Xe3heE"); - } - - if(install_title_dir) { - title_dir_rc = - install_title_dir(BFPILOT_APP_TITLE_ID, BFPILOT_APP_PARENT, NULL); - bfpilot_log("app installer: AppInstallTitleDir rc=0x%08x", title_dir_rc); - } - if(title_dir_rc != 0) { - install_all_rc = sceAppInstUtilAppInstallAll(NULL); - bfpilot_log("app installer: AppInstallAll fallback rc=0x%08x", - install_all_rc); - } - - if(title_dir_rc == 0 || install_all_rc == 0) final_rc = 0; - - if(original_authid != 0) - restore_authid_rc = kernel_set_ucred_authid(pid, original_authid); - (void)restore_authid_rc; - - sceAppInstUtilTerminate(); - sceUserServiceTerminate(); - - if(final_rc == 0) { - bfpilot_diag_set_launcher_status("installed", 0); - bfpilot_notify("BFpilot", "Media home tile ready"); - bfpilot_log("app installer: Media tile installed/refreshed ok"); - return 0; - } - - bfpilot_diag_set_launcher_status("failed", final_rc); - bfpilot_notify("BFpilot", "Media tile install failed (see log)"); - return final_rc; -} diff --git a/src/app_installer.h b/src/app_installer.h deleted file mode 100644 index a4c62fb..0000000 --- a/src/app_installer.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Media-category home tile install (pldmgr-style), callable from main ELF. - */ - -#pragma once - -/* Install or refresh BFPL00001 under /user/app if param/icon changed. - * Returns 0 on success / already up to date, negative or nonzero on failure. - * Non-fatal for the file manager: callers should continue serving the UI. - */ -int bfpilot_install_app_if_needed(void); diff --git a/src/lite_main.c b/src/lite_main.c index 890ba0d..928410d 100644 --- a/src/lite_main.c +++ b/src/lite_main.c @@ -22,7 +22,6 @@ #include #include -#include "app_installer.h" #include "boot_marker.h" #include "diag.h" #include "notify.h" @@ -299,6 +298,9 @@ on_web_ready(unsigned short port, void *arg) { state->notified = 1; } + /* Home Media tile via separate installer ELF + elfldr — AppInst stays out of main. */ + bfpilot_tile_bootstrap_try(); + debug_notify("BFpilot debug", "web server ready"); } @@ -329,16 +331,16 @@ main(int argc, char **argv) { detect_lan_ip(ready.ip, sizeof(ready.ip)); ready.port = runtime_port_requested(argc, argv); websrv_set_runtime_port(ready.port); - websrv_set_runtime_diag(0); + websrv_set_runtime_diag(1); bfpilot_log("runtime web_port=%u", (unsigned int)ready.port); - bfpilot_log("runtime launcher=embedded_media_tile"); + bfpilot_log("runtime launcher=separate_payload"); puts(".----------------------------------------------."); puts("| BFpilot |"); printf("| %-18s browser file manager |\n", VERSION_TAG); puts("'----------------------------------------------'"); puts(""); - puts(" active: file manager + Media home tile (single ELF)"); + puts(" active: standalone web file manager"); printf(" mode: %s\n", BFPILOT_BUILD_MODE); puts(" scope: browse, upload, download, copy, move, delete, rename, mkdir"); #if BFPILOT_ENABLE_INTEGRATED_ARCHIVE @@ -346,7 +348,7 @@ main(int argc, char **argv) { #else puts(" archive: prepare jobs only; inject bfpilot-archive-worker.elf to extract"); #endif - puts(" ps5 app: Media category tile install from this payload (65536)"); + puts(" ps5 app: Media tile via separate bfpilot-launcher-installer.elf"); puts(" payloads: double-click .elf/.bin to load via elfldr :9021"); printf(" data dir: %s (only)\n", BFPILOT_DATA_DIR); printf(" web ui: http://%s:%u/\n", ready.ip, (unsigned int)ready.port); @@ -366,17 +368,10 @@ main(int argc, char **argv) { #endif bfpilot_diag_set_service_rcs(BFPILOT_DIAG_SKIPPED, BFPILOT_DIAG_SKIPPED); + bfpilot_diag_set_launcher_status("separate_payload", BFPILOT_DIAG_SKIPPED); bfpilot_log("sceNetCtlInit rc=skipped"); - - /* Payload Manager model: install Media home tile from the main ELF. */ - { - int install_rc = bfpilot_install_app_if_needed(); - bfpilot_log("embedded Media tile install rc=%d", install_rc); - if(install_rc != 0) { - /* Optional recovery: separate installer under /data/BFpilot/ only. */ - bfpilot_tile_bootstrap_try(); - } - } + bfpilot_log("sceUserServiceInitialize rc=skipped"); + bfpilot_log("launcher skipped: separate payload (inject installer for Media tile)"); bfpilot_checkpoint("handoff check"); debug_notify("BFpilot debug", "handoff check"); diff --git a/src/tile_bootstrap.c b/src/tile_bootstrap.c index bb7a696..8412509 100644 --- a/src/tile_bootstrap.c +++ b/src/tile_bootstrap.c @@ -1,7 +1,7 @@ /* - * Optional recovery: if embedded Media-tile install was skipped/failed and a - * separate installer ELF exists under the single data dir, push it to elfldr. - * Never creates alternate /data roots (bfpilot / BFPILOT / bfpilot-launcher-installer). + * After the web UI is up, optionally inject the separate Media-tile installer + * if present under the single data dir (or common USB/homebrew paths). + * Main bfpilot.elf never links AppInst — that stays in the installer payload. */ #include @@ -16,9 +16,12 @@ #include "payload_launch.h" #include "tile_bootstrap.h" -/* Only the canonical data tree — installer is a file, not a sibling folder. */ static const char *const k_installer_candidates[] = { BFPILOT_INSTALLER_ELF_PATH, + "/data/homebrew/BFpilot/bfpilot-launcher-installer.elf", + "/mnt/usb0/BFpilot/bfpilot-launcher-installer.elf", + "/mnt/usb0/bfpilot-launcher-installer.elf", + "/mnt/usb1/bfpilot-launcher-installer.elf", NULL, }; @@ -48,17 +51,21 @@ bfpilot_tile_bootstrap_try(void) { } if(!found) { - bfpilot_log("tile bootstrap: optional installer ELF not under %s (ok)", + bfpilot_log("tile bootstrap: installer ELF not found under %s " + "(inject bfpilot-launcher-installer.elf separately for Media tile)", BFPILOT_DATA_DIR); return; } - bfpilot_log("tile bootstrap: recovery inject %s", found); + bfpilot_log("tile bootstrap: injecting %s", found); rc = bfpilot_launch_payload_path(found); if(rc == 0) { - bfpilot_notify("BFpilot", "recovery tile installer started"); + bfpilot_notify("BFpilot", "home tile installer started"); bfpilot_log("tile bootstrap: installer sent to elfldr ok"); } else { bfpilot_log("tile bootstrap: inject failed rc=%d", rc); + if(rc == -ECONNREFUSED) { + bfpilot_notify("BFpilot", "tile install skipped (elfldr offline)"); + } } } diff --git a/src/version.h b/src/version.h index b3e237a..1cfa92e 100644 --- a/src/version.h +++ b/src/version.h @@ -17,11 +17,11 @@ #endif #ifndef BFPILOT_ENABLE_LAUNCHER -#define BFPILOT_ENABLE_LAUNCHER 1 +#define BFPILOT_ENABLE_LAUNCHER 0 #endif #ifndef BFPILOT_DISABLE_LAUNCHER -#define BFPILOT_DISABLE_LAUNCHER 0 +#define BFPILOT_DISABLE_LAUNCHER 1 #endif #if BFPILOT_DISABLE_LAUNCHER