diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd71da..829ba32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,16 @@ ## v0.4.1 -* Home tile installs as **Media** category (`applicationCategoryType` 65536), same idea as Payload Manager -* After the web UI is up, auto-injects `bfpilot-launcher-installer.elf` if found under `/data/BFpilot/` (or common USB/homebrew paths) so the PS5 tile installs without merging AppInst into the main ELF -* **Load payload**: double-click `.elf`/`.bin`/`.js` or use the toolbar button — streams the file to elfldr on `127.0.0.1:9021` +* **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) +* 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=...` -* Launcher installer calls `sceAppInstUtilTerminate` when done -* **chmod** UI + `GET /api/fs/chmod?path=&mode=` (presets 444/644/755/777) for offline appmeta icon use-case (#10) -* Single-click folder name opens directory (#7); checkboxes still multi-select (#11) -* Case-insensitive path resolve on list/Go when exact path misses (#8) -* Extract still covers zip/rar/7z including `.7z.001` (#9) +* **chmod** UI + `GET /api/fs/chmod?path=&mode=` (presets 444/644/755/777) (#10) +* Single-click folder name opens directory (#7); checkboxes multi-select (#11) +* Case-insensitive path resolve on list/Go (#8) +* Extract zip/rar/7z including `.7z.001` (#9) * Version strings locked to **v0.4.1** (#12) ## v0.4.0 Stable (2026-07-14) diff --git a/Makefile b/Makefile index c5d6430..deb7835 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ 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 @@ -142,8 +143,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 -BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0 -BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1 +# Embedded Media-tile AppInst in main ELF (Payload Manager model). +BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=1 +BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=0 BFPILOT_CFLAGS += -DBFPILOT_ENABLE_INTEGRATED_ARCHIVE=$(ENABLE_ARCHIVE) ifeq ($(ENABLE_ARCHIVE),1) @@ -163,7 +165,7 @@ ARCHIVE_WORKER_DEFINES := -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_UNIX ARCHIVE_WORKER_DEFINES += -DPS5_PAYLOAD -DSILENT -DRAR_SMP -DNDEBUG ARCHIVE_SEVENZ_DEFINES := -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_UNIX ARCHIVE_SEVENZ_DEFINES += -DZ7_EXTRACT_ONLY -DZ7_PROG_VARIANT_R -DZ7_AFFINITY_DISABLE -ARCHIVE_WORKER_INCLUDES := -I$(UNRAR_SRC_DIR) -I$(MINIZ_DIR) +ARCHIVE_WORKER_INCLUDES := -Isrc -I$(UNRAR_SRC_DIR) -I$(MINIZ_DIR) ARCHIVE_WORKER_INCLUDES += -I$(LZMA_C_DIR) -I$(LZMA_CPP_DIR) -I$(SEVENZ_DIR) ARCHIVE_WORKER_CXXFLAGS := -O3 -std=c++11 -Wall -fexceptions ARCHIVE_WORKER_CXXFLAGS += -Wno-logical-op-parentheses -Wno-switch @@ -217,9 +219,16 @@ 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 -WINDOWS_CXX_LINK_SUFFIX := -lSceNotification -lkernel_web -lSceLibcInternal -WINDOWS_CXX_LINK_SUFFIX += -lSceNet -lpthread -lc_stub_weak -lkernel_stub_weak +# 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 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 @@ -267,12 +276,14 @@ 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)) +$(BFPILOT_BIN): $(BFPILOT_OBJS) $(if $(filter 1,$(ENABLE_ARCHIVE)),$(BFPILOT_ARCHIVE_OBJS)) $(APP_ASSETS) $(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)) @@ -280,13 +291,12 @@ $(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)) + $(call run,$(WINDOWS_CXX_LD_CMD) -o $@ $(WINDOWS_CXX_LINK_PREFIX) @build/archive-worker-link.rsp $(WINDOWS_CXX_LINK_SUFFIX_ARCHIVE)) $(call run,$(STRIP_CMD) --strip-all $@) else -$(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)) +$(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)) $(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 6dcc0e1..1d06d41 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PS5 payload that runs a browser file manager on port **5905**: http://:5905/ ``` -Works from the PS5 browser or any PC/phone on the same network. No extra app after you inject it. +Works from the PS5 browser or any PC/phone on the same network. Version **v0.4.1**. Tested on FW **11.60**. @@ -14,11 +14,13 @@ Version **v0.4.1**. Tested on FW **11.60**. | File | What it is | |------|------------| -| `bfpilot.elf` | File manager + archive extract. Inject this first. | -| `bfpilot-launcher-installer.elf` | Home menu **Media** tile (`BFPL00001` → `http://127.0.0.1:5905/`). Put a copy under `/data/BFpilot/` so the main ELF can auto-run it once the UI is up. | +| `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-archive-worker.elf` | Build-only diagnostic. You don't need this for normal use. | -Releases ship the first two. Keep the installer next to the main payload (or under `/data/BFpilot/`) if you want the tile installed automatically. +Normal use is just `bfpilot.elf`. On boot it installs/refreshes the **Media** shelf tile (`BFPL00001` → `http://127.0.0.1:5905/`). + +**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. ## Features diff --git a/assets/files.html b/assets/files.html index 78b8fde..1bc9e75 100644 --- a/assets/files.html +++ b/assets/files.html @@ -315,20 +315,32 @@ .actions { display: flex; - gap: 8px; - overflow-x: auto; - padding: 9px 10px; + flex-wrap: wrap; + align-items: center; + gap: 6px; + overflow-x: hidden; + overflow-y: visible; + padding: 8px 10px; border-bottom: 1px solid var(--line); } + .actions button { + min-height: 32px; + padding: 0 9px; + font-size: 13px; + flex: 0 0 auto; + } + .count { margin-left: auto; - min-height: 34px; + min-height: 32px; display: inline-flex; align-items: center; color: var(--muted); font-size: 12px; white-space: nowrap; + flex: 1 1 auto; + justify-content: flex-end; } .file-head, @@ -697,7 +709,14 @@ } .actions { - flex-wrap: nowrap; + flex-wrap: wrap; + gap: 5px; + } + + .actions button { + min-height: 30px; + padding: 0 8px; + font-size: 12px; } .file-head, diff --git a/bfpilot-launcher-installer.elf b/bfpilot-launcher-installer.elf index 9bae584..84df8ea 100755 Binary files a/bfpilot-launcher-installer.elf and b/bfpilot-launcher-installer.elf differ diff --git a/bfpilot.elf b/bfpilot.elf index e4b1fea..dfb2e36 100755 Binary files a/bfpilot.elf and b/bfpilot.elf differ diff --git a/docs/COMPATIBILITY_STRATEGY.md b/docs/COMPATIBILITY_STRATEGY.md index 1dad688..41eb4c2 100644 --- a/docs/COMPATIBILITY_STRATEGY.md +++ b/docs/COMPATIBILITY_STRATEGY.md @@ -1,155 +1,63 @@ # BFpilot Compatibility Strategy -BFpilot keeps the file manager/archive runtime and the fragile launcher -installer as separate payloads. +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. ## Payload Roles -### `bfpilot.elf` +### `bfpilot.elf` (primary) 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` (no AppInst). -- Can **auto-bootstrap the home tile** by injecting - `bfpilot-launcher-installer.elf` once if that ELF is found under - `/data/BFpilot/` (or a few USB/homebrew paths). Still does **not** link - AppInstUtil itself. -- Does not include integrated launcher installer source in the main link. -- Does not link AppInstUtil. -- Does not depend on SystemService, UserService, AppInstUtil, or `kernel_sys`. -- Writes boot/runtime/crash diagnostics under `/data/BFpilot`. -- Writes integrated archive job, status, lock, and extraction logs under + 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`. -Broad firmware support comes from avoiding imports that can fail before -`main()` on some loader/firmware combinations. +### `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. Prefer putting a copy at -`/data/BFpilot/bfpilot-launcher-installer.elf` so `bfpilot.elf` can start it -automatically after the web UI is ready (or inject it yourself once). - -- Direct-links the working websrv-style installer dependency set: - `kernel_sys`, SystemService, UserService, and AppInstUtil. -- Initializes UserService. +- Direct-links AppInstUtil + UserService + SystemService + `kernel_sys`. - Temporarily sets authid `0x4801000000000013`. -- Validates/stages embedded `param.json` and `icon0.png`. -- `param.json` uses `applicationCategoryType: 65536` (Media tab). -- Registers the tile through AppInst; terminates AppInst when done. -- Restores the original authid before exit. +- Stages embedded param/icon; Media category `65536`. - Logs to `/data/BFpilot/launcher-installer.log`. The tile target is: ```text -http://127.0.0.1:5905/ +titleId: BFPL00001 +applicationCategoryType: 65536 (Media) +deeplinkUri: http://127.0.0.1:5905/ ``` -### Archive Runtime +### Data directory (single root) -Archive extraction is handled by the integrated daemon inside `bfpilot.elf`. -`bfpilot-archive-worker.elf` is a fallback diagnostic build of the same archive -engine, not the normal user flow. +Canonical: **`/data/BFpilot`** -- Reads `/data/BFpilot/archive-integrated/job.ini` in the integrated main ELF. -- Writes `/data/BFpilot/archive-integrated/status.json` in the integrated main - ELF. -- Logs to `/data/BFpilot/archive-integrated/archive-worker.log` in the - integrated main ELF. -- Extracts only to allowed roots: `/data`, `/mnt/usb0..7`, and - `/mnt/ext0..7`. -- Uses a BFpilot-owned staging directory and renames it into place only after a - successful extraction. -- Does not link AppInstUtil or launcher installer libraries. -- Uses `/data/BFpilot/archive-integrated/daemon.lock` so repeated payload - injections do not create competing archive daemons. -- Records the daemon PID in `daemon.lock` and exits when its parent file - manager process is replaced. -- The standalone diagnostic worker keeps the legacy - `/data/BFpilot/archive/*` paths so it cannot race integrated jobs. +Do not introduce: -## Why The Split Exists +- `/data/bfpilot` +- `/data/BFPILOT` +- `/data/bfpilot-launcher-installer` (as a directory root) -Working PS5 launcher projects such as ps5 websrv, Payload Manager, and -elf-arsenal use the complete AppInst service/privilege composition. Partial -approaches are unreliable: +Installer/helper ELFs are files under the canonical dir, not separate trees. -- AppInst-only direct imports can be rejected before `main()`. -- Runtime AppInst lookup can return unavailable on some environments. -- Loading AppInst without the matching service/authid context can fail before a - useful install result is produced. +## Archive worker -BFpilot keeps launcher risk out of the file manager. Archive extraction is -inside `bfpilot.elf` so users do not need a second injection, while the -launcher installer remains isolated because those imports are the highest -compatibility risk. - -## Boot Marker - -Both release ELFs call: - -```c -bfpilot_boot_marker(payload_name, build_mode); -``` - -early in `main()`. - -The marker: - -- prints `BFpilot BOOT: entered main` -- creates `/data/BFpilot` if possible -- appends payload name, build mode, PID, and build version to - `/data/BFpilot/boot.log` -- attempts a notification titled `BFpilot BOOT` -- continues if notification fails - -No notification and no boot-log entry usually means the loader rejected the ELF -or execution failed before `main()`. A boot marker without a working server or -installer result means the failure happened after `main()`. - -## Reload Behavior - -`bfpilot.elf` startup is explicit: - -1. Boot marker runs. -2. BFpilot checks whether the selected port already has an old BFpilot server. -3. If an old server is found, BFpilot asks it to shut down through - `/api/control/shutdown`. -4. If shutdown succeeds, the new server starts. -5. If shutdown fails or a file job is busy, BFpilot logs the reason and exits - cleanly. - -The default web port is `5905`. A runtime alternate port can be passed as: - -```text ---port 5906 -``` - -## Import Inspection - -Run: - -```sh -make inspect-imports -``` - -The target builds and checks only: - -- `bfpilot.elf` -- `bfpilot-launcher-installer.elf` -- `bfpilot-archive-worker.elf` - -It fails if `bfpilot.elf` contains launcher/AppInstUtil fingerprints or direct -launcher imports. It also fails if the installer is missing any required direct -dependency: `libkernel_sys.sprx`, `libSceSystemService.sprx`, -`libSceUserService.sprx`, or `libSceAppInstUtil.sprx`. - -The supported release build contains only the file manager payload and the -isolated launcher installer for normal use. The archive worker build remains -available for diagnostics and comparison testing. +`bfpilot-archive-worker.elf` is a build diagnostic / standalone extractor. +Integrated extract in `bfpilot.elf` is the normal path. diff --git a/scripts/inspect_imports.sh b/scripts/inspect_imports.sh index c163f2a..852c964 100644 --- a/scripts/inspect_imports.sh +++ b/scripts/inspect_imports.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Compatibility checks for BFpilot ELFs. +# v0.4.1+: main bfpilot.elf embeds Media-tile AppInst (Payload Manager model). set -euo pipefail find_tool() { @@ -57,20 +59,6 @@ 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} ==" @@ -104,27 +92,48 @@ for file in "$@"; do "${STRINGS}" "${file}" | grep -Ei '(^|[^A-Za-z0-9_])(sce|Sce|AppInst|SystemService|UserService|BFPL00001|app_installer)' || true done -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}" +# 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 fi -done + # 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 +} if [[ -f bfpilot.elf ]]; then - if "${STRINGS}" bfpilot.elf | - grep -E 'SceAppInstUtil|sceAppInst|app_installer|BFPL00001' >"${TMP_DIR}/bfpilot-forbidden-imports.txt"; then - echo - echo "inspect-imports: forbidden launcher/AppInst content in bfpilot.elf" >&2 - cat "${TMP_DIR}/bfpilot-forbidden-imports.txt" >&2 + 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 + 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" 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 \ @@ -135,6 +144,16 @@ 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 + exit 1 + fi fi echo diff --git a/scripts/test_v041_ux_tile_host.mjs b/scripts/test_v041_ux_tile_host.mjs new file mode 100644 index 0000000..e7f5e11 --- /dev/null +++ b/scripts/test_v041_ux_tile_host.mjs @@ -0,0 +1,89 @@ +/** + * Structural checks for v0.4.1 UX / single data dir / Media tile parity. + * Drives real shipped sources (no mocks of the paths under test). + */ +import assert from 'assert' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..') +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 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 +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) +const srcFiles = [] +function walk(d) { + for (const ent of fs.readdirSync(d, { withFileTypes: true })) { + const p = path.join(d, ent.name) + if (ent.isDirectory()) walk(p) + else if (/\.(c|h|cpp)$/.test(ent.name)) srcFiles.push(p) + } +} +walk(path.join(root, 'src')) +for (const f of srcFiles) { + const t = fs.readFileSync(f, 'utf8') + assert(!t.includes('"/data/bfpilot"'), `no /data/bfpilot in ${path.relative(root, f)}`) + assert(!t.includes('"/data/BFPILOT"'), `no /data/BFPILOT in ${path.relative(root, f)}`) + assert(!t.includes('"/data/bfpilot-launcher-installer"'), + `no installer dir root in ${path.relative(root, f)}`) +} + +// --- Media tile in primary payload (pldmgr parity) --- +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(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') +assert(pathOps.includes('bfpilot_path_resolve_ci'), '#8') +assert(html.includes("endsWith('.7z')"), '#9') +assert(transfer.includes('chmod_request') || transfer.includes('/api/fs/chmod'), '#10') +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') diff --git a/src/app_installer.c b/src/app_installer.c new file mode 100644 index 0000000..59637c8 --- /dev/null +++ b/src/app_installer.c @@ -0,0 +1,223 @@ +/* + * 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 new file mode 100644 index 0000000..a4c62fb --- /dev/null +++ b/src/app_installer.h @@ -0,0 +1,11 @@ +/* + * 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/archive_worker.cpp b/src/archive_worker.cpp index ce2fc14..26b83ce 100644 --- a/src/archive_worker.cpp +++ b/src/archive_worker.cpp @@ -44,10 +44,11 @@ extern "C" int posix_fallocate(int fd, off_t offset, off_t len); #define BFPILOT_ARCHIVE_INTEGRATED 0 #endif +#include "paths.h" #if BFPILOT_ARCHIVE_INTEGRATED -#define BFPILOT_ARCHIVE_DIR "/data/BFpilot/archive-integrated" +#define BFPILOT_ARCHIVE_DIR BFPILOT_ARCHIVE_INTEGRATED_DIR #else -#define BFPILOT_ARCHIVE_DIR "/data/BFpilot/archive" +#define BFPILOT_ARCHIVE_DIR BFPILOT_ARCHIVE_STANDALONE_DIR #endif #define BFPILOT_ARCHIVE_JOB BFPILOT_ARCHIVE_DIR "/job.ini" #define BFPILOT_ARCHIVE_STATUS BFPILOT_ARCHIVE_DIR "/status.json" diff --git a/src/boot_marker.c b/src/boot_marker.c index da4cf32..7981184 100644 --- a/src/boot_marker.c +++ b/src/boot_marker.c @@ -13,6 +13,7 @@ #include #include "notify.h" +#include "paths.h" #include "version.h" #ifndef VERSION_TAG @@ -27,10 +28,6 @@ #define BFPILOT_BOOT_NOTIFY 1 #endif -#define BFPILOT_DATA_ROOT "/data" -#define BFPILOT_DATA_DIR "/data/BFpilot" -#define BFPILOT_BOOT_LOG_PATH "/data/BFpilot/boot.log" - static void mkdir_ignore(const char *path) { diff --git a/src/diag.c b/src/diag.c index f5d387a..60ebc30 100644 --- a/src/diag.c +++ b/src/diag.c @@ -19,11 +19,8 @@ #include #include -#define BFPILOT_DATA_ROOT "/data" -#define BFPILOT_DATA_DIR "/data/BFpilot" -#define BFPILOT_HOMEBREW_DIR "/data/homebrew" -#define BFPILOT_LOG_PATH "/data/BFpilot/log.txt" -#define BFPILOT_CRASH_LOG_PATH "/data/BFpilot/crash.log" +#include "paths.h" + #define BFPILOT_EXT0_ROOT "/mnt/ext0" #define BFPILOT_EXT0_HOMEBREW "/mnt/ext0/homebrew" diff --git a/src/launcher_installer_force_main.c b/src/launcher_installer_force_main.c index 5173aac..eab36b4 100644 --- a/src/launcher_installer_force_main.c +++ b/src/launcher_installer_force_main.c @@ -24,12 +24,8 @@ #include "notify.h" #include "version.h" -#define BFPILOT_APP_TITLE_ID "BFPL00001" -#define BFPILOT_APP_ROOT "/user/app" -#define BFPILOT_APP_PARENT BFPILOT_APP_ROOT "/" -#define BFPILOT_DATA_ROOT "/data" -#define BFPILOT_DATA_DIR "/data/BFpilot" -#define BFPILOT_LAUNCHER_LOG "/data/BFpilot/launcher-installer.log" +#include "paths.h" + #define BFPILOT_DIAG_SKIPPED (-2147483000) #define BFPILOT_APPINST_AUTHID UINT64_C(0x4801000000000013) diff --git a/src/lite_main.c b/src/lite_main.c index 1a350fb..890ba0d 100644 --- a/src/lite_main.c +++ b/src/lite_main.c @@ -22,9 +22,11 @@ #include #include +#include "app_installer.h" #include "boot_marker.h" #include "diag.h" #include "notify.h" +#include "paths.h" #include "search.h" #include "tile_bootstrap.h" #include "transfer.h" @@ -297,9 +299,6 @@ on_web_ready(unsigned short port, void *arg) { state->notified = 1; } - /* Home tile (Media) via separate installer ELF + elfldr — keeps AppInst out of main. */ - bfpilot_tile_bootstrap_try(); - debug_notify("BFpilot debug", "web server ready"); } @@ -322,24 +321,24 @@ main(int argc, char **argv) { "disable_launcher=%d", BFPILOT_SDK_PATH, BFPILOT_ENABLE_LAUNCHER, BFPILOT_DISABLE_LAUNCHER); - bfpilot_log("diagnostic paths log=/data/BFpilot/log.txt crash=/data/BFpilot/" - "crash.log"); + bfpilot_log("diagnostic paths log=%s crash=%s data_dir=%s", + BFPILOT_LOG_PATH, BFPILOT_CRASH_LOG_PATH, BFPILOT_DATA_DIR); ready_state_t ready; memset(&ready, 0, sizeof(ready)); 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(1); + websrv_set_runtime_diag(0); bfpilot_log("runtime web_port=%u", (unsigned int)ready.port); - bfpilot_log("runtime launcher=separate_payload"); + bfpilot_log("runtime launcher=embedded_media_tile"); puts(".----------------------------------------------."); puts("| BFpilot |"); printf("| %-18s browser file manager |\n", VERSION_TAG); puts("'----------------------------------------------'"); puts(""); - puts(" active: standalone web file manager"); + puts(" active: file manager + Media home tile (single ELF)"); printf(" mode: %s\n", BFPILOT_BUILD_MODE); puts(" scope: browse, upload, download, copy, move, delete, rename, mkdir"); #if BFPILOT_ENABLE_INTEGRATED_ARCHIVE @@ -347,8 +346,9 @@ main(int argc, char **argv) { #else puts(" archive: prepare jobs only; inject bfpilot-archive-worker.elf to extract"); #endif - puts(" ps5 app: Media tile auto-install if launcher ELF is present"); + puts(" ps5 app: Media category tile install from this payload (65536)"); 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); puts(" inject/deploy port: 9021"); puts(""); @@ -366,10 +366,17 @@ 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"); - bfpilot_log("sceUserServiceInitialize rc=skipped"); - bfpilot_log("launcher skipped: separate payload"); + + /* 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_checkpoint("handoff check"); debug_notify("BFpilot debug", "handoff check"); diff --git a/src/paths.h b/src/paths.h new file mode 100644 index 0000000..90e00ff --- /dev/null +++ b/src/paths.h @@ -0,0 +1,32 @@ +/* + * Canonical on-console data layout for BFpilot. + * Single root: /data/BFpilot — do not introduce alternate casings or + * sibling trees (/data/bfpilot, /data/BFPILOT, /data/bfpilot-launcher-installer). + */ + +#pragma once + +#define BFPILOT_DATA_ROOT "/data" +#define BFPILOT_DATA_DIR "/data/BFpilot" +#define BFPILOT_HOMEBREW_DIR "/data/homebrew" + +#define BFPILOT_LOG_PATH BFPILOT_DATA_DIR "/log.txt" +#define BFPILOT_CRASH_LOG_PATH BFPILOT_DATA_DIR "/crash.log" +#define BFPILOT_BOOT_LOG_PATH BFPILOT_DATA_DIR "/boot.log" +#define BFPILOT_SHORTCUTS_PATH BFPILOT_DATA_DIR "/shortcuts.txt" +#define BFPILOT_SHORTCUTS_TMP BFPILOT_DATA_DIR "/shortcuts.tmp" +#define BFPILOT_SEARCH_IDX BFPILOT_DATA_DIR "/search.idx" +#define BFPILOT_SEARCH_IDX_TMP BFPILOT_DATA_DIR "/search.idx.tmp" +#define BFPILOT_SEARCH_CRAWL_LOG BFPILOT_DATA_DIR "/search_crawl.log" +#define BFPILOT_LAUNCHER_LOG BFPILOT_DATA_DIR "/launcher-installer.log" +#define BFPILOT_INSTALLER_ELF_PATH BFPILOT_DATA_DIR "/bfpilot-launcher-installer.elf" + +/* Integrated archive jobs live under the same data root. */ +#define BFPILOT_ARCHIVE_INTEGRATED_DIR BFPILOT_DATA_DIR "/archive-integrated" +#define BFPILOT_ARCHIVE_STANDALONE_DIR BFPILOT_DATA_DIR "/archive" + +/* PS5 home Media tile (param.json applicationCategoryType 65536). */ +#define BFPILOT_APP_TITLE_ID "BFPL00001" +#define BFPILOT_APP_ROOT "/user/app" +#define BFPILOT_APP_PARENT BFPILOT_APP_ROOT "/" +#define BFPILOT_DEEPLINK_URI "http://127.0.0.1:5905/" diff --git a/src/search.c b/src/search.c index 28e9888..ba4228f 100644 --- a/src/search.c +++ b/src/search.c @@ -22,6 +22,7 @@ #include #include "diag.h" +#include "paths.h" #include "websrv.h" #define BFPILOT_SEARCH_ALL_ROOTS_LABEL "all" @@ -154,7 +155,7 @@ search_diag_log(const char *fmt, ...) { line[n++] = '\n'; line[n] = 0; } - FILE *file = fopen("/data/BFpilot/search_crawl.log", "a"); + FILE *file = fopen(BFPILOT_SEARCH_CRAWL_LOG, "a"); if(file) { fwrite(line, 1, (size_t)n, file); fclose(file); @@ -642,7 +643,7 @@ typedef struct { static void search_save_index(search_index_t *idx) { if(!idx || idx->count == 0) return; - FILE *f = fopen("/data/BFpilot/search.idx.tmp", "wb"); + FILE *f = fopen(BFPILOT_SEARCH_IDX_TMP, "wb"); if(!f) return; uint64_t string_block_size = 0; @@ -701,15 +702,15 @@ search_save_index(search_index_t *idx) { fclose(f); if(ok) { - rename("/data/BFpilot/search.idx.tmp", "/data/BFpilot/search.idx"); + rename(BFPILOT_SEARCH_IDX_TMP, BFPILOT_SEARCH_IDX); } else { - unlink("/data/BFpilot/search.idx.tmp"); + unlink(BFPILOT_SEARCH_IDX_TMP); } } static search_index_t * search_load_index(void) { - FILE *f = fopen("/data/BFpilot/search.idx", "rb"); + FILE *f = fopen(BFPILOT_SEARCH_IDX, "rb"); if(!f) return NULL; idx_header_t hdr; diff --git a/src/tile_bootstrap.c b/src/tile_bootstrap.c index 5924a4b..bb7a696 100644 --- a/src/tile_bootstrap.c +++ b/src/tile_bootstrap.c @@ -1,5 +1,7 @@ /* - * Find bfpilot-launcher-installer.elf and push it to elfldr once. + * 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). */ #include @@ -10,17 +12,13 @@ #include "diag.h" #include "notify.h" +#include "paths.h" #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[] = { - "/data/BFpilot/bfpilot-launcher-installer.elf", - "/data/homebrew/BFpilot/bfpilot-launcher-installer.elf", - "/data/homebrew/bfpilot-launcher-installer.elf", - "/mnt/usb0/bfpilot-launcher-installer.elf", - "/mnt/usb0/BFpilot/bfpilot-launcher-installer.elf", - "/mnt/usb1/bfpilot-launcher-installer.elf", - "/user/data/BFpilot/bfpilot-launcher-installer.elf", + BFPILOT_INSTALLER_ELF_PATH, NULL, }; @@ -50,19 +48,17 @@ bfpilot_tile_bootstrap_try(void) { } if(!found) { - bfpilot_log("tile bootstrap: installer ELF not found (skipped)"); + bfpilot_log("tile bootstrap: optional installer ELF not under %s (ok)", + BFPILOT_DATA_DIR); return; } - bfpilot_log("tile bootstrap: injecting %s", found); + bfpilot_log("tile bootstrap: recovery inject %s", found); rc = bfpilot_launch_payload_path(found); if(rc == 0) { - bfpilot_notify("BFpilot", "home tile installer started"); + bfpilot_notify("BFpilot", "recovery 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/transfer.c b/src/transfer.c index 09fed90..f79431b 100644 --- a/src/transfer.c +++ b/src/transfer.c @@ -23,6 +23,7 @@ #include "diag.h" #include "path_ops.h" +#include "paths.h" #include "payload_launch.h" #include "search.h" #include "transfer.h" @@ -47,10 +48,7 @@ #define COPY_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE #define UPLOAD_BUF_SIZE BFPILOT_UPLOAD_BUF_SIZE #define BFPILOT_SPACE_MARGIN_BYTES (256ULL * 1024ULL * 1024ULL) -#define BFPILOT_DATA_ROOT "/data" -#define BFPILOT_DATA_DIR "/data/BFpilot" -#define BFPILOT_SHORTCUTS_PATH "/data/BFpilot/shortcuts.txt" -#define BFPILOT_SHORTCUTS_TMP "/data/BFpilot/shortcuts.tmp" + #define BFPILOT_MAX_SHORTCUTS 32 #ifndef BFPILOT_ENABLE_INTEGRATED_ARCHIVE @@ -58,9 +56,9 @@ #endif #if BFPILOT_ENABLE_INTEGRATED_ARCHIVE -#define BFPILOT_ARCHIVE_DIR "/data/BFpilot/archive-integrated" +#define BFPILOT_ARCHIVE_DIR BFPILOT_ARCHIVE_INTEGRATED_DIR #else -#define BFPILOT_ARCHIVE_DIR "/data/BFpilot/archive" +#define BFPILOT_ARCHIVE_DIR BFPILOT_ARCHIVE_STANDALONE_DIR #endif #define BFPILOT_ARCHIVE_JOB BFPILOT_ARCHIVE_DIR "/job.ini" #define BFPILOT_ARCHIVE_JOB_TMP BFPILOT_ARCHIVE_DIR "/job.tmp" diff --git a/src/version.h b/src/version.h index 1cfa92e..b3e237a 100644 --- a/src/version.h +++ b/src/version.h @@ -17,11 +17,11 @@ #endif #ifndef BFPILOT_ENABLE_LAUNCHER -#define BFPILOT_ENABLE_LAUNCHER 0 +#define BFPILOT_ENABLE_LAUNCHER 1 #endif #ifndef BFPILOT_DISABLE_LAUNCHER -#define BFPILOT_DISABLE_LAUNCHER 1 +#define BFPILOT_DISABLE_LAUNCHER 0 #endif #if BFPILOT_DISABLE_LAUNCHER