v0.4.1: restore separate launcher; main ELF without AppInst.

All-in-one AppInst in bfpilot.elf broke loaders. Revert to proven model:
file manager only in main, Media tile via bfpilot-launcher-installer.elf.
Keep action-bar wrap, single /data/BFpilot, and issue #7-#12 fixes.
This commit is contained in:
ItsBlurf committed 2026-07-17 20:23:52 +01:00
1 parent dd7e1d4d1f
commit 00028d2dcf
12 files changed
+142 -389

No files matched your search

+3 -3
View File
@@ -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=...`
+12 -20
View File
@@ -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))
+4 -4
View File
@@ -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
BIN
View File
Binary file not shown.
+19 -33
View File
@@ -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.
+56 -43
View File
@@ -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
+22 -28
View File
@@ -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')
-223
View File
@@ -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 <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ps5/kernel.h>
#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;
}
-11
View File
@@ -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);
+10 -15
View File
@@ -22,7 +22,6 @@
#include <net/if.h>
#include <netinet/in.h>
#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");
+14 -7
View File
@@ -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 <errno.h>
@@ -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)");
}
}
}
+2 -2
View File
@@ -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