diff --git a/.gitignore b/.gitignore index bbb38aa..2665037 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ gen/ *.a *.so *.elf +__pycache__/ +diagnostics/*.json # Local environment / editor noise .env diff --git a/Makefile b/Makefile index 663f5ee..9d2575f 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,12 @@ SHELL := bash ifeq ($(strip $(PS5_PAYLOAD_SDK)),) +ifneq ($(filter ps5-diag,$(MAKECMDGOALS)),ps5-diag) $(error PS5_PAYLOAD_SDK is required, e.g. export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk) endif - +else include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk +endif HOST_UNAME := $(shell uname -s 2>/dev/null || echo unknown) HOST_IS_WINDOWS := 0 @@ -39,6 +41,8 @@ HELLO_NOTIFY_BIN := tests/hello_notify.elf INSTALLER_ENTER_PROBE_BIN := tests/installer_enter_probe.elf INSTALLER_LINKONLY_APPINST_BIN := tests/installer_linkonly_appinst.elf INSTALLER_RUNTIME_RESOLVE_APPINST_BIN := tests/installer_runtime_resolve_appinst.elf +INSTALLER_SYSMODULE_APPINST_BIN := tests/installer_sysmodule_appinst.elf +INSTALLER_WEBSRV_PATTERN_BIN := tests/installer_websrv_pattern.elf WEB_SRCS := src/lite_main.c WEB_SRCS += src/boot_marker.c @@ -75,6 +79,12 @@ INSTALLER_LINKONLY_APPINST_SRCS += src/notify.c INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS := tests/installer_runtime_resolve_appinst.c INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS += src/boot_marker.c INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS += src/notify.c +INSTALLER_SYSMODULE_APPINST_SRCS := tests/installer_sysmodule_appinst.c +INSTALLER_SYSMODULE_APPINST_SRCS += src/boot_marker.c +INSTALLER_SYSMODULE_APPINST_SRCS += src/notify.c +INSTALLER_WEBSRV_PATTERN_SRCS := tests/installer_websrv_pattern.c +INSTALLER_WEBSRV_PATTERN_SRCS += src/boot_marker.c +INSTALLER_WEBSRV_PATTERN_SRCS += src/notify.c ASSETS := $(wildcard assets/*) GEN_SRCS := $(patsubst assets/%,gen/assets/%,$(ASSETS:=.c)) @@ -91,6 +101,8 @@ HELLO_NOTIFY_OBJS := $(patsubst %.c,build/hello-notify/%.o,$(HELLO_NOTIFY_SRCS)) INSTALLER_ENTER_PROBE_OBJS := $(patsubst %.c,build/installer-enter-probe/%.o,$(INSTALLER_ENTER_PROBE_SRCS)) INSTALLER_LINKONLY_APPINST_OBJS := $(patsubst %.c,build/installer-linkonly-appinst/%.o,$(INSTALLER_LINKONLY_APPINST_SRCS)) INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS := $(patsubst %.c,build/installer-runtime-resolve-appinst/%.o,$(INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS)) +INSTALLER_SYSMODULE_APPINST_OBJS := $(patsubst %.c,build/installer-sysmodule-appinst/%.o,$(INSTALLER_SYSMODULE_APPINST_SRCS)) +INSTALLER_WEBSRV_PATTERN_OBJS := $(patsubst %.c,build/installer-websrv-pattern/%.o,$(INSTALLER_WEBSRV_PATTERN_SRCS)) COMMON_CFLAGS := -Os -Wall -Werror -Isrc COMMON_CFLAGS += -ffunction-sections -fdata-sections -flto @@ -151,6 +163,8 @@ INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS += -DBFPILOT_BUILD_MODE=\"installer-run COMMON_LDFLAGS := -Wl,--gc-sections -flto APPINST_LDLIBS := -lSceAppInstUtil +PRIVILEGED_APPINST_LDLIBS := -lkernel_sys -lSceSystemService +PRIVILEGED_APPINST_LDLIBS += -lSceUserService -lSceAppInstUtil CC_CMD := "$(CC)" STRIP_CMD := "$(STRIP)" @@ -174,7 +188,10 @@ WINDOWS_BASE_SUFFIX += -l:crtend.o -l:crtn.o WINDOWS_WEB_SUFFIX := -lkernel_web -lSceLibcInternal -lSceNet WINDOWS_WEB_SUFFIX += -lc_stub_weak -lkernel_stub_weak WINDOWS_WEB_SUFFIX += -l:crtend.o -l:crtn.o -WINDOWS_APPINST_SUFFIX := -lSceAppInstUtil $(WINDOWS_BASE_SUFFIX) +WINDOWS_APPINST_SUFFIX := -lkernel_sys -lSceSystemService +WINDOWS_APPINST_SUFFIX += -lSceUserService -lSceAppInstUtil +WINDOWS_APPINST_SUFFIX += -lSceLibcInternal -lc_stub_weak -lkernel_stub_weak +WINDOWS_APPINST_SUFFIX += -l:crtend.o -l:crtn.o define run bash -lc '$(RUN_ENV) && $(1)' endef @@ -184,7 +201,7 @@ $(1) endef endif -all: bfpilot debug full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst +all: bfpilot debug full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst installer-websrv-pattern bfpilot: $(BFPILOT_BIN) @@ -210,6 +227,10 @@ installer-linkonly-appinst: $(INSTALLER_LINKONLY_APPINST_BIN) installer-runtime-resolve-appinst: $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) +installer-sysmodule-appinst: $(INSTALLER_SYSMODULE_APPINST_BIN) + +installer-websrv-pattern: $(INSTALLER_WEBSRV_PATTERN_BIN) + gen/assets: $(call run,mkdir -p $@) @@ -260,6 +281,14 @@ build/installer-runtime-resolve-appinst/%.o: %.c Makefile $(call run,mkdir -p $(dir $@)) $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@) +build/installer-sysmodule-appinst/%.o: %.c Makefile + $(call run,mkdir -p $(dir $@)) + $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@) + +build/installer-websrv-pattern/%.o: %.c Makefile + $(call run,mkdir -p $(dir $@)) + $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@) + ifeq ($(HOST_IS_WINDOWS),1) $(BFPILOT_BIN): $(BFPILOT_OBJS) $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(BFPILOT_OBJS) $(WINDOWS_WEB_SUFFIX)) @@ -306,6 +335,10 @@ $(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN): $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS) $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS) $(WINDOWS_BASE_SUFFIX)) $(call run,$(STRIP_CMD) --strip-all $@) + +$(INSTALLER_WEBSRV_PATTERN_BIN): $(INSTALLER_WEBSRV_PATTERN_OBJS) + $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_WEBSRV_PATTERN_OBJS) $(WINDOWS_APPINST_SUFFIX)) + $(call run,$(STRIP_CMD) --strip-all $@) else $(BFPILOT_BIN): $(BFPILOT_OBJS) $(call run,$(CC_CMD) $(BFPILOT_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(BFPILOT_OBJS)) @@ -322,7 +355,7 @@ $(FULL_BIN): $(LEGACY_FULL_OBJS) $(APP_ASSETS) $(call run,$(STRIP_CMD) --strip-all $@) $(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) - $(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(APPINST_LDLIBS)) + $(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(PRIVILEGED_APPINST_LDLIBS)) $(call run,$(STRIP_CMD) --strip-all $@) $(SAFE_LAUNCHER_INSTALLER_BIN): $(SAFE_LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) @@ -352,10 +385,18 @@ $(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN): $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS) $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS)) $(call run,$(STRIP_CMD) --strip-all $@) + +$(INSTALLER_SYSMODULE_APPINST_BIN): $(INSTALLER_SYSMODULE_APPINST_OBJS) + $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_SYSMODULE_APPINST_OBJS) -lSceSysmodule) + $(call run,$(STRIP_CMD) --strip-all $@) + +$(INSTALLER_WEBSRV_PATTERN_BIN): $(INSTALLER_WEBSRV_PATTERN_OBJS) + $(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_WEBSRV_PATTERN_OBJS) -lkernel_sys -lSceSystemService -lSceUserService -lSceAppInstUtil) + $(call run,$(STRIP_CMD) --strip-all $@) endif inspect-imports: all - $(call run,bash scripts/inspect_imports.sh $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN)) + $(call run,bash scripts/inspect_imports.sh $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) $(INSTALLER_WEBSRV_PATTERN_BIN)) deploy-bfpilot: bfpilot $(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(BFPILOT_BIN)) @@ -369,8 +410,11 @@ deploy-full: full deploy-launcher-installer: launcher-installer $(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(LAUNCHER_INSTALLER_BIN)) +ps5-diag: + $(call run,$(PYTHON) scripts/ps5_diag.py) + clean: - $(call run,rm -rf $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) bfpilot-full-legacy.elf $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) bfpilot-core.elf build gen $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) tests/hello_appinst.elf) + $(call run,rm -rf $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) bfpilot-full-legacy.elf $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) bfpilot-core.elf build gen $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) $(INSTALLER_SYSMODULE_APPINST_BIN) $(INSTALLER_WEBSRV_PATTERN_BIN) tests/hello_appinst.elf) .SECONDARY: $(GEN_SRCS) -.PHONY: all bfpilot debug full legacy-full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer +.PHONY: all bfpilot debug full legacy-full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst installer-sysmodule-appinst installer-websrv-pattern inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer ps5-diag diff --git a/README.md b/README.md index 548950c..57f9d1d 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ previous full build. Use `bfpilot-launcher-installer.elf` only after `bfpilot.elf` works. It is a separate optional payload whose only job is installing or refreshing the -home-screen tile. It intentionally direct-links AppInstUtil, matching the -websrv/Payload Manager/ftpsrv launcher-install pattern. If the loader rejects -that import, the installer can fail before `main()`. The file manager is still -unaffected. +home-screen tile. It uses the complete pattern proven by ps5 websrv: direct +`kernel_sys`, SystemService, UserService, and AppInstUtil imports, UserService +initialization, and temporary system authid during registration. It restores +the original authid before exit. The file manager is still unaffected. Use `bfpilot-launcher-installer-safe.elf` only for diagnostics. It does not direct-link AppInstUtil and reports whether AppInstUtil is already reachable @@ -105,10 +105,9 @@ send bfpilot-launcher-installer.elf to :9021 check /data/BFpilot/launcher-installer.log ``` -If `bfpilot-launcher-installer.elf` gives no notification and no log, send -`bfpilot-launcher-installer-safe.elf` next. If the safe installer reaches -`main()` but says AppInstUtil is unavailable, the direct installer is being -rejected before it can run. +If `bfpilot-launcher-installer.elf` gives no notification and no log, run the +probes below to distinguish ordinary entry, incomplete AppInst imports, and the +complete websrv dependency pattern. If the launcher installer appears to do nothing, run the probes in this order: @@ -116,6 +115,7 @@ If the launcher installer appears to do nothing, run the probes in this order: tests/installer_enter_probe.elf tests/installer_linkonly_appinst.elf tests/installer_runtime_resolve_appinst.elf +tests/installer_websrv_pattern.elf ``` If `installer_linkonly_appinst.elf` does not create @@ -190,9 +190,9 @@ make inspect-imports It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain AppInstUtil, `sceAppInst`, `app_installer`, or `BFPL00001` installer fingerprints. Direct -`libSceAppInstUtil.sprx` import is allowed only for -`bfpilot-launcher-installer.elf` and -`tests/installer_linkonly_appinst.elf`. +`libSceAppInstUtil.sprx` import is allowed only for installer/probe targets. +The release installer must also import `kernel_sys`, SystemService, and +UserService; `make inspect-imports` enforces this composition. ## Project Layout diff --git a/bfpilot-debug.elf b/bfpilot-debug.elf new file mode 100755 index 0000000..110b045 Binary files /dev/null and b/bfpilot-debug.elf differ diff --git a/bfpilot-full.elf b/bfpilot-full.elf new file mode 100755 index 0000000..c55377d Binary files /dev/null and b/bfpilot-full.elf differ diff --git a/bfpilot-launcher-installer-safe.elf b/bfpilot-launcher-installer-safe.elf new file mode 100755 index 0000000..da973f2 Binary files /dev/null and b/bfpilot-launcher-installer-safe.elf differ diff --git a/bfpilot-launcher-installer.elf b/bfpilot-launcher-installer.elf new file mode 100755 index 0000000..15aa210 Binary files /dev/null and b/bfpilot-launcher-installer.elf differ diff --git a/bfpilot.elf b/bfpilot.elf new file mode 100755 index 0000000..de3d50e Binary files /dev/null and b/bfpilot.elf differ diff --git a/build-tools/llvm-config b/build-tools/llvm-config old mode 100644 new mode 100755 diff --git a/docs/CODEX_FINAL_REPORT.md b/docs/CODEX_FINAL_REPORT.md new file mode 100644 index 0000000..e60c2bc --- /dev/null +++ b/docs/CODEX_FINAL_REPORT.md @@ -0,0 +1,68 @@ +# BFpilot Final Report + +## Root Causes And Fixes + +Copy performance was code-limited by 256 KiB buffers and a forced `fsync` after +every copied file. Copy/upload now use configurable 1 MiB buffers, copy no +longer forces per-file `fsync`, and transfer diagnostics report bytes, +elapsed-ms, MB/s, paths, device IDs, return state, and errno through job status, +upload responses, logs, and `/api/fs/transfer/stats`. + +The tile failure was architectural. BFpilot linked AppInstUtil alone and omitted +the working-project service/privilege context. Live testing proved: + +- AppInst-only direct import was rejected before `main()`. +- Safe runtime lookup returned `0xffffffff`. +- Runtime `dlopen` and Sysmodule loading stopped before returning. +- The exact ps5 websrv pattern reached `main()` and AppInst init returned `0`. + +`bfpilot-launcher-installer.elf` now directly links `kernel_sys`, +SystemService, UserService, and AppInstUtil; initializes UserService; sets +authid `0x4801000000000013`; performs non-destructive staging/registration; and +restores its original authid before exit. Live `AppInstallTitleDir` returned +`0`, installing `BFPL00001` with `http://127.0.0.1:5905/`. + +## Payload Choice + +- `bfpilot.elf`: stable file manager only. +- `bfpilot-debug.elf`: file manager startup proof. +- `bfpilot-launcher-installer.elf`: proven isolated tile installer. +- `bfpilot-launcher-installer-safe.elf`: read-only AppInst availability probe. +- `bfpilot-full.elf`: experimental integrated dynamic path; not the recommended installer. + +## Live Measurements + +| Test | Result | +| --- | --- | +| 8 MiB upload | 3.46 MiB/s server, 2.331 s client | +| 8 MiB same-device copy | 133.33 MiB/s, 60 ms | +| 32 MiB upload | 3.92 MiB/s server, 8.183 s client | +| 32 MiB download | 5.58 MiB/s client | +| 32 MiB same-device copy | 179.78 MiB/s, 178 ms | + +Internal copy is 34-46 times faster than upload. `/data` storage and PS5-side +copy are not the current upload bottleneck; the limiting path is LAN/client/HTTP +streaming. Browser folder upload remains sequential per file. + +## Exact Commands + +```sh +export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk +make clean all +make inspect-imports + +python3 payload_sender.py 192.168.1.204 9021 bfpilot.elf +PS5_IP=192.168.1.204 BF_WEB_PORT=5905 make ps5-diag + +python3 payload_sender.py 192.168.1.204 9021 bfpilot-launcher-installer.elf +PS5_IP=192.168.1.204 BF_WEB_PORT=5905 python3 scripts/ps5_diag.py --logs +``` + +## Remaining Limitations + +- Results prove the repaired installer on the tested firmware/loader, not every + firmware in existence. +- Download still uses a 64 KiB userspace loop without `sendfile`. +- Recursive directory copy pre-scans for accurate progress. +- USB write throughput was not benchmarked. +- `bfpilot-full.elf` remains less compatible than the isolated installer. diff --git a/docs/CODEX_ROOT_CAUSE_AUDIT.md b/docs/CODEX_ROOT_CAUSE_AUDIT.md new file mode 100644 index 0000000..be49029 --- /dev/null +++ b/docs/CODEX_ROOT_CAUSE_AUDIT.md @@ -0,0 +1,152 @@ +# BFpilot Root-Cause Audit + +Audit date: 2026-06-12 + +## Safety Finding + +The previous `/api/diag` and `/api/fs/usb` implementations were not read-only. +They created, truncated, and deleted probe files under `/data/BFpilot`, +`/user/app`, and discovered mount paths. This violated the diagnostic safety +model. The probes are now removed. Write capability is reported as unknown and +the server advertises `diagReadOnly: true`. + +Live writes were limited to BFpilot benchmark artifacts under +`/data/BFpilot/bench`, diagnostic logs under `/data/BFpilot`, and the explicitly +requested launcher staging directory `/user/app/BFPL00001`. No remote files +were deleted. The stable file manager remained available on port 5905. + +## Architecture + +- `src/lite_main.c`: boot marker, reload handoff, optional integrated launcher + path, then the HTTP listener. +- `src/websrv_lite.c`: threaded HTTP server and `/api/status`/`/api/diag`. +- `src/fs.c`: direct download streaming through `/fs/`. +- `src/transfer.c`: list/stat/du, upload, PS5-side copy/move/delete, and the + single long-running job state. +- `assets/files.html`: browser UI. Copy and move call PS5-side API endpoints; + files do not round-trip through the browser. +- `src/app_installer.c`: dynamically resolved integrated installer used only by + `bfpilot-full.elf`. +- `src/launcher_installer_force_main.c`: direct AppInstUtil installer. +- `src/launcher_installer_main.c`: runtime-resolution diagnostic installer. + +## Transfer Paths + +### Upload + +The browser sends one raw `XMLHttpRequest` body per file to +`POST /api/fs/upload`. There is no multipart parser and no chunked HTTP +support; `Content-Length` is required. Files are uploaded sequentially. The +server receives and writes each chunk directly to the final path. On an upload +failure, only the incomplete file created by that request is removed. + +### Download + +`src/fs.c` opens the requested file, sends a fixed `Content-Length`, then +streams it with a 64 KiB buffer. It does not use `sendfile`. + +### Copy And Move + +Copy is PS5-side. It performs a full recursive pre-scan for totals, copies to a +unique `.bfpilot-part-*` staging path, then renames the staging path into place. +Move first attempts `rename`; only `EXDEV` falls back to copy plus source +cleanup. Cancellation is checked between reads and recursive entries. + +### Progress + +There is one global file-operation job. Counters are atomic; strings and +timestamps use a short mutex. Browser polling runs every 500-700 ms in separate +HTTP client threads and does not hold the worker lock while transferring. + +## Performance Root Causes + +Confirmed from source: + +- Copy and upload used fixed 256 KiB buffers. +- Every copied file ended with synchronous `fsync`, which can dominate copies + involving many files or slow USB media. +- Directory copy performs two recursive walks: pre-scan and copy. +- Upload is sequential per browser file, so many small files pay one HTTP + request/response cost each. +- Download uses a smaller 64 KiB buffer. +- There is no multipart parsing, per-chunk allocation, destination rescanning, + or global lock around transfer I/O. + +Changes: + +- Copy/upload buffer is now configurable through + `BFPILOT_TRANSFER_BUF_SIZE`, defaulting to 1 MiB. +- Removed per-file `fsync`; close and staged final rename remain checked. +- Added monotonic elapsed milliseconds, bytes read/written, average MB/s, + current/source/destination paths, source/destination `st_dev`, return state, + and errno. +- Added `/api/fs/transfer/stats` for the last upload and extended job status. + +Live safe benchmark results are recorded in `docs/CODEX_FINAL_REPORT.md`. +Same-device internal copy reached 133-180 MiB/s while upload reached 3.46-3.92 +MiB/s and download reached 5.58 MiB/s. This isolates the current throughput +limit to the LAN/client/HTTP streaming path rather than `/data` storage or the +PS5-side copy loop. + +## Launcher Build Boundaries + +| Target | Launcher behavior | AppInstUtil handling | +| --- | --- | --- | +| `bfpilot.elf` | none | no launcher source; no direct import | +| `bfpilot-debug.elf` | none | no launcher source; no direct import | +| `bfpilot-full.elf` | experimental integrated install | dynamic resolution via `sce_resolve.c` | +| `bfpilot-launcher-installer-safe.elf` | diagnostic/runtime path | runtime handle/symbol lookup | +| `bfpilot-launcher-installer.elf` | isolated installer | direct websrv-pattern imports: `kernel_sys`, SystemService, UserService, AppInstUtil | +| `tests/installer_linkonly_appinst.elf` | import probe | direct `-lSceAppInstUtil` | +| runtime/entry probes | diagnostics | no direct AppInstUtil import | + +The tile metadata in `assets-app/param.json` points to +`http://127.0.0.1:5905/`. + +The original direct installer linked only AppInstUtil. That ELF was rejected +before `main()`. Runtime handle lookup returned `0xffffffff`; both `dlopen` and +`sceSysmoduleLoadModuleInternal` stopped before returning. Comparing working +projects exposed the missing architecture: + +- ps5-payload-dev/websrv links `kernel_sys`, SystemService, UserService, and + AppInstUtil together. +- It initializes UserService and changes the process authid to + `0x4801000000000013` before AppInst initialization. +- Elf Arsenal independently uses the same authid and documents it as required + for AppInst initialization on firmware 11.00+. + +An exact non-installing websrv-pattern probe reached `main()` live and returned +zero from UserService init, authid setup, and AppInst init. The repaired +isolated installer then returned zero from AppInst init and +`AppInstallTitleDir`, registered `BFPL00001`, and restored the original authid. +This proves the root cause was BFpilot's incomplete direct dependency and +privilege setup, not tile metadata. + +Launcher refresh previously uninstalled existing/legacy tiles and removed an +invalid app tree before proving registration would succeed. That made failure +recovery worse. Those removals are now skipped. Installers log the title ID, +deep link, embedded asset sizes, PNG signature, init/result codes, and staging +results. + +## Build Baseline And Results + +Initial local failures: + +- `make clean`: failed because `PS5_PAYLOAD_SDK` was absent. +- Public SDK source checkout was not an installed SDK. +- Binary SDK initially failed because repository + `build-tools/llvm-config` lacked executable mode and host `ld.lld`/LLVM tool + names were unavailable. + +After downloading the public binary SDK, making `build-tools/llvm-config` +executable, and supplying host LLVM tool aliases: + +- baseline `make clean all`: PASS before behavior changes. +- final `make clean all`: PASS. +- final `make inspect-imports`: PASS. +- Python syntax checks: PASS. +- `make ps5-diag`: works without requiring the SDK and safely refuses the old + mutating `/api/diag`. + +The workspace has an empty read-only `.git` mount, so changes are separated by +subsystem but actual commits could not be created. diff --git a/docs/COMPATIBILITY_STRATEGY.md b/docs/COMPATIBILITY_STRATEGY.md index 2aa85c6..048735f 100644 --- a/docs/COMPATIBILITY_STRATEGY.md +++ b/docs/COMPATIBILITY_STRATEGY.md @@ -51,6 +51,9 @@ It is intentionally isolated from the file manager: - It direct-links AppInstUtil, matching the launcher-install pattern used by websrv, Payload Manager, and ftpsrv. +- It also links `kernel_sys`, SystemService, and UserService, initializes + UserService, temporarily sets authid `0x4801000000000013`, and restores the + original authid after registration. - It logs to `/data/BFpilot/launcher-installer.log`. - It cannot break the file manager because it is not part of `bfpilot.elf`. @@ -78,11 +81,11 @@ The current probes separate three cases: `kernel_dynlib_handle` returns `0xffffffff`, AppInstUtil is not reachable through the safe runtime path. -On that combination, the safe runtime path cannot install. The force installer -uses the fragile direct AppInst import path because that is the pattern used by -working launcher installers. If the loader rejects that import before `main()`, -no code inside that ELF can recover. The file manager remains supported because -it avoids that import entirely. +The AppInst-only direct probe can fail even when the complete websrv pattern +works. `tests/installer_websrv_pattern.elf` reproduces the full dependency and +privilege setup without registering a title. On the tested live PS5 it reached +`main()` and returned zero from AppInst initialization; the repaired isolated +installer then registered the tile successfully. ## Patterns Checked @@ -98,11 +101,10 @@ That pattern can work in environments where direct AppInst imports are accepted, but it can also fail before `main()` on loaders that reject or cannot satisfy the import. BFpilot keeps this pattern out of `bfpilot.elf`. -Some payloads also depend on a system-authid or other elevated runtime context -before AppInst init can succeed on newer firmware. BFpilot does not implement -authid changes, kernel patches, kstuff integration, forced unloads, package -installers, or DRM-related behavior. Those are outside the compatibility goal -for the standalone file manager. +Working payloads depend on a system authid before AppInst init can succeed on +newer firmware. BFpilot implements that only in the isolated launcher installer. +The stable file manager does not change authid and does not import launcher +services. ## Boot Marker @@ -185,8 +187,7 @@ It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain: - `app_installer` - `BFPL00001` -The only payloads allowed to directly import AppInstUtil are -`bfpilot-launcher-installer.elf` and -`tests/installer_linkonly_appinst.elf`. The link-only test exists specifically -to prove whether that loader/firmware rejects AppInstUtil-linked ELFs before -`main()`. +Only installer/probe payloads may directly import AppInstUtil. The release +installer must additionally import `kernel_sys`, SystemService, and UserService. +The link-only test intentionally proves that an incomplete AppInst-only +composition can be rejected before `main()`. diff --git a/docs/FIRMWARE_TESTING.md b/docs/FIRMWARE_TESTING.md index 3bc69ea..ce9f46a 100644 --- a/docs/FIRMWARE_TESTING.md +++ b/docs/FIRMWARE_TESTING.md @@ -1,212 +1,177 @@ # BFpilot Firmware Testing -Use this protocol on every firmware/loader combination. Test the file manager -first. Test launcher/AppInstUtil only after the file manager works. +Use this order on every firmware/loader combination. Do not skip ahead to the +launcher. Record the exact payload, endpoint, HTTP status/return code, expected +marker, actual result, and relevant source subsystem. -Record: +Remote diagnostics are read-only. Remote write tests require: -- Firmware version. -- Exploit/loader name and version. -- Payload name. -- Whether `/data/BFpilot/boot.log` received a new entry. -- Whether a notification appeared. -- Whether the HTTP server started. -- `/api/status` and `/api/diag` output when reachable. -- Relevant log files under `/data/BFpilot`. +```sh +export BF_ALLOW_PS5_WRITE=1 +export BF_ALLOWED_REMOTE_ROOTS=/data/BFpilot/bench +``` -## Stage A: Main File Manager +Never delete remote benchmark artifacts during diagnostics. They have unique +timestamped BFpilot-owned names and can be reviewed before any later cleanup. -1. Send `bfpilot.elf` to the loader on port `9021`. -2. Verify `BFpilot BOOT` notification if notifications are working. -3. Verify `/data/BFpilot/boot.log` has a new `bfpilot file-manager` entry. -4. Open `http://PS5_IP:5905/`. -5. Open `http://PS5_IP:5905/api/status`. -6. Open `http://PS5_IP:5905/api/diag`. -7. Browse `/data`. -8. Browse `/mnt/usb0` if an external drive is connected. -9. Upload a small text file to `/data/BFpilot-test`. -10. Copy, move, and delete that test file. -11. Save `/data/BFpilot/log.txt`. +## 1. Build Locally -Pass criteria: +```sh +export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk +make clean all +make inspect-imports +``` -- Boot marker appears. -- Server starts on port `5905`. -- `/`, `/api/status`, `/api/diag`, `/fs`, and `/api/fs/*` are reachable. -- Basic file operations work in `/data/BFpilot-test`. +Expected: all ELFs build; import inspection reports compatibility checks passed. -## Stage B: Debug File Manager +Failure meaning: source/toolchain/import boundary failure. Do not inject. -Use this when Stage A appears to do nothing or when testing a new loader. +## 2. Stable File Manager -1. Send `bfpilot-debug.elf` to the loader on port `9021`. -2. Verify `/data/BFpilot/boot.log` has a new `bfpilot-debug debug` entry. -3. Open `http://PS5_IP:5905/api/status`. -4. Open `http://PS5_IP:5905/api/diag`. -5. Save `/data/BFpilot/log.txt`. +```sh +python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" bfpilot.elf +PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" make ps5-diag +``` -Expected result: +Expected notification/log: `BFpilot BOOT`; a new `bfpilot file-manager` line in +`/data/BFpilot/boot.log`; server startup lines in `/data/BFpilot/log.txt`. -- If `boot.log` has no new entry, the payload likely failed before `main()` or - the loader rejected it. -- If `boot.log` has an entry but `/api/status` is unreachable, the failure is - after `main()` and before or during server startup. +Expected endpoints: `/api/status` and `/api/diag` return HTTP 200; +`/api/status` reports `mode=file-manager`, `port=5905`, and +`diagReadOnly=true`. -## Stage C: Alternate Port +Failure meaning: -Use this when another BFpilot instance may already be running on `5905`. +- No boot marker: loader rejected the ELF or failure occurred before `main()`. +- Boot marker but no status: failure after `main()` during startup/bind/listen. +- Status works but diagnostics fail: diagnostic route defect. -1. Send `bfpilot-debug.elf --port 5906` to the loader. -2. Open `http://PS5_IP:5906/api/status`. -3. Open `http://PS5_IP:5906/api/diag`. -4. Save `/data/BFpilot/log.txt`. +Next action: inject the debug payload only if this stage fails. -Pass criteria: +## 3. Debug File Manager -- The new payload can start on `5906` without touching the old server on `5905`. +```sh +python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" bfpilot-debug.elf +PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" make ps5-diag +``` -## Stage D: Reinjection +Expected: `bfpilot-debug debug` boot marker and stronger startup notifications. -1. Start `bfpilot.elf`. -2. Reinject `bfpilot.elf` while idle. -3. Verify notification `BFpilot reload: old server detected`. -4. Verify the old listener stops cleanly and the new server starts. -5. Start a large copy or move. -6. Reinject `bfpilot.elf` during the active job. -7. Verify notification `BFpilot reload blocked`. -8. Verify the old job is not killed. -9. Save `/data/BFpilot/log.txt`. +Failure meaning: use `/data/BFpilot/boot.log`, `/data/BFpilot/log.txt`, and +`/data/BFpilot/crash.log` to locate the last checkpoint. -Pass criteria: +## 4. Safe Transfer Benchmark -- Idle reinjection performs a clean handoff. -- Active file jobs are not interrupted. -- Reload exits are logged and notified instead of appearing silent. +Run only after the stable file manager passes and writes are explicitly +authorized: -## Stage E: Launcher Installer +```sh +PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" \ +BF_ALLOW_PS5_WRITE=1 \ +BF_ALLOWED_REMOTE_ROOTS=/data/BFpilot/bench \ +python3 scripts/ps5_diag.py --bench +``` -Only run this after Stage A passes on the same firmware/loader. +Expected files: unique timestamped source/copy folders only under +`/data/BFpilot/bench`. -### Integrated Full Build +Expected results: upload response contains `elapsedMs`, `averageMBps`, and +`destinationDev`; copy job contains bytes read/written, elapsed ms, MB/s, +source/destination device IDs, and errno. -This is the first installer-capable build to try on firmware 11.6, because it -preserves the 0.2.0-style launcher flow that already installed there. +Failure meaning: -1. Send `bfpilot-full.elf` to the loader on port `9021`. -2. Watch for `BFpilot BOOT`, `BFpilot app`, and `BFpilot started` - notifications. -3. Open `http://PS5_IP:5905/`. -4. Save `/data/BFpilot/log.txt`. -5. Open `http://PS5_IP:5905/api/diag` if the web server starts. -6. Check whether the BFpilot tile appears or refreshes. +- Slow client upload and slow server upload metric: network/browser/receive or + storage path. +- Fast server upload metric but slow client elapsed time: browser/network. +- Slow same-device copy: copy/storage path. +- Slow only when device IDs differ: USB/cross-filesystem path. -Pass criteria: +Next action: save the generated diagnostics JSON. Do not benchmark user files. -- On firmware where AppInstUtil runtime resolution works, the tile installs or - refreshes. -- If launcher install fails, the web server still starts and logs the exact - AppInst resolution/init/install return codes. +## 5. Launcher Diagnostic Order -### Isolated Direct Installer +Only continue after stages 1-4 are stable. -Use this after Stage A if you want to test the direct AppInst import pattern -used by websrv/Payload Manager/ftpsrv. It may fail before `main()` if the loader -rejects AppInstUtil imports. +### 5.1 Safe Installer -1. Send `bfpilot-launcher-installer.elf` to the loader on port `9021`. -2. Watch for `BFpilot BOOT` and launcher installer notifications. -3. Check whether the BFpilot tile appears or refreshes. -4. Save `/data/BFpilot/launcher-installer.log`. -5. Save `/data/BFpilot/boot.log`. +Inject `bfpilot-launcher-installer-safe.elf`. -Launcher installer fields to record from the log: +Expected: boot marker and `/data/BFpilot/launcher-installer.log`. -- `entered main`. -- `AppInst init return code`. -- `/user/app/BFPL00001` mkdir return code. -- `param.json` and `icon0.png` write return code. -- `AppInstallTitleDir resolved`. -- `AppInstallTitleDir return code`. -- `AppInstallAll fallback return code`. -- `final result`. +Failure meaning: no marker means failure before `main()` unrelated to direct +AppInst import. A log showing AppInstUtil unavailable means runtime resolution +cannot install on this environment. -Pass criteria: +### 5.2 Entry Probe -- Launcher install may succeed or fail. -- Launcher failure does not affect `bfpilot.elf`. -- File manager remains testable through `http://PS5_IP:5905/`. +Inject `tests/installer_enter_probe.elf`. -If the direct installer gives no notification and no log: +Expected file: `/data/BFpilot/installer_enter_probe.txt`. -1. Send `bfpilot-launcher-installer-safe.elf`. -2. Save `/data/BFpilot/launcher-installer.log`. -3. If the safe installer reaches `main()` but logs - `kernel_dynlib_handle libSceAppInstUtil.sprx rc=0xffffffff`, AppInstUtil is - not available through the safe runtime path. -4. If `tests/installer_linkonly_appinst.elf` also produces no marker, direct - AppInst imports fail before `main()` on that loader/firmware. +Failure meaning: loader rejected even the safe installer-shaped probe. -In that case the direct installer is being rejected before it can run, while -the main file manager remains usable through `http://PS5_IP:5905/`. +### 5.3 Direct Import Probe -## Stage F: Minimal Probe Payloads +Inject `tests/installer_linkonly_appinst.elf`. -Use these to isolate loader, notification, HTTP, and AppInstUtil behavior. +Expected file: `/data/BFpilot/linkonly_appinst_entered.txt`. -### `tests/hello_boot.elf` +Failure meaning: absent marker after the entry probe passed means direct +AppInstUtil imports were rejected before `main()`. -1. Send `tests/hello_boot.elf`. -2. Verify boot marker notification. -3. Verify `/data/BFpilot/boot.log`. -4. Wait 10 seconds for `BFpilot BOOT still alive`. +### 5.4 Runtime Resolve Probe -### `tests/hello_http.elf` +Inject `tests/installer_runtime_resolve_appinst.elf`. -1. Send `tests/hello_http.elf`. -2. Open `http://PS5_IP:5906/api/status`. +Expected files: `/data/BFpilot/runtime_resolve_entered.txt` and +`/data/BFpilot/runtime_resolve_appinst.log`. -### `tests/hello_notify.elf` +Failure meaning: `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is +not reachable through the safe runtime path. -1. Send `tests/hello_notify.elf`. -2. Record notification result from sender output and `/data/BFpilot/boot.log`. +### 5.5 Exact Websrv-Pattern Probe -### `tests/installer_enter_probe.elf` +Build and inject `tests/installer_websrv_pattern.elf`: -1. Send `tests/installer_enter_probe.elf`. -2. Verify `/data/BFpilot/installer_enter_probe.txt` exists. -3. If it does not exist, the loader rejected even the safe installer-shaped - probe before `main()`. +```sh +make installer-websrv-pattern +python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" \ + tests/installer_websrv_pattern.elf +``` -### `tests/installer_linkonly_appinst.elf` +Expected files: `/data/BFpilot/websrv_pattern_entered.txt` and +`/data/BFpilot/websrv_pattern.log`. Expected return codes for UserService init, +authid setup, and AppInst init are all zero. -1. Send `tests/installer_linkonly_appinst.elf`. -2. Verify `/data/BFpilot/linkonly_appinst_entered.txt` exists. -3. If it does not exist, direct AppInstUtil linking is incompatible with that - loader/firmware and installer code must avoid direct AppInst imports. +Failure meaning: the loader/firmware does not support the complete dependency +and privilege composition used by the isolated installer. Do not attempt tile +registration. -### `tests/installer_runtime_resolve_appinst.elf` +### 5.6 Isolated Installer -1. Send `tests/installer_runtime_resolve_appinst.elf`. -2. Verify `/data/BFpilot/runtime_resolve_entered.txt` exists. -3. Save `/data/BFpilot/runtime_resolve_appinst.log`. -4. Use the log to see whether `kernel_dynlib_handle`, symbol lookup, and NID - resolve reached AppInstUtil. +Inject `bfpilot-launcher-installer.elf` only when the websrv-pattern probe +passes. -Interpretation: +Expected: boot marker, launcher log, metadata line showing title +`BFPL00001` and `http://127.0.0.1:5905/`, asset validation, AppInst init code, +staging codes, install code, and final result. -- `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is not already - loaded in this process. -- BFpilot intentionally skips `dlopen` for AppInstUtil after a prior test stops - before the `dlopen` result log, because that path is not safe enough for a - compatibility payload. +Failure meaning: -## Compatibility Table +- No boot marker: the complete direct-import set was rejected before `main()`. +- Init failed: AppInstUtil present but unusable in the current context. +- Authid setup failed: `kernel_sys` privilege operation unavailable. +- Asset/staging failed: path permissions or invalid/missing assets. +- Registration failed: AppInst install call rejected metadata/title directory. +- Installed but wrong URL: compare staged `param.json` with the logged deep link. -| firmware | exploit/loader | payload used | boot marker | server starts | file browse | upload | copy | move | delete | launcher install | notes | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| 12.70 | | bfpilot.elf | | | | | | | | n/a | | -| 12.70 | | bfpilot-full.elf | | | | | | | | | | -| 12.70 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | | -| 11.60 | | bfpilot.elf | | | | | | | | n/a | | -| 11.60 | | bfpilot-full.elf | | | | | | | | | | -| 11.60 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | | +### 5.7 Experimental Full Build + +Inject `bfpilot-full.elf` last. + +Expected: launcher diagnostics plus a functioning web server even if launcher +registration fails. + +Failure meaning: integrated dynamic path is incompatible with the environment. +Continue using `bfpilot.elf`; do not merge installer imports into it. diff --git a/payload_sender.py b/payload_sender.py new file mode 100644 index 0000000..c402699 --- /dev/null +++ b/payload_sender.py @@ -0,0 +1,29 @@ +import socket +def send_payload(jar_path, host, port=50000): + with open(jar_path, 'rb') as f: + data = f.read() + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + sock.sendall(data) + sock.close() + print(f"Sent {len(data)} bytes to {host}:{port}") + +if __name__ == "__main__": + import sys + if len(sys.argv) == 3: + host = sys.argv[1] + file_path = sys.argv[2] + send_payload(file_path, host) + elif len(sys.argv) == 4: + host = sys.argv[1] + port = int(sys.argv[2]) + file_path = sys.argv[3] + send_payload(file_path, host, port) + else: + print("Usage: python payload_sender.py ") + print(" python payload_sender.py ") + print("Examples:") + print(" python payload_sender.py 192.168.1.100 helloworld.js") + print(" python payload_sender.py 192.168.1.100 50000 helloworld.js") + print(" python payload_sender.py 192.168.1.100 9020 payload.bin") diff --git a/scripts/inspect_imports.sh b/scripts/inspect_imports.sh index d5f1ffa..25bce4c 100644 --- a/scripts/inspect_imports.sh +++ b/scripts/inspect_imports.sh @@ -58,7 +58,7 @@ check_no_direct_appinst_import() { local needed needed="$(needed_libraries "${file}" || true)" if printf '%s\n' "${needed}" | - grep -E 'libSceAppInstUtil\.sprx|libSceSystemService\.sprx|libSceUserService\.sprx' >/dev/null; then + 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 @@ -89,7 +89,7 @@ for file in "$@"; do echo "-- undefined symbols --" if [[ -n "${READELF}" ]]; then - "${READELF}" --dyn-symbols "${file}" | grep -E 'UND|Undefined' || true + "${READELF}" --dyn-syms "${file}" | grep -E 'UND|Undefined' || true elif [[ -n "${NM}" ]]; then "${NM}" -u "${file}" || true else @@ -108,6 +108,9 @@ for file in "$@"; do tests/installer_linkonly_appinst.elf|./tests/installer_linkonly_appinst.elf) continue ;; + tests/installer_websrv_pattern.elf|./tests/installer_websrv_pattern.elf) + continue + ;; esac if [[ -f "${file}" ]]; then check_no_direct_appinst_import "${file}" @@ -137,12 +140,15 @@ if [[ -f tests/installer_linkonly_appinst.elf ]]; then fi if [[ -f bfpilot-launcher-installer.elf ]]; then - if ! needed_libraries bfpilot-launcher-installer.elf | - grep -q 'libSceAppInstUtil\.sprx'; then - echo - echo "inspect-imports: bfpilot-launcher-installer.elf must directly import libSceAppInstUtil.sprx" >&2 - exit 1 - fi + installer_needed="$(needed_libraries bfpilot-launcher-installer.elf || true)" + for required in libkernel_sys.sprx libSceSystemService.sprx \ + libSceUserService.sprx libSceAppInstUtil.sprx; do + if ! printf '%s\n' "${installer_needed}" | grep -q "${required}"; then + echo + echo "inspect-imports: bfpilot-launcher-installer.elf missing ${required}" >&2 + exit 1 + fi + done fi echo diff --git a/scripts/ps5_diag.py b/scripts/ps5_diag.py new file mode 100644 index 0000000..c207468 --- /dev/null +++ b/scripts/ps5_diag.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Collect BFpilot HTTP diagnostics without mutating the PS5.""" + +from __future__ import annotations + +import datetime as dt +import json +import os +import pathlib +import sys +import time +import urllib.parse +import urllib.error +import urllib.request + + +def fetch_json(base: str, path: str) -> tuple[bool, object]: + url = base + path + try: + with urllib.request.urlopen(url, timeout=8) as response: + raw = response.read() + body = json.loads(raw) + if response.status != 200: + raise RuntimeError(f"HTTP {response.status}") + print(f"PASS GET {url} HTTP {response.status}") + return True, body + except (OSError, ValueError, urllib.error.URLError) as exc: + print(f"FAIL GET {url}: {exc}", file=sys.stderr) + return False, {"ok": False, "url": url, "error": str(exc)} + +def fetch_logs(base: str, out_dir: pathlib.Path, stamp: str) -> dict[str, object]: + results: dict[str, object] = {} + for name in ("boot.log", "log.txt", "crash.log", "launcher-installer.log"): + url = base + "/fs/data/BFpilot/" + urllib.parse.quote(name) + try: + with urllib.request.urlopen(url, timeout=8) as response: + data = response.read() + path = out_dir / f"{stamp}-{name}" + path.write_bytes(data) + results[name] = {"ok": True, "bytes": len(data), "saved": str(path)} + print(f"PASS GET {url} HTTP {response.status}") + except urllib.error.HTTPError as exc: + results[name] = {"ok": False, "http": exc.code} + print(f"SKIP GET {url}: HTTP {exc.code}") + except OSError as exc: + results[name] = {"ok": False, "error": str(exc)} + print(f"FAIL GET {url}: {exc}", file=sys.stderr) + return results + +def allowed_remote(path: str) -> bool: + roots = os.environ.get("BF_ALLOWED_REMOTE_ROOTS", "/data/BFpilot/bench") + return any(path == root.rstrip("/") or path.startswith(root.rstrip("/") + "/") + for root in roots.split(",") if root.startswith("/data/")) + + +def run_bench(base: str) -> tuple[bool, object]: + if os.environ.get("BF_ALLOW_PS5_WRITE", "0") != "1": + return False, {"ok": False, "skipped": True, + "reason": "BF_ALLOW_PS5_WRITE is not 1"} + + stamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ") + root = "/data/BFpilot/bench" + source_dir = f"{root}/source-{stamp}" + copy_dir = f"{root}/copy-{stamp}" + filename = "bfpilot-bench-8m.bin" + if not allowed_remote(source_dir) or not allowed_remote(copy_dir): + return False, {"ok": False, "skipped": True, + "reason": "benchmark paths not in BF_ALLOWED_REMOTE_ROOTS"} + + results: dict[str, object] = { + "sourceDir": source_dir, + "copyDir": copy_dir, + "cleanup": "not performed; diagnostics never delete remote files", + } + for path in (source_dir, copy_dir): + ok, body = fetch_json(base, "/api/fs/mkdir?path=" + urllib.parse.quote(path)) + results[f"mkdir:{path}"] = body + if not ok: + return False, results + + payload = bytes(8 * 1024 * 1024) + upload_url = (base + "/api/fs/upload?path=" + urllib.parse.quote(source_dir) + + "&filename=" + urllib.parse.quote(filename)) + request = urllib.request.Request(upload_url, data=payload, method="POST") + try: + started = time.monotonic() + with urllib.request.urlopen(request, timeout=120) as response: + upload = json.loads(response.read()) + results["upload"] = upload + results["clientUploadElapsedMs"] = round((time.monotonic() - started) * 1000) + print(f"PASS POST {upload_url} HTTP {response.status}") + except (OSError, ValueError, urllib.error.URLError) as exc: + results["upload"] = {"ok": False, "error": str(exc)} + print(f"FAIL POST {upload_url}: {exc}", file=sys.stderr) + return False, results + + source = f"{source_dir}/{filename}" + copy_path = ("/api/fs/copy?src=" + urllib.parse.quote(source) + + "&dst=" + urllib.parse.quote(copy_dir)) + ok, results["copyStart"] = fetch_json(base, copy_path) + if not ok: + return False, results + for _ in range(240): + ok, job = fetch_json(base, "/api/fs/job/status") + results["copyJob"] = job + if not ok or not isinstance(job, dict) or not job.get("busy"): + return bool(ok and isinstance(job, dict) and not job.get("error")), results + time.sleep(0.5) + results["copyJob"] = {"ok": False, "error": "timeout"} + return False, results + + +def main() -> int: + host = os.environ.get("PS5_IP", "192.168.1.204") + port = os.environ.get("BF_WEB_PORT", "5905") + allow_write = os.environ.get("BF_ALLOW_PS5_WRITE", "0") == "1" + base = f"http://{host}:{port}" + stamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ") + out_dir = pathlib.Path("diagnostics") + out_dir.mkdir(exist_ok=True) + + status_ok, status = fetch_json(base, "/api/status") + diag_ok = False + if isinstance(status, dict) and status.get("diagReadOnly") is True: + diag_ok, diag = fetch_json(base, "/api/diag") + elif allow_write: + print("WARN /api/diag read-only capability absent; querying because BF_ALLOW_PS5_WRITE=1") + diag_ok, diag = fetch_json(base, "/api/diag") + else: + diag = { + "ok": False, + "skipped": True, + "reason": "server does not advertise diagReadOnly=true", + } + diag_ok = True + print("SKIP GET /api/diag: deployed server may create/delete probe files") + + report = { + "timestampUtc": stamp, + "baseUrl": base, + "allowPs5Write": allow_write, + "status": status, + "diag": diag, + } + if "--bench" in sys.argv: + bench_ok, bench = run_bench(base) + report["bench"] = bench + else: + bench_ok = True + if "--logs" in sys.argv: + report["logs"] = fetch_logs(base, out_dir, stamp) + output = out_dir / f"ps5-diag-{stamp}.json" + output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n") + print(f"saved {output}") + return 0 if status_ok and diag_ok and bench_ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/app_installer.c b/src/app_installer.c index 3e1f05a..c3f1f0c 100644 --- a/src/app_installer.c +++ b/src/app_installer.c @@ -3,7 +3,6 @@ */ #include -#include #include #include #include @@ -47,13 +46,11 @@ INCASSET(bfpilot_param_json, "assets-app/param.json"); INCASSET(bfpilot_icon0_png, "assets-app/icon0.png"); typedef int (*appinst_initialize_fn)(void); -typedef int (*appinst_uninstall_fn)(const char *); typedef int (*app_install_title_dir_fn)(const char *, const char *, void *); typedef int (*appinst_install_all_fn)(void *); typedef struct appinst_api { appinst_initialize_fn initialize; - appinst_uninstall_fn uninstall; app_install_title_dir_fn install_title_dir; appinst_install_all_fn install_all; } appinst_api_t; @@ -61,12 +58,17 @@ typedef struct appinst_api { static const uint8_t g_install_marker[] = BFPILOT_INSTALL_MARKER; static bfpilot_launcher_diag_t g_launcher_diag = { .launcher_enabled = 1, - .appinst_init_rc = -1, + .launcher_attempted = 0, + .appinst_init_rc = BFPILOT_DIAG_SKIPPED, + .install_title_rc = BFPILOT_DIAG_SKIPPED, .install_title_dir_resolved = 0, .uninstall_resolved = 0, + .uninstall_rc = BFPILOT_DIAG_SKIPPED, .install_all_resolved = 0, + .install_all_rc = BFPILOT_DIAG_SKIPPED, .user_app_writable = -1, - .launcher_install_rc = -1, + .launcher_install_rc = BFPILOT_DIAG_SKIPPED, + .launcher_final_state = "not-attempted", }; @@ -80,12 +82,17 @@ static void reset_launcher_diag(void) { bfpilot_launcher_diag_t diag = { .launcher_enabled = 1, - .appinst_init_rc = -1, + .launcher_attempted = 1, + .appinst_init_rc = BFPILOT_DIAG_SKIPPED, + .install_title_rc = BFPILOT_DIAG_SKIPPED, .install_title_dir_resolved = 0, .uninstall_resolved = 0, + .uninstall_rc = BFPILOT_DIAG_SKIPPED, .install_all_resolved = 0, + .install_all_rc = BFPILOT_DIAG_SKIPPED, .user_app_writable = -1, - .launcher_install_rc = -1, + .launcher_install_rc = BFPILOT_DIAG_SKIPPED, + .launcher_final_state = "resolving-appinst", }; g_launcher_diag = diag; publish_launcher_diag(); @@ -111,16 +118,11 @@ resolve_appinst(appinst_api_t *api) { bfpilot_log("launcher resolve Initialize %s rc=0x%08x", api->initialize ? "ok" : "missing", rc); - rc = resolve_optional((void **)&api->uninstall, - "sceAppInstUtilAppUnInstall", NULL); - g_launcher_diag.uninstall_resolved = api->uninstall != NULL; - bfpilot_log("launcher resolve AppUnInstall %s rc=0x%08x", - api->uninstall ? "ok" : "missing", rc); - rc = resolve_optional((void **)&api->install_title_dir, "sceAppInstUtilAppInstallTitleDir", BFPILOT_NID_INSTALL_TITLE_DIR); g_launcher_diag.install_title_dir_resolved = api->install_title_dir != NULL; + g_launcher_diag.title_dir_resolved = api->install_title_dir != NULL; bfpilot_log("launcher resolve AppInstallTitleDir %s rc=0x%08x", api->install_title_dir ? "ok" : "missing", rc); @@ -134,25 +136,11 @@ resolve_appinst(appinst_api_t *api) { } -static int -probe_dir_writable(const char *dir) { - char probe[256]; - int n = snprintf(probe, sizeof(probe), "%s/.bfpilot_write_probe", dir); - if(n < 0 || (size_t)n >= sizeof(probe)) return 0; - - int fd = open(probe, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if(fd < 0) return 0; - - close(fd); - unlink(probe); - return 1; -} - - static int install_app(const appinst_api_t *api, const char *title_id, const char *dir) { if(api->install_title_dir) { int err = api->install_title_dir(title_id, dir, NULL); + g_launcher_diag.install_title_rc = err; g_launcher_diag.launcher_install_rc = err; publish_launcher_diag(); if(err == 0) return 0; @@ -163,6 +151,7 @@ install_app(const appinst_api_t *api, const char *title_id, const char *dir) { if(api->install_all) { int err = api->install_all(NULL); + g_launcher_diag.install_all_rc = err; g_launcher_diag.launcher_install_rc = err; publish_launcher_diag(); bfpilot_log("launcher install AppInstallAll rc=0x%08x", err); @@ -220,39 +209,6 @@ mkdir_if_needed(const char *path) { } -static int -remove_tree(const char *path) { - struct stat st; - if(lstat(path, &st) != 0) return errno == ENOENT ? 0 : -1; - - if(S_ISDIR(st.st_mode)) { - DIR *dir = opendir(path); - if(!dir) return -1; - - struct dirent *ent; - while((ent = readdir(dir))) { - if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; - - char child[512]; - int n = snprintf(child, sizeof(child), "%s/%s", path, ent->d_name); - if(n < 0 || (size_t)n >= sizeof(child)) { - closedir(dir); - errno = ENAMETOOLONG; - return -1; - } - if(remove_tree(child) != 0) { - closedir(dir); - return -1; - } - } - closedir(dir); - return rmdir(path); - } - - return unlink(path); -} - - static int ensure_data_dir(void) { if(mkdir_if_needed("/data") != 0) return -1; @@ -273,13 +229,25 @@ bfpilot_install_app_if_needed(void) { reset_launcher_diag(); resolve_appinst(&api); - g_launcher_diag.user_app_writable = probe_dir_writable(BFPILOT_APP_ROOT); + g_launcher_diag.user_app_writable = -1; publish_launcher_diag(); - bfpilot_log("launcher install %s writable=%d", BFPILOT_APP_ROOT, - g_launcher_diag.user_app_writable); + bfpilot_log("launcher install %s write probe skipped; installer is " + "non-destructive until staging", BFPILOT_APP_ROOT); + bfpilot_log("launcher metadata title_id=%s " + "deeplink=http://127.0.0.1:5905/ param_bytes=%lu " + "icon_bytes=%lu icon_png_magic=%s", + BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size, + (unsigned long)bfpilot_icon0_png_size, + bfpilot_icon0_png_size >= 8 && + bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' && + bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G' + ? "yes" : "no"); if(!api.initialize) { g_launcher_diag.launcher_install_rc = -2; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "appinst-unavailable"); publish_launcher_diag(); bfpilot_log("launcher install sceAppInstUtilInitialize not resolved"); bfpilot_notify("BFpilot app failed", "AppInst init not resolved"); @@ -288,6 +256,9 @@ bfpilot_install_app_if_needed(void) { if(!api.install_title_dir && !api.install_all) { g_launcher_diag.launcher_install_rc = -2; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "install-symbols-unavailable"); publish_launcher_diag(); bfpilot_log("launcher install no AppInst install function resolved"); bfpilot_notify("BFpilot app failed", "AppInst install not resolved"); @@ -324,40 +295,24 @@ bfpilot_install_app_if_needed(void) { bfpilot_log("launcher install sceAppInstUtilInitialize failed rc=0x%08x", err); g_launcher_diag.launcher_install_rc = err; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "appinst-init-failed"); publish_launcher_diag(); snprintf(msg, sizeof(msg), "AppInst init failed 0x%08x", err); bfpilot_notify("BFpilot app failed", msg); return -1; } - if(api.uninstall) { - int uninstall_err = api.uninstall(BFPILOT_APP_TITLE_ID); - bfpilot_log("launcher install refresh BFpilot tile rc=0x%08x", - uninstall_err); - uninstall_err = api.uninstall(BFPILOT_INVALID_APP_TITLE_ID); - bfpilot_log("launcher install remove invalid BFpilot tile rc=0x%08x", - uninstall_err); - uninstall_err = api.uninstall(BFPILOT_LEGACY_APP_TITLE_ID); - bfpilot_log("launcher install remove BS5FileManager tile rc=0x%08x", - uninstall_err); - uninstall_err = api.uninstall(BFPILOT_OLD_LEGACY_APP_TITLE_ID); - bfpilot_log("launcher install remove old legacy tile rc=0x%08x", - uninstall_err); - } else { - bfpilot_log("launcher install AppUnInstall not resolved, skipping removes"); - } - - char invalid_dir[256]; - snprintf(invalid_dir, sizeof(invalid_dir), BFPILOT_APP_ROOT "/%s", - BFPILOT_INVALID_APP_TITLE_ID); - if(remove_tree(invalid_dir) != 0) { - bfpilot_log("launcher install warning failed removing %s errno=%d", - invalid_dir, errno); - } + bfpilot_log("launcher existing/legacy tile removal skipped: " + "non-destructive refresh"); if(mkdir_if_needed(app_dir) != 0 || mkdir_if_needed(sce_sys_dir) != 0) { bfpilot_log("launcher install mkdir failed errno=%d", errno); g_launcher_diag.launcher_install_rc = -errno; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "staging-mkdir-failed"); publish_launcher_diag(); snprintf(msg, sizeof(msg), "mkdir failed errno %d", errno); bfpilot_notify("BFpilot app failed", msg); @@ -368,6 +323,9 @@ bfpilot_install_app_if_needed(void) { bfpilot_log("launcher install failed writing %s errno=%d", param_path, errno); g_launcher_diag.launcher_install_rc = -errno; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "param-write-failed"); publish_launcher_diag(); bfpilot_notify("BFpilot app failed", "could not write param.json"); return -1; @@ -377,6 +335,9 @@ bfpilot_install_app_if_needed(void) { bfpilot_log("launcher install failed writing %s errno=%d", icon_path, errno); g_launcher_diag.launcher_install_rc = -errno; + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "icon-write-failed"); publish_launcher_diag(); bfpilot_notify("BFpilot app failed", "could not write icon0.png"); return -1; @@ -384,6 +345,10 @@ bfpilot_install_app_if_needed(void) { err = install_app(&api, BFPILOT_APP_TITLE_ID, BFPILOT_APP_PARENT); if(err) { + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), + "%s", "registration-failed"); + publish_launcher_diag(); bfpilot_log("launcher install install_app failed rc=0x%08x", err); snprintf(msg, sizeof(msg), "register BFPL00001 failed 0x%08x", err); bfpilot_notify("BFpilot app failed", msg); @@ -401,5 +366,8 @@ bfpilot_install_app_if_needed(void) { bfpilot_notify("BFpilot app ready", "Tile BFPL00001 opens http://127.0.0.1:5905/"); + snprintf(g_launcher_diag.launcher_final_state, + sizeof(g_launcher_diag.launcher_final_state), "%s", "installed"); + publish_launcher_diag(); return 1; } diff --git a/src/diag.c b/src/diag.c index a4a592b..d5cbc8d 100644 --- a/src/diag.c +++ b/src/diag.c @@ -21,7 +21,6 @@ #define BFPILOT_HOMEBREW_DIR "/data/homebrew" #define BFPILOT_LOG_PATH "/data/BFpilot/log.txt" #define BFPILOT_CRASH_LOG_PATH "/data/BFpilot/crash.log" -#define BFPILOT_USER_APP_ROOT "/user/app" #define BFPILOT_EXT0_ROOT "/mnt/ext0" #define BFPILOT_EXT0_HOMEBREW "/mnt/ext0/homebrew" @@ -386,24 +385,6 @@ probe_opendir(const char *path) { } -static int -probe_write_file(const char *path) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if(fd < 0) { - bfpilot_diag_set_last_errno(errno); - return 0; - } - ssize_t wr = write(fd, "x", 1); - int close_rc = close(fd); - unlink(path); - if(wr != 1 || close_rc != 0) { - bfpilot_diag_set_last_errno(errno ? errno : EIO); - return 0; - } - return 1; -} - - int bfpilot_diag_can_stat_root(void) { return probe_stat("/"); @@ -456,12 +437,11 @@ bfpilot_diag_can_opendir_ext0_homebrew(void) { int bfpilot_diag_can_write_data_bfpilot(void) { - ensure_diag_dir(); - return probe_write_file(BFPILOT_DATA_DIR "/.write_probe"); + return -1; } int bfpilot_diag_can_write_user_app(void) { - return probe_write_file(BFPILOT_USER_APP_ROOT "/.bfpilot_write_probe"); + return -1; } diff --git a/src/launcher_installer_force_main.c b/src/launcher_installer_force_main.c index d97c55f..c92625b 100644 --- a/src/launcher_installer_force_main.c +++ b/src/launcher_installer_force_main.c @@ -30,9 +30,8 @@ #define BFPILOT_DATA_ROOT "/data" #define BFPILOT_DATA_DIR "/data/BFpilot" #define BFPILOT_LAUNCHER_LOG "/data/BFpilot/launcher-installer.log" -#define BFPILOT_APPINST_MODULE "libSceAppInstUtil.sprx" -#define BFPILOT_NID_INSTALL_TITLE_DIR "Wudg3Xe3heE" #define BFPILOT_DIAG_SKIPPED (-2147483000) +#define BFPILOT_APPINST_AUTHID UINT64_C(0x4801000000000013) #define INCASSET(name, file) \ __asm__(".section .rodata\n" \ @@ -49,11 +48,12 @@ 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 sceAppInstUtilAppInstallAll(void *); -int sceAppInstUtilAppUnInstall(const char *); - -typedef int (*app_install_title_dir_fn)(const char *, const char *, void *); static int @@ -124,26 +124,6 @@ write_file(const char *path, const uint8_t *data, size_t size) { } -static app_install_title_dir_fn -resolve_title_dir(void) { - uint32_t handle = 0; - installer_log("about to kernel_dynlib_handle %s", BFPILOT_APPINST_MODULE); - int handle_rc = kernel_dynlib_handle(-1, BFPILOT_APPINST_MODULE, &handle); - installer_log("kernel_dynlib_handle %s rc=0x%08x handle=0x%08x", - BFPILOT_APPINST_MODULE, handle_rc, handle); - if(handle_rc != 0) return NULL; - - installer_log("about to kernel_dynlib_resolve AppInstallTitleDir nid=%s", - BFPILOT_NID_INSTALL_TITLE_DIR); - app_install_title_dir_fn fn = - (app_install_title_dir_fn)kernel_dynlib_resolve( - -1, handle, BFPILOT_NID_INSTALL_TITLE_DIR); - installer_log("kernel_dynlib_resolve AppInstallTitleDir addr=%p", - (void *)fn); - return fn; -} - - int main(void) { bfpilot_boot_marker("bfpilot-launcher-installer", BFPILOT_BUILD_MODE); @@ -154,19 +134,60 @@ main(void) { char icon_path[256]; int title_dir_rc = BFPILOT_DIAG_SKIPPED; int install_all_rc = BFPILOT_DIAG_SKIPPED; - int uninstall_rc = BFPILOT_DIAG_SKIPPED; int final_rc = -1; + pid_t pid = getpid(); + uint64_t original_authid = kernel_get_ucred_authid(pid); + int set_authid_rc; + int restore_authid_rc = BFPILOT_DIAG_SKIPPED; + int user_service_rc; - installer_log("entered main direct-appinst tag=%s build=%s", + installer_log("entered main websrv-pattern-appinst tag=%s build=%s", VERSION_TAG, BUILD_VERSION); - bfpilot_notify("BFpilot launcher installer", "direct AppInst installer"); + installer_log("launcher metadata title_id=%s deeplink=http://127.0.0.1:5905/ " + "param_bytes=%lu icon_bytes=%lu icon_png_magic=%s", + BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size, + (unsigned long)bfpilot_icon0_png_size, + bfpilot_icon0_png_size >= 8 && + bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' && + bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G' + ? "yes" : "no"); + bfpilot_notify("BFpilot launcher installer", "websrv-pattern installer"); + + user_service_rc = sceUserServiceInitialize(NULL); + installer_log("sceUserServiceInitialize return code=0x%08x", + user_service_rc); + installer_log("privilege context original_authid=0x%016llx " + "target_authid=0x%016llx", + (unsigned long long)original_authid, + (unsigned long long)BFPILOT_APPINST_AUTHID); + set_authid_rc = kernel_set_ucred_authid(pid, BFPILOT_APPINST_AUTHID); + installer_log("kernel_set_ucred_authid target return code=0x%08x " + "current_authid=0x%016llx", + set_authid_rc, + (unsigned long long)kernel_get_ucred_authid(pid)); + if(set_authid_rc != 0) { + installer_log("final result=privilege_setup_failed final_rc=%d " + "set_authid_rc=0x%08x", final_rc, set_authid_rc); + sceUserServiceTerminate(); + bfpilot_notify("BFpilot launcher installer", "system authid setup failed"); + return 1; + } installer_log("about to sceAppInstUtilInitialize"); int appinst_init_rc = sceAppInstUtilInitialize(); installer_log("AppInst init return code=0x%08x", appinst_init_rc); if(appinst_init_rc != 0) { + if(original_authid != 0) { + restore_authid_rc = kernel_set_ucred_authid(pid, original_authid); + installer_log("kernel_set_ucred_authid restore return code=0x%08x " + "current_authid=0x%016llx", + restore_authid_rc, + (unsigned long long)kernel_get_ucred_authid(pid)); + } installer_log("final result=failed_nonfatal final_rc=%d " - "appinst_init_rc=0x%08x", final_rc, appinst_init_rc); + "appinst_init_rc=0x%08x restore_authid_rc=0x%08x", + final_rc, appinst_init_rc, restore_authid_rc); + sceUserServiceTerminate(); bfpilot_notify("BFpilot launcher installer", "AppInst init failed"); return 1; } @@ -177,10 +198,7 @@ main(void) { snprintf(param_path, sizeof(param_path), "%s/param.json", sce_sys_dir); snprintf(icon_path, sizeof(icon_path), "%s/icon0.png", sce_sys_dir); - installer_log("about to sceAppInstUtilAppUnInstall %s", - BFPILOT_APP_TITLE_ID); - uninstall_rc = sceAppInstUtilAppUnInstall(BFPILOT_APP_TITLE_ID); - installer_log("uninstall return code=0x%08x", uninstall_rc); + installer_log("existing tile uninstall skipped: non-destructive refresh"); errno = 0; int mkdir_app_rc = mkdir_if_needed(app_dir); @@ -201,10 +219,14 @@ main(void) { installer_log("%s write return code=%d errno=%d", icon_path, write_icon_rc, errno); - app_install_title_dir_fn install_title_dir = resolve_title_dir(); - installer_log("AppInstallTitleDir resolved=%s nid=%s", - install_title_dir ? "yes" : "no", - BFPILOT_NID_INSTALL_TITLE_DIR); + uint32_t appinst_handle = 0; + int handle_rc = kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx", + &appinst_handle); + app_install_title_dir_fn install_title_dir = handle_rc == 0 ? + (app_install_title_dir_fn)kernel_dynlib_resolve( + -1, appinst_handle, "Wudg3Xe3heE") : NULL; + installer_log("AppInstallTitleDir resolved=%s handle_rc=0x%08x", + install_title_dir ? "yes" : "no", handle_rc); if(mkdir_app_rc == 0 && mkdir_sce_sys_rc == 0 && write_param_rc == 0 && write_icon_rc == 0) { @@ -233,15 +255,27 @@ main(void) { final_rc = 0; } - installer_log("final result=%s final_rc=%d appinst_init_rc=0x%08x " - "uninstall_rc=0x%08x mkdir_app_rc=%d mkdir_sce_sys_rc=%d " - "write_param_rc=%d write_icon_rc=%d title_dir_rc=0x%08x " - "install_all_rc=0x%08x", - final_rc == 0 ? "installed" : "failed_nonfatal", final_rc, - appinst_init_rc, uninstall_rc, mkdir_app_rc, - mkdir_sce_sys_rc, write_param_rc, write_icon_rc, - title_dir_rc, install_all_rc); + if(original_authid != 0) { + restore_authid_rc = kernel_set_ucred_authid(pid, original_authid); + installer_log("kernel_set_ucred_authid restore return code=0x%08x " + "current_authid=0x%016llx", + restore_authid_rc, + (unsigned long long)kernel_get_ucred_authid(pid)); + } + installer_log("final result=%s final_rc=%d appinst_init_rc=0x%08x " + "user_service_rc=0x%08x " + "mkdir_app_rc=%d mkdir_sce_sys_rc=%d " + "write_param_rc=%d write_icon_rc=%d title_dir_rc=0x%08x " + "install_all_rc=0x%08x set_authid_rc=0x%08x " + "restore_authid_rc=0x%08x", + final_rc == 0 ? "installed" : "failed_nonfatal", final_rc, + appinst_init_rc, user_service_rc, mkdir_app_rc, + mkdir_sce_sys_rc, write_param_rc, write_icon_rc, + title_dir_rc, install_all_rc, set_authid_rc, + restore_authid_rc); + + sceUserServiceTerminate(); if(final_rc == 0) { bfpilot_notify("BFpilot launcher installer", "installed or refreshed"); return 0; diff --git a/src/launcher_installer_main.c b/src/launcher_installer_main.c index ac57fb6..aa5ad3e 100644 --- a/src/launcher_installer_main.c +++ b/src/launcher_installer_main.c @@ -18,6 +18,7 @@ #include "boot_marker.h" #include "notify.h" +#include "sce_resolve.h" #include "version.h" #define BFPILOT_APP_TITLE_ID "BFPL00001" @@ -138,7 +139,14 @@ main(void) { const char *final_state = "unsupported"; installer_log("entered main tag=%s build=%s", VERSION_TAG, BUILD_VERSION); - + installer_log("launcher metadata title_id=%s deeplink=http://127.0.0.1:5905/ " + "param_bytes=%lu icon_bytes=%lu icon_png_magic=%s", + BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size, + (unsigned long)bfpilot_icon0_png_size, + bfpilot_icon0_png_size >= 8 && + bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' && + bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G' + ? "yes" : "no"); installer_log("about to kernel_dynlib_handle %s", BFPILOT_APPINST_MODULE); appinst_handle_rc = kernel_dynlib_handle(-1, BFPILOT_APPINST_MODULE, &appinst_handle); diff --git a/src/sce_resolve.c b/src/sce_resolve.c index aaa834f..b1242df 100644 --- a/src/sce_resolve.c +++ b/src/sce_resolve.c @@ -9,7 +9,6 @@ #include "sce_resolve.h" - int sce_resolve_symbol(const char *module, const char *symbol, void **out) { uint32_t handle = 0; diff --git a/src/sce_resolve.h b/src/sce_resolve.h index 98f5785..979f6b8 100644 --- a/src/sce_resolve.h +++ b/src/sce_resolve.h @@ -5,4 +5,3 @@ #pragma once int sce_resolve_symbol(const char *module, const char *symbol, void **out); - diff --git a/src/transfer.c b/src/transfer.c index 4194f6c..3659a80 100644 --- a/src/transfer.c +++ b/src/transfer.c @@ -24,8 +24,12 @@ #include "websrv.h" -#define COPY_BUF_SIZE (256 * 1024) -#define UPLOAD_BUF_SIZE (256 * 1024) +#ifndef BFPILOT_TRANSFER_BUF_SIZE +#define BFPILOT_TRANSFER_BUF_SIZE (1024 * 1024) +#endif + +#define COPY_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE +#define UPLOAD_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE typedef struct json_buf { @@ -192,11 +196,20 @@ struct job_state { atomic_int total_files; atomic_int done_files; atomic_int failed_files; + atomic_long elapsed_ms; + atomic_long read_bytes; + atomic_long written_bytes; + atomic_int last_errno; char current[512]; + char source[512]; + char destination[512]; char verb[16]; char error[256]; + unsigned long source_dev; + unsigned long destination_dev; time_t started_at; time_t ended_at; + long started_ms; }; @@ -204,6 +217,27 @@ static struct job_state g_job = { .lock = PTHREAD_MUTEX_INITIALIZER, }; +struct upload_state { + pthread_mutex_t lock; + long elapsed_ms; + unsigned long bytes; + unsigned long destination_dev; + int error_code; + char path[512]; +}; + +static struct upload_state g_upload = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + + +static long +monotonic_ms(void) { + struct timespec ts; + if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; + return (long)ts.tv_sec * 1000L + (long)(ts.tv_nsec / 1000000L); +} + static void job_set_current(const char *path) { pthread_mutex_lock(&g_job.lock); @@ -242,11 +276,20 @@ job_begin(const char *verb) { atomic_store(&g_job.total_files, 0); atomic_store(&g_job.done_files, 0); atomic_store(&g_job.failed_files, 0); + atomic_store(&g_job.elapsed_ms, 0); + atomic_store(&g_job.read_bytes, 0); + atomic_store(&g_job.written_bytes, 0); + atomic_store(&g_job.last_errno, 0); g_job.current[0] = 0; + g_job.source[0] = 0; + g_job.destination[0] = 0; g_job.error[0] = 0; + g_job.source_dev = 0; + g_job.destination_dev = 0; snprintf(g_job.verb, sizeof(g_job.verb), "%s", verb); g_job.started_at = time(NULL); g_job.ended_at = 0; + g_job.started_ms = monotonic_ms(); pthread_mutex_unlock(&g_job.lock); return 1; } @@ -254,14 +297,28 @@ job_begin(const char *verb) { static void job_end(int rc, const char *err) { + int saved_errno = errno; pthread_mutex_lock(&g_job.lock); g_job.ended_at = time(NULL); + long ended_ms = monotonic_ms(); + atomic_store(&g_job.elapsed_ms, + ended_ms > g_job.started_ms ? ended_ms - g_job.started_ms : 0); + atomic_store(&g_job.last_errno, rc == 0 ? 0 : saved_errno); if(rc != 0 && err && !g_job.error[0]) { snprintf(g_job.error, sizeof(g_job.error), "%s", err); } pthread_mutex_unlock(&g_job.lock); - bfpilot_log("transfer job end verb=%s rc=%d error=%s", - g_job.verb, rc, err ? err : ""); + long elapsed_ms = atomic_load(&g_job.elapsed_ms); + long written = atomic_load(&g_job.written_bytes); + double mbps = elapsed_ms > 0 ? + ((double)written * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0; + bfpilot_log("transfer job end verb=%s rc=%d errno=%d bytes_read=%ld " + "bytes_written=%ld elapsed_ms=%ld average_mbps=%.2f " + "src_dev=%lu dst_dev=%lu current=%s error=%s", + g_job.verb, rc, atomic_load(&g_job.last_errno), + atomic_load(&g_job.read_bytes), written, elapsed_ms, mbps, + g_job.source_dev, g_job.destination_dev, g_job.current, + err ? err : ""); atomic_store(&g_job.busy, 0); } @@ -330,6 +387,7 @@ copy_file(const char *src, const char *dst) { break; } if(r == 0) break; + atomic_fetch_add(&g_job.read_bytes, (long)r); char *p = buf; ssize_t left = r; while(left > 0) { @@ -348,6 +406,7 @@ copy_file(const char *src, const char *dst) { p += w; left -= w; atomic_fetch_add(&g_job.copied_bytes, (long)w); + atomic_fetch_add(&g_job.written_bytes, (long)w); } if(rc != 0) break; } @@ -359,10 +418,6 @@ copy_file(const char *src, const char *dst) { free(buf); close(in); - if(rc == 0 && fsync(out) != 0 && errno != EINVAL) { - job_set_error("sync(%s): %s", dst, strerror(errno)); - rc = -1; - } if(close(out) != 0 && rc == 0) { job_set_error("close(%s): %s", dst, strerror(errno)); rc = -1; @@ -519,6 +574,11 @@ copy_worker(void *arg) { free(a); return NULL; } + pthread_mutex_lock(&g_job.lock); + snprintf(g_job.source, sizeof(g_job.source), "%s", a->src); + snprintf(g_job.destination, sizeof(g_job.destination), "%s", a->dst); + g_job.source_dev = (unsigned long)src_st.st_dev; + pthread_mutex_unlock(&g_job.lock); job_set_current("Scanning source"); size_walker(a->src, &files, &bytes, 0); @@ -552,6 +612,11 @@ copy_worker(void *arg) { return NULL; } } + if(stat(a->dst, &dst_st) == 0) { + pthread_mutex_lock(&g_job.lock); + g_job.destination_dev = (unsigned long)dst_st.st_dev; + pthread_mutex_unlock(&g_job.lock); + } const char *base = path_basename(a->src); if(strlen(a->dst) + strlen(base) + 2 >= sizeof(final_dst)) { @@ -851,18 +916,6 @@ append_fs_place(json_buf_t *b, int *first, const char *path, struct stat st; if(stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) return 0; - char probe[128]; - int n = snprintf(probe, sizeof(probe), "%s/.bfpilot_probe", path); - int writable = 0; - if(n > 0 && (size_t)n < sizeof(probe)) { - int fd = open(probe, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if(fd >= 0) { - writable = 1; - close(fd); - unlink(probe); - } - } - if(!*first && json_append(b, ",") != 0) return -1; *first = 0; @@ -870,7 +923,7 @@ append_fs_place(json_buf_t *b, int *first, const char *path, json_string(b, path) != 0 || json_append(b, ",\"kind\":") != 0 || json_string(b, kind ? kind : "path") != 0 || - json_appendf(b, ",\"writable\":%s}", writable ? "true" : "false") != 0) { + json_append(b, ",\"writable\":null,\"writeProbe\":\"skipped-read-only\"}") != 0) { return -1; } return 0; @@ -1075,15 +1128,26 @@ status_handler(const http_request_t *req) { int tf = atomic_load(&g_job.total_files); int df = atomic_load(&g_job.done_files); int ff = atomic_load(&g_job.failed_files); + long elapsed_ms = atomic_load(&g_job.elapsed_ms); + long read_bytes = atomic_load(&g_job.read_bytes); + long written_bytes = atomic_load(&g_job.written_bytes); + int last_errno = atomic_load(&g_job.last_errno); int cancel = atomic_load(&g_job.cancel); - char verb[16], current[512], err[256]; + char verb[16], current[512], source[512], destination[512], err[256]; + unsigned long source_dev, destination_dev; + long started_ms; time_t started_at; time_t ended_at; pthread_mutex_lock(&g_job.lock); snprintf(verb, sizeof(verb), "%s", g_job.verb); snprintf(current, sizeof(current), "%s", g_job.current); + snprintf(source, sizeof(source), "%s", g_job.source); + snprintf(destination, sizeof(destination), "%s", g_job.destination); snprintf(err, sizeof(err), "%s", g_job.error); + source_dev = g_job.source_dev; + destination_dev = g_job.destination_dev; + started_ms = g_job.started_ms; started_at = g_job.started_at; ended_at = g_job.ended_at; pthread_mutex_unlock(&g_job.lock); @@ -1097,6 +1161,10 @@ status_handler(const http_request_t *req) { long eta = busy && speed > 0 && tb > cb ? (tb - cb + speed - 1) / speed : 0; + if(busy) { + long now_ms = monotonic_ms(); + elapsed_ms = now_ms > started_ms ? now_ms - started_ms : 0; + } json_buf_t b = {0}; if(json_appendf(&b, @@ -1107,14 +1175,53 @@ status_handler(const http_request_t *req) { json_string(&b, current) != 0 || json_append(&b, ",\"error\":") != 0 || json_string(&b, err) != 0 || + json_append(&b, ",\"source\":") != 0 || + json_string(&b, source) != 0 || + json_append(&b, ",\"destination\":") != 0 || + json_string(&b, destination) != 0 || json_appendf(&b, ",\"totalBytes\":%ld,\"copiedBytes\":%ld," "\"totalFiles\":%d,\"doneFiles\":%d,\"failedFiles\":%d," "\"startedAt\":%ld,\"endedAt\":%ld," - "\"elapsedSeconds\":%ld,\"speedBytesPerSec\":%ld," - "\"etaSeconds\":%ld}", + "\"elapsedSeconds\":%ld,\"elapsedMs\":%ld," + "\"bytesRead\":%ld,\"bytesWritten\":%ld," + "\"sourceDev\":%lu,\"destinationDev\":%lu,\"errno\":%d," + "\"speedBytesPerSec\":%ld,\"etaSeconds\":%ld}", tb, cb, tf, df, ff, (long)started_at, (long)ended_at, - elapsed, speed, eta) != 0) { + elapsed, elapsed_ms, read_bytes, written_bytes, source_dev, + destination_dev, last_errno, speed, eta) != 0) { + free(b.data); + return -1; + } + return serve_owned(req, 200, b.data, b.len); +} + +static int +transfer_stats_handler(const http_request_t *req) { + long elapsed_ms; + unsigned long bytes, destination_dev; + int error_code; + char path[512]; + + pthread_mutex_lock(&g_upload.lock); + elapsed_ms = g_upload.elapsed_ms; + bytes = g_upload.bytes; + destination_dev = g_upload.destination_dev; + error_code = g_upload.error_code; + snprintf(path, sizeof(path), "%s", g_upload.path); + pthread_mutex_unlock(&g_upload.lock); + + json_buf_t b = {0}; + double mbps = elapsed_ms > 0 ? + ((double)bytes * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0; + if(json_append(&b, "{\"ok\":true,\"bufferSize\":") != 0 || + json_appendf(&b, "%u,\"lastUpload\":{\"path\":", + (unsigned int)UPLOAD_BUF_SIZE) != 0 || + json_string(&b, path) != 0 || + json_appendf(&b, + ",\"bytes\":%lu,\"elapsedMs\":%ld,\"averageMBps\":%.2f," + "\"destinationDev\":%lu,\"errno\":%d}}", + bytes, elapsed_ms, mbps, destination_dev, error_code) != 0) { free(b.data); return -1; } @@ -1143,6 +1250,7 @@ transfer_request(const http_request_t *req, const char *url) { if(!strcmp(url, "/api/fs/mkdir")) return mkdir_handler(req); if(!strcmp(url, "/api/fs/rename")) return rename_handler(req); if(!strcmp(url, "/api/fs/job/status")) return status_handler(req); + if(!strcmp(url, "/api/fs/transfer/stats")) return transfer_stats_handler(req); if(!strcmp(url, "/api/fs/job/cancel")) return cancel_handler(req); return serve_error(req, 404, "no such endpoint"); } @@ -1261,6 +1369,7 @@ transfer_upload_request(const http_request_t *req, const char *initial_data, char err[200] = {0}; size_t bytes = 0; size_t remaining = content_size; + long started_ms = monotonic_ms(); if(initial_size > remaining) initial_size = remaining; if(initial_size > 0) { @@ -1299,7 +1408,31 @@ transfer_upload_request(const http_request_t *req, const char *initial_data, } free(buf); - close(out); + int close_rc = close(out); + if(close_rc != 0 && !failed) { + failed = 1; + snprintf(err, sizeof(err), "close: %s", strerror(errno)); + } + int saved_errno = failed ? (errno ? errno : EIO) : 0; + long ended_ms = monotonic_ms(); + long elapsed_ms = ended_ms > started_ms ? ended_ms - started_ms : 0; + struct stat upload_st; + unsigned long destination_dev = + stat(dir, &upload_st) == 0 ? (unsigned long)upload_st.st_dev : 0; + pthread_mutex_lock(&g_upload.lock); + g_upload.elapsed_ms = elapsed_ms; + g_upload.bytes = (unsigned long)bytes; + g_upload.destination_dev = destination_dev; + g_upload.error_code = saved_errno; + snprintf(g_upload.path, sizeof(g_upload.path), "%s", final_path); + pthread_mutex_unlock(&g_upload.lock); + double mbps = elapsed_ms > 0 ? + ((double)bytes * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0; + bfpilot_log("upload end rc=%d errno=%d bytes_read=%lu bytes_written=%lu " + "elapsed_ms=%ld average_mbps=%.2f dst_dev=%lu path=%s", + failed ? -1 : 0, saved_errno, (unsigned long)bytes, + (unsigned long)bytes, elapsed_ms, mbps, destination_dev, + final_path); if(failed) { drain_body(req->fd, 0, remaining); @@ -1310,7 +1443,10 @@ transfer_upload_request(const http_request_t *req, const char *initial_data, json_buf_t b = {0}; if(json_append(&b, "{\"ok\":true,\"path\":") != 0 || json_string(&b, final_path) != 0 || - json_appendf(&b, ",\"size\":%lu}", (unsigned long)bytes) != 0) { + json_appendf(&b, + ",\"size\":%lu,\"elapsedMs\":%ld,\"averageMBps\":%.2f," + "\"destinationDev\":%lu}", + (unsigned long)bytes, elapsed_ms, mbps, destination_dev) != 0) { free(b.data); return -1; } diff --git a/src/websrv_lite.c b/src/websrv_lite.c index 751918c..08ae35f 100644 --- a/src/websrv_lite.c +++ b/src/websrv_lite.c @@ -349,6 +349,7 @@ status_request(const http_request_t *req) { "\"pid\":%ld," "\"now\":%ld," "\"services\":[\"web\",\"file-manager\"]," + "\"diagReadOnly\":true," "\"launcherCompiled\":%s," "\"launcherDisabled\":%s," "\"port\":%u}", @@ -412,6 +413,7 @@ diag_request(const http_request_t *req) { "\"can_opendir_ext0_homebrew\":%s," "\"can_write_data_bfpilot\":%s," "\"can_write_user_app\":%s," + "\"write_probe_policy\":\"skipped-read-only\"," "\"launcher_status\":\"%s\"," "\"last_errno\":%d," "\"checkpoint\":\"%s\"," @@ -451,7 +453,7 @@ diag_request(const http_request_t *req) { "\"launcher_final_state\":\"%s\"}," "\"notifications\":{\"mode\":\"optional-raw-debug\"}," "\"routes\":[\"/\",\"/api/status\",\"/api/diag\"," - "\"/fs\",\"/api/fs/*\"]}", + "\"/fs\",\"/api/fs/*\",\"/api/fs/transfer/stats\"]}", VERSION_TAG, BUILD_VERSION, BFPILOT_BUILD_MODE, (long)getpid(), (long)now, bfpilot_diag_uptime(), @@ -463,8 +465,8 @@ diag_request(const http_request_t *req) { json_bool(can_opendir_usb0), json_bool(can_opendir_ext0), json_bool(can_opendir_ext0_homebrew), - json_bool(can_write_data), - json_bool(can_write_user_app), + json_tristate(can_write_data), + json_tristate(can_write_user_app), launcher_status_json, last_errno, checkpoint_json, diff --git a/tests/hello_boot.elf b/tests/hello_boot.elf new file mode 100755 index 0000000..6001fa8 Binary files /dev/null and b/tests/hello_boot.elf differ diff --git a/tests/hello_http.elf b/tests/hello_http.elf new file mode 100755 index 0000000..2e97f1f Binary files /dev/null and b/tests/hello_http.elf differ diff --git a/tests/hello_notify.elf b/tests/hello_notify.elf new file mode 100755 index 0000000..5104a1a Binary files /dev/null and b/tests/hello_notify.elf differ diff --git a/tests/installer_enter_probe.elf b/tests/installer_enter_probe.elf new file mode 100755 index 0000000..b4abac8 Binary files /dev/null and b/tests/installer_enter_probe.elf differ diff --git a/tests/installer_linkonly_appinst.elf b/tests/installer_linkonly_appinst.elf new file mode 100755 index 0000000..4c8a25f Binary files /dev/null and b/tests/installer_linkonly_appinst.elf differ diff --git a/tests/installer_runtime_resolve_appinst.elf b/tests/installer_runtime_resolve_appinst.elf new file mode 100755 index 0000000..8555fac Binary files /dev/null and b/tests/installer_runtime_resolve_appinst.elf differ diff --git a/tests/installer_sysmodule_appinst.c b/tests/installer_sysmodule_appinst.c new file mode 100644 index 0000000..675129f --- /dev/null +++ b/tests/installer_sysmodule_appinst.c @@ -0,0 +1,46 @@ +/* + * BFpilot probe - direct Sysmodule import, then load and resolve AppInstUtil. + */ + +#include + +#include + +#include "boot_marker.h" +#include "notify.h" +#include "probe_common.h" + +#define MARKER "/data/BFpilot/sysmodule_appinst_entered.txt" +#define LOG_PATH "/data/BFpilot/sysmodule_appinst.log" +#define APPINST_MODULE "libSceAppInstUtil.sprx" +#define APPINST_INTERNAL_ID 0x80000014U + +int sceSysmoduleLoadModuleInternal(unsigned int); + + +int +main(void) { + int marker_rc = probe_write_marker(MARKER, "tests/installer_sysmodule_appinst"); + bfpilot_boot_marker("tests/installer_sysmodule_appinst", + "installer-sysmodule-appinst"); + probe_log(LOG_PATH, "entered main marker_rc=%d", marker_rc); + probe_log(LOG_PATH, "about to sceSysmoduleLoadModuleInternal id=0x%08x", + APPINST_INTERNAL_ID); + int load_rc = sceSysmoduleLoadModuleInternal(APPINST_INTERNAL_ID); + probe_log(LOG_PATH, "sceSysmoduleLoadModuleInternal rc=0x%08x", load_rc); + + uint32_t handle = 0; + int handle_rc = kernel_dynlib_handle(-1, APPINST_MODULE, &handle); + probe_log(LOG_PATH, "kernel_dynlib_handle %s rc=0x%08x handle=0x%08x", + APPINST_MODULE, handle_rc, handle); + if(handle_rc == 0) { + intptr_t init = kernel_dynlib_dlsym( + -1, handle, "sceAppInstUtilInitialize"); + intptr_t install_all = kernel_dynlib_dlsym( + -1, handle, "sceAppInstUtilAppInstallAll"); + probe_log(LOG_PATH, "symbols initialize=%p install_all=%p", + (void *)init, (void *)install_all); + } + bfpilot_notify("BFpilot Sysmodule AppInst", "probe complete"); + return handle_rc == 0 ? 0 : 1; +} diff --git a/tests/installer_websrv_pattern.c b/tests/installer_websrv_pattern.c new file mode 100644 index 0000000..a4a64ee --- /dev/null +++ b/tests/installer_websrv_pattern.c @@ -0,0 +1,53 @@ +/* + * BFpilot probe - exact direct-import composition used by ps5 websrv. + */ + +#include +#include + +#include + +#include "boot_marker.h" +#include "notify.h" +#include "probe_common.h" + +#define MARKER "/data/BFpilot/websrv_pattern_entered.txt" +#define LOG_PATH "/data/BFpilot/websrv_pattern.log" +#define APPINST_AUTHID UINT64_C(0x4801000000000013) + +int sceUserServiceInitialize(void *); +void sceUserServiceTerminate(void); +int sceSystemServiceGetAppIdOfRunningBigApp(void); +int sceAppInstUtilInitialize(void); + + +int +main(void) { + int marker_rc = probe_write_marker(MARKER, "tests/installer_websrv_pattern"); + bfpilot_boot_marker("tests/installer_websrv_pattern", + "installer-websrv-pattern"); + probe_log(LOG_PATH, "entered main marker_rc=%d", marker_rc); + + int user_rc = sceUserServiceInitialize(NULL); + probe_log(LOG_PATH, "sceUserServiceInitialize rc=0x%08x", user_rc); + + pid_t pid = getpid(); + uint64_t original_authid = kernel_get_ucred_authid(pid); + int authid_rc = kernel_set_ucred_authid(pid, APPINST_AUTHID); + probe_log(LOG_PATH, "set authid rc=0x%08x original=0x%016llx current=0x%016llx", + authid_rc, (unsigned long long)original_authid, + (unsigned long long)kernel_get_ucred_authid(pid)); + + int appinst_rc = sceAppInstUtilInitialize(); + probe_log(LOG_PATH, "sceAppInstUtilInitialize rc=0x%08x", appinst_rc); + + if(original_authid != 0) { + int restore_rc = kernel_set_ucred_authid(pid, original_authid); + probe_log(LOG_PATH, "restore authid rc=0x%08x current=0x%016llx", + restore_rc, + (unsigned long long)kernel_get_ucred_authid(pid)); + } + sceUserServiceTerminate(); + bfpilot_notify("BFpilot websrv-pattern probe", "probe complete"); + return appinst_rc == 0 ? 0 : 1; +} diff --git a/tests/installer_websrv_pattern.elf b/tests/installer_websrv_pattern.elf new file mode 100755 index 0000000..a4de24d Binary files /dev/null and b/tests/installer_websrv_pattern.elf differ diff --git a/tests/probe_common.h b/tests/probe_common.h index fa67d97..3a58e08 100644 --- a/tests/probe_common.h +++ b/tests/probe_common.h @@ -44,7 +44,7 @@ probe_write_marker(const char *path, const char *payload_name) { if(n < 0) return -EINVAL; if((size_t)n >= sizeof(line)) n = (int)sizeof(line) - 1; - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600); if(fd < 0) return -errno; ssize_t wr = write(fd, line, (size_t)n);