Release BFpilot v0.3

This commit is contained in:
ItsBlurf committed 2026-06-19 01:57:07 +00:00
1 parent bc2c0e284f
commit 43ada0e1ae
42 files changed
+1220 -2326

No files matched your search

+4 -1
View File
@@ -1,11 +1,14 @@
# Build output
gen/
build/
*.o
*.a
*.so
*.elf
__pycache__/
diagnostics/*.json
diagnostics/
output/
screenshot.png
# Local environment / editor noise
.env
+9 -260
View File
@@ -3,7 +3,7 @@
SHELL := bash
ifeq ($(strip $(PS5_PAYLOAD_SDK)),)
ifneq ($(filter ps5-diag,$(MAKECMDGOALS)),ps5-diag)
ifeq ($(filter ps5-diag ps5-smoke,$(MAKECMDGOALS)),)
$(error PS5_PAYLOAD_SDK is required, e.g. export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk)
endif
else
@@ -20,9 +20,8 @@ PS5_HOST ?= ps5
PS5_PORT ?= 9021
PYTHON ?= python3
WEB_PORT ?= 5905
HELLO_HTTP_PORT ?= 5906
VERSION_TAG := bfpilot-v0.2.1
VERSION_TAG := bfpilot-v0.3
BUILD_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LLVM_BINDIR ?= $(shell dirname "$$(command -v clang 2>/dev/null || command -v clang.exe 2>/dev/null || command -v llvm-strip 2>/dev/null || command -v llvm-strip.exe 2>/dev/null || echo clang)" 2>/dev/null || echo .)
@@ -31,18 +30,7 @@ export LLVM_BINDIR
export LLVM_CONFIG
BFPILOT_BIN := bfpilot.elf
DEBUG_BIN := bfpilot-debug.elf
FULL_BIN := bfpilot-full.elf
LAUNCHER_INSTALLER_BIN := bfpilot-launcher-installer.elf
SAFE_LAUNCHER_INSTALLER_BIN := bfpilot-launcher-installer-safe.elf
HELLO_BOOT_BIN := tests/hello_boot.elf
HELLO_HTTP_BIN := tests/hello_http.elf
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
@@ -54,55 +42,16 @@ WEB_SRCS += src/mime.c
WEB_SRCS += src/notify.c
WEB_SRCS += src/transfer.c
LEGACY_FULL_SRCS := $(WEB_SRCS)
LEGACY_FULL_SRCS += src/app_installer.c
LEGACY_FULL_SRCS += src/sce_resolve.c
LAUNCHER_INSTALLER_SRCS := src/launcher_installer_force_main.c
LAUNCHER_INSTALLER_SRCS += src/boot_marker.c
LAUNCHER_INSTALLER_SRCS += src/notify.c
SAFE_LAUNCHER_INSTALLER_SRCS := src/launcher_installer_main.c
SAFE_LAUNCHER_INSTALLER_SRCS += src/boot_marker.c
SAFE_LAUNCHER_INSTALLER_SRCS += src/notify.c
SAFE_LAUNCHER_INSTALLER_SRCS += src/sce_resolve.c
HELLO_BOOT_SRCS := tests/hello_boot.c src/boot_marker.c src/notify.c
HELLO_HTTP_SRCS := tests/hello_http.c src/boot_marker.c src/notify.c
HELLO_NOTIFY_SRCS := tests/hello_notify.c src/boot_marker.c src/notify.c
INSTALLER_ENTER_PROBE_SRCS := tests/installer_enter_probe.c
INSTALLER_ENTER_PROBE_SRCS += src/boot_marker.c
INSTALLER_ENTER_PROBE_SRCS += src/notify.c
INSTALLER_LINKONLY_APPINST_SRCS := tests/installer_linkonly_appinst.c
INSTALLER_LINKONLY_APPINST_SRCS += src/boot_marker.c
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))
APP_ASSETS := assets-app/param.json assets-app/icon0.png
BFPILOT_OBJS := $(patsubst %.c,build/bfpilot/%.o,$(WEB_SRCS) $(GEN_SRCS))
DEBUG_OBJS := $(patsubst %.c,build/debug/%.o,$(WEB_SRCS) $(GEN_SRCS))
LEGACY_FULL_OBJS := $(patsubst %.c,build/legacy-full/%.o,$(LEGACY_FULL_SRCS) $(GEN_SRCS))
LAUNCHER_INSTALLER_OBJS := $(patsubst %.c,build/launcher-installer/%.o,$(LAUNCHER_INSTALLER_SRCS))
SAFE_LAUNCHER_INSTALLER_OBJS := $(patsubst %.c,build/launcher-installer-safe/%.o,$(SAFE_LAUNCHER_INSTALLER_SRCS))
HELLO_BOOT_OBJS := $(patsubst %.c,build/hello-boot/%.o,$(HELLO_BOOT_SRCS))
HELLO_HTTP_OBJS := $(patsubst %.c,build/hello-http/%.o,$(HELLO_HTTP_SRCS))
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
@@ -118,51 +67,11 @@ BFPILOT_CFLAGS += -DBFPILOT_DEBUG_NOTIFICATIONS=0
BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0
BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1
DEBUG_CFLAGS := $(COMMON_CFLAGS)
DEBUG_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot-debug\"
DEBUG_CFLAGS += -DBFPILOT_BUILD_MODE=\"debug\"
DEBUG_CFLAGS += -DBFPILOT_WEB_PORT=$(WEB_PORT)
DEBUG_CFLAGS += -DBFPILOT_DEBUG_NOTIFICATIONS=1
DEBUG_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0
DEBUG_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1
FULL_CFLAGS := $(COMMON_CFLAGS)
FULL_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot-full\"
FULL_CFLAGS += -DBFPILOT_BUILD_MODE=\"full\"
FULL_CFLAGS += -DBFPILOT_WEB_PORT=$(WEB_PORT)
FULL_CFLAGS += -DBFPILOT_DEBUG_NOTIFICATIONS=1
FULL_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=1
FULL_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=0
LAUNCHER_INSTALLER_CFLAGS := $(COMMON_CFLAGS)
LAUNCHER_INSTALLER_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot-launcher-installer\"
LAUNCHER_INSTALLER_CFLAGS += -DBFPILOT_BUILD_MODE=\"launcher-installer-force\"
SAFE_LAUNCHER_INSTALLER_CFLAGS := $(COMMON_CFLAGS)
SAFE_LAUNCHER_INSTALLER_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot-launcher-installer-safe\"
SAFE_LAUNCHER_INSTALLER_CFLAGS += -DBFPILOT_BUILD_MODE=\"launcher-installer-safe\"
HELLO_BOOT_CFLAGS := $(COMMON_CFLAGS)
HELLO_BOOT_CFLAGS += -DBFPILOT_BUILD_MODE=\"hello-boot\"
HELLO_HTTP_CFLAGS := $(COMMON_CFLAGS)
HELLO_HTTP_CFLAGS += -DBFPILOT_BUILD_MODE=\"hello-http\"
HELLO_HTTP_CFLAGS += -DBFPILOT_HELLO_HTTP_PORT=$(HELLO_HTTP_PORT)
HELLO_NOTIFY_CFLAGS := $(COMMON_CFLAGS)
HELLO_NOTIFY_CFLAGS += -DBFPILOT_BUILD_MODE=\"hello-notify\"
INSTALLER_ENTER_PROBE_CFLAGS := $(COMMON_CFLAGS)
INSTALLER_ENTER_PROBE_CFLAGS += -DBFPILOT_BUILD_MODE=\"installer-enter-probe\"
INSTALLER_LINKONLY_APPINST_CFLAGS := $(COMMON_CFLAGS)
INSTALLER_LINKONLY_APPINST_CFLAGS += -DBFPILOT_BUILD_MODE=\"installer-linkonly-appinst\"
INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS := $(COMMON_CFLAGS)
INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS += -DBFPILOT_BUILD_MODE=\"installer-runtime-resolve-appinst\"
COMMON_LDFLAGS := -Wl,--gc-sections -flto
APPINST_LDLIBS := -lSceAppInstUtil
PRIVILEGED_APPINST_LDLIBS := -lkernel_sys -lSceSystemService
PRIVILEGED_APPINST_LDLIBS += -lSceUserService -lSceAppInstUtil
@@ -182,9 +91,6 @@ WINDOWS_LINK_PREFIX := --gc-sections --sysroot="$(PS5_PAYLOAD_SDK_POSIX)"
WINDOWS_LINK_PREFIX += -L"$(PS5_PAYLOAD_SDK_POSIX)/target/lib"
WINDOWS_LINK_PREFIX += -L"$(PS5_PAYLOAD_SDK_POSIX)/target/user/homebrew/lib"
WINDOWS_LINK_PREFIX += -l:crt1.o -l:crti.o -l:crtbegin.o -lc
WINDOWS_BASE_SUFFIX := -lkernel_web -lSceLibcInternal
WINDOWS_BASE_SUFFIX += -lc_stub_weak -lkernel_stub_weak
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
@@ -201,36 +107,12 @@ $(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 installer-websrv-pattern
all: bfpilot launcher-installer
bfpilot: $(BFPILOT_BIN)
debug: $(DEBUG_BIN)
full: $(FULL_BIN)
legacy-full: full
launcher-installer: $(LAUNCHER_INSTALLER_BIN)
launcher-installer-safe: $(SAFE_LAUNCHER_INSTALLER_BIN)
hello-boot: $(HELLO_BOOT_BIN)
hello-http: $(HELLO_HTTP_BIN)
hello-notify: $(HELLO_NOTIFY_BIN)
installer-enter-probe: $(INSTALLER_ENTER_PROBE_BIN)
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 $@)
@@ -241,180 +123,47 @@ build/bfpilot/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(BFPILOT_CFLAGS) -c $< -o $@)
build/debug/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(DEBUG_CFLAGS) -c $< -o $@)
build/legacy-full/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(FULL_CFLAGS) -c $< -o $@)
build/launcher-installer/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) -c $< -o $@)
build/launcher-installer-safe/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(SAFE_LAUNCHER_INSTALLER_CFLAGS) -c $< -o $@)
build/hello-boot/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(HELLO_BOOT_CFLAGS) -c $< -o $@)
build/hello-http/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(HELLO_HTTP_CFLAGS) -c $< -o $@)
build/hello-notify/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(HELLO_NOTIFY_CFLAGS) -c $< -o $@)
build/installer-enter-probe/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(INSTALLER_ENTER_PROBE_CFLAGS) -c $< -o $@)
build/installer-linkonly-appinst/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(INSTALLER_LINKONLY_APPINST_CFLAGS) -c $< -o $@)
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))
$(call run,$(STRIP_CMD) --strip-all $@)
$(call run,$(PYTHON) scripts/scrub_main_payload.py $@)
$(DEBUG_BIN): $(DEBUG_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(DEBUG_OBJS) $(WINDOWS_WEB_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(call run,$(PYTHON) scripts/scrub_main_payload.py $@)
$(FULL_BIN): $(LEGACY_FULL_OBJS) $(APP_ASSETS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(LEGACY_FULL_OBJS) $(WINDOWS_WEB_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(LAUNCHER_INSTALLER_OBJS) $(WINDOWS_APPINST_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(SAFE_LAUNCHER_INSTALLER_BIN): $(SAFE_LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(SAFE_LAUNCHER_INSTALLER_OBJS) $(WINDOWS_BASE_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_BOOT_BIN): $(HELLO_BOOT_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(HELLO_BOOT_OBJS) $(WINDOWS_BASE_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_HTTP_BIN): $(HELLO_HTTP_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(HELLO_HTTP_OBJS) $(WINDOWS_WEB_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_NOTIFY_BIN): $(HELLO_NOTIFY_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(HELLO_NOTIFY_OBJS) $(WINDOWS_BASE_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_ENTER_PROBE_BIN): $(INSTALLER_ENTER_PROBE_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_ENTER_PROBE_OBJS) $(WINDOWS_BASE_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_LINKONLY_APPINST_OBJS) $(WINDOWS_APPINST_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(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))
$(call run,$(STRIP_CMD) --strip-all $@)
$(call run,$(PYTHON) scripts/scrub_main_payload.py $@)
$(DEBUG_BIN): $(DEBUG_OBJS)
$(call run,$(CC_CMD) $(DEBUG_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(DEBUG_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(call run,$(PYTHON) scripts/scrub_main_payload.py $@)
$(FULL_BIN): $(LEGACY_FULL_OBJS) $(APP_ASSETS)
$(call run,$(CC_CMD) $(FULL_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LEGACY_FULL_OBJS))
$(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) $(PRIVILEGED_APPINST_LDLIBS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(SAFE_LAUNCHER_INSTALLER_BIN): $(SAFE_LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS)
$(call run,$(CC_CMD) $(SAFE_LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(SAFE_LAUNCHER_INSTALLER_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_BOOT_BIN): $(HELLO_BOOT_OBJS)
$(call run,$(CC_CMD) $(HELLO_BOOT_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(HELLO_BOOT_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_HTTP_BIN): $(HELLO_HTTP_OBJS)
$(call run,$(CC_CMD) $(HELLO_HTTP_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(HELLO_HTTP_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(HELLO_NOTIFY_BIN): $(HELLO_NOTIFY_OBJS)
$(call run,$(CC_CMD) $(HELLO_NOTIFY_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(HELLO_NOTIFY_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_ENTER_PROBE_BIN): $(INSTALLER_ENTER_PROBE_OBJS)
$(call run,$(CC_CMD) $(INSTALLER_ENTER_PROBE_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_ENTER_PROBE_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS)
$(call run,$(CC_CMD) $(INSTALLER_LINKONLY_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_LINKONLY_APPINST_OBJS) $(APPINST_LDLIBS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(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) $(INSTALLER_WEBSRV_PATTERN_BIN))
$(call run,bash scripts/inspect_imports.sh $(BFPILOT_BIN) $(LAUNCHER_INSTALLER_BIN))
deploy-bfpilot: bfpilot
$(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(BFPILOT_BIN))
deploy-debug: debug
$(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(DEBUG_BIN))
deploy-full: full
$(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(FULL_BIN))
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)
ps5-smoke:
$(call run,$(PYTHON) scripts/ps5_smoke.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) $(INSTALLER_SYSMODULE_APPINST_BIN) $(INSTALLER_WEBSRV_PATTERN_BIN) tests/hello_appinst.elf)
$(call run,rm -rf $(BFPILOT_BIN) $(LAUNCHER_INSTALLER_BIN) build gen)
.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 installer-sysmodule-appinst installer-websrv-pattern inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer ps5-diag
.PHONY: all bfpilot launcher-installer inspect-imports clean deploy-bfpilot deploy-launcher-installer ps5-diag ps5-smoke
+98 -171
View File
@@ -1,136 +1,122 @@
# BFpilot
this is still in BETA phase but its working and does a good enough job.
BFpilot is a small PS5 userland payload that starts a browser-based file
manager on port `5905`.
It is intentionally narrow: no daemons,or anythink that doesnt serve the purpose of a file manager
BFpilot is a lightweight PS5 payload that serves a browser-based file manager at
`http://<PS5_IP>:5905/`.
Current prepared release: `v0.2.1`
The project is split into two payloads:
## Recommended Payload
- `bfpilot.elf` - the main file manager payload.
- `bfpilot-launcher-installer.elf` - an optional home-screen tile installer.
Use `bfpilot.elf` as the main release payload.
The main payload stays deliberately small and compatibility-focused. It does
not import launcher installer libraries, does not install the tile, and can be
used without touching PS5 app installation services.
`bfpilot.elf` is the maximum-compatibility file manager:
## Features
- Starts the HTTP file manager at `http://<PS5_IP>:5905/`.
- Does not install or refresh a PS5 home-screen tile.
- Does not compile or link `src/app_installer.c`.
- Does not link `libSceAppInstUtil`.
- Does not depend on AppInstUtil, SystemService, UserService, or optional
launcher-only Sony libraries.
- Writes boot and runtime diagnostics under `/data/BFpilot`.
- Browse local PS5 paths from a web browser.
- Upload and download files.
- Copy, move, rename, create folders, and delete from the web UI.
- PS5-side copy and move operations with progress and cancellation.
- Transfer timing, throughput, and device diagnostics.
- Clean places sidebar for Root, Homebrew, Mounts, User, Data, mounted drives,
and custom shortcuts.
- Mounted USB/ext drives are shown only when they are actually present.
- Top storage summary for Data and mounted drives.
- Persistent custom shortcuts with add, rename, and remove controls.
- Optional launcher tile that opens `http://127.0.0.1:5905/`.
Use `bfpilot-debug.elf` when you need extra proof that the payload reached
`main()` and progressed through startup. It is still file-manager-only and does
not link AppInstUtil.
## Download
Use `bfpilot-full.elf` when you want the 0.2.0-style integrated launcher
install path. This is the best first installer-capable build for your 11.6 case:
it starts BFpilot and attempts the home-screen tile install inside the same
process, after optional PS5 service initialization. It does not direct-link
AppInstUtil; it dynamically resolves the same AppInst functions used by the
previous full build.
Use the release assets:
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 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.
- `bfpilot.elf` for the file manager.
- `bfpilot-launcher-installer.elf` only if you want the PS5 home-screen tile.
Use `bfpilot-launcher-installer-safe.elf` only for diagnostics. It does not
direct-link AppInstUtil and reports whether AppInstUtil is already reachable
through the safe runtime path.
Run `bfpilot.elf` first. After the web UI is working, run the launcher installer
payload if you want the tile.
## What It Does
## Usage
- Runs a file-manager web UI at `http://<PS5_IP>:5905/`.
- Supports browse, upload, download, copy, move, rename, mkdir, and delete.
- Shows progress for copy, move, delete, and browser uploads.
- Creates missing copy/move target folders automatically.
- Keeps default shortcuts at `/data/homebrew` and `/mnt/usb0`.
- Writes boot markers to `/data/BFpilot/boot.log`.
- Writes runtime logs to `/data/BFpilot/log.txt`.
- Writes fatal-signal diagnostics to `/data/BFpilot/crash.log`.
Inject the file manager payload to your loader, usually on port `9021`:
The optional launcher tile, if installed by the separate installer payload,
opens:
```sh
python3 payload_sender.py 192.168.1.204 9021 bfpilot.elf
```
Open:
```text
http://192.168.1.204:5905/
```
Health checks:
```text
http://192.168.1.204:5905/api/status
http://192.168.1.204:5905/api/diag
http://192.168.1.204:5905/api/fs/places
```
Install or refresh the launcher tile:
```sh
python3 payload_sender.py 192.168.1.204 9021 bfpilot-launcher-installer.elf
```
The tile opens:
```text
http://127.0.0.1:5905/
```
## Runtime Notes
## Build
Inject the ELF to your payload loader on port `9021`.
Set `PS5_PAYLOAD_SDK` to your PS5 payload SDK path:
First test:
```text
send bfpilot.elf to <PS5_IP>:9021
open http://<PS5_IP>:5905/
open http://<PS5_IP>:5905/api/status
open http://<PS5_IP>:5905/api/diag
```sh
export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk
make clean all
make inspect-imports
```
Debug test:
Expected outputs:
```text
send bfpilot-debug.elf to <PS5_IP>:9021
open http://<PS5_IP>:5905/api/diag
check /data/BFpilot/boot.log
check /data/BFpilot/log.txt
bfpilot.elf
bfpilot-launcher-installer.elf
```
Full install test:
```text
send bfpilot-full.elf to <PS5_IP>:9021
open http://<PS5_IP>:5905/
check /data/BFpilot/log.txt
```
Alternate port test while an old instance is still running:
```text
send bfpilot-debug.elf --port 5906 to <PS5_IP>:9021
open http://<PS5_IP>:5906/api/status
```
Launcher installer test:
```text
send bfpilot-launcher-installer.elf to <PS5_IP>:9021
check /data/BFpilot/launcher-installer.log
```
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:
```text
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
`/data/BFpilot/linkonly_appinst_entered.txt`, direct AppInstUtil imports are
failing before `main()` on that loader/firmware.
If `installer_runtime_resolve_appinst.elf` logs
`kernel_dynlib_handle libSceAppInstUtil.sprx rc=0xffffffff`, AppInstUtil is not
reachable through BFpilot's safe runtime path on that firmware/loader. That does
not invalidate the `bfpilot-full.elf` path on firmware where the same runtime
resolution is available, such as the 11.6 case you reported.
`make inspect-imports` verifies that the file manager payload does not contain
launcher/AppInstUtil imports and that the isolated installer contains the
required installer imports.
## Diagnostics
Save these files when reporting failures:
Local read-only diagnostics:
```sh
PS5_IP=192.168.1.204 BF_WEB_PORT=5905 make ps5-diag
```
Smoke test the file APIs using only BFpilot-created files under `/data/test`:
```sh
PS5_IP=192.168.1.204 BF_WEB_PORT=5905 \
BF_ALLOW_PS5_WRITE=1 \
make ps5-smoke
```
Optional benchmark mode writes under `/data/test/bfpilot-bench` by default:
```sh
PS5_IP=192.168.1.204 BF_WEB_PORT=5905 \
BF_ALLOW_PS5_WRITE=1 \
BF_ALLOWED_REMOTE_ROOTS=/data/test/bfpilot-bench \
python3 scripts/ps5_diag.py --bench
```
Runtime logs are stored on the PS5 at:
```text
/data/BFpilot/boot.log
@@ -139,74 +125,15 @@ Save these files when reporting failures:
/data/BFpilot/launcher-installer.log
```
If there is no notification and no `/data/BFpilot/boot.log` entry, the payload
probably failed before `main()` or the loader rejected it. If the boot marker
appears but the server does not start, the failure happened after `main()`.
## Compatibility Notes
## Build
`bfpilot.elf` is file-manager-only by design. It avoids AppInstUtil,
SystemService, UserService, and privileged launcher imports because those can
fail before `main()` on some firmware/loader combinations.
```sh
export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk
make clean all
```
`bfpilot-launcher-installer.elf` is separate and uses the complete launcher
installer dependency set. If launcher installation fails on a firmware, the file
manager payload remains usable.
Individual targets:
```sh
make bfpilot
make debug
make full
make launcher-installer
make hello-boot
make hello-http
make hello-notify
make installer-enter-probe
make installer-linkonly-appinst
make installer-runtime-resolve-appinst
make inspect-imports
```
Outputs:
```text
bfpilot.elf
bfpilot-debug.elf
bfpilot-full.elf
bfpilot-launcher-installer.elf
bfpilot-launcher-installer-safe.elf
tests/hello_boot.elf
tests/hello_http.elf
tests/hello_notify.elf
tests/installer_enter_probe.elf
tests/installer_linkonly_appinst.elf
tests/installer_runtime_resolve_appinst.elf
```
The compatibility check must pass for release builds:
```sh
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 installer/probe targets.
The release installer must also import `kernel_sys`, SystemService, and
UserService; `make inspect-imports` enforces this composition.
## Project Layout
- `src/lite_main.c` starts the file-manager payload, handles clean reload, and
starts the web server.
- `src/boot_marker.c` writes the earliest cross-payload boot marker.
- `src/websrv_lite.c` serves `/`, `/api/status`, `/api/diag`, `/fs`, and
`/api/fs/*`.
- `src/transfer.c` contains upload, copy, move, delete, and long-running job
handling.
- `src/launcher_installer_main.c` is the isolated optional launcher installer.
- `tests/` contains small probe payloads for loader, HTTP, notification, and
AppInstUtil testing.
See [docs/COMPATIBILITY_STRATEGY.md](docs/COMPATIBILITY_STRATEGY.md) for the
firmware compatibility strategy and [docs/FIRMWARE_TESTING.md](docs/FIRMWARE_TESTING.md)
for the test protocol.
More detail is in [docs/COMPATIBILITY_STRATEGY.md](docs/COMPATIBILITY_STRATEGY.md)
and [docs/FIRMWARE_TESTING.md](docs/FIRMWARE_TESTING.md).
+355 -56
View File
@@ -119,16 +119,49 @@
min-width: 0;
}
.title strong {
.title > strong {
display: block;
font-size: 18px;
}
.title span {
.title > span {
color: var(--muted);
font-size: 12px;
}
.storage-summary {
margin-top: 3px;
display: flex;
flex-wrap: wrap;
gap: 8px;
color: var(--muted);
font-size: 11px;
line-height: 1.3;
}
.storage-summary span {
min-width: 0;
max-width: 100%;
border: 0;
background: transparent;
padding: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.storage-summary span + span::before {
content: "· ";
color: #667789;
}
.storage-summary strong {
display: inline;
color: var(--ink);
font-size: inherit;
font-weight: 600;
}
.status {
min-height: 30px;
display: inline-flex;
@@ -158,7 +191,7 @@
margin: 0 auto;
padding: 14px;
display: grid;
grid-template-columns: 150px minmax(0, 1fr) 300px;
grid-template-columns: 190px minmax(0, 1fr) 300px;
gap: 12px;
min-height: 0;
}
@@ -179,13 +212,32 @@
align-self: start;
display: grid;
gap: 8px;
overflow: hidden;
}
.rail-list,
.rail-tools {
display: grid;
gap: 6px;
}
.rail-title {
color: var(--muted);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.rail button {
width: 100%;
min-width: 0;
justify-content: flex-start;
text-align: left;
background: var(--soft);
white-space: normal;
padding: 6px 10px;
min-height: 34px;
}
.rail button.active {
@@ -195,6 +247,24 @@
font-weight: 700;
}
.place-label {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.place-meta {
display: block;
margin-top: 1px;
color: var(--muted);
font-size: 10.5px;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.browser {
min-height: 650px;
display: grid;
@@ -341,14 +411,48 @@
}
.target-buttons,
.target-grid {
.target-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.target-grid {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
.target-grid button {
background: var(--soft);
min-width: 0;
padding: 7px 9px;
overflow-wrap: anywhere;
white-space: normal;
font-family: Consolas, Menlo, monospace;
font-size: 12px;
line-height: 1.25;
}
.target-label {
display: block;
color: var(--ink);
font-family: Arial, Helvetica, sans-serif;
font-size: 13px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.target-meta {
display: block;
margin-top: 2px;
color: var(--muted);
font-family: Consolas, Menlo, monospace;
font-size: 10.5px;
font-weight: 400;
overflow-wrap: anywhere;
}
.detail {
@@ -502,19 +606,21 @@
grid-column: 1 / -1;
}
.storage-summary span {
max-width: calc(100vw - 96px);
}
.shell {
grid-template-columns: 1fr;
padding: 10px;
}
.rail {
display: flex;
overflow-x: auto;
display: grid;
}
.rail button {
width: auto;
flex: 0 0 auto;
width: 100%;
}
.pathbar {
@@ -565,13 +671,24 @@
<div class="title">
<strong>BFpilot</strong>
<span id="build">File Manager</span>
<div class="storage-summary" id="storage-summary">
<span>Storage: loading</span>
</div>
</div>
<div class="status" id="status"><span class="dot"></span><span>Connecting</span></div>
</div>
</header>
<main class="shell">
<nav class="rail" id="places" aria-label="Places"></nav>
<nav class="rail" aria-label="Places">
<div class="rail-title">Places</div>
<div class="rail-list" id="places"></div>
<div class="rail-tools">
<button id="add-shortcut">Add Shortcut</button>
<button id="remove-shortcut">Remove Shortcut</button>
<button id="customize-shortcut">Customize Shortcut</button>
</div>
</nav>
<section class="browser" aria-label="Files">
<div class="pathbar">
@@ -603,15 +720,13 @@
<aside class="target" aria-label="Transfer target">
<h2>Target</h2>
<input id="target-path" class="target-path" spellcheck="false" value="/mnt/usb0">
<input id="target-path" class="target-path" spellcheck="false" value="/data">
<div class="target-buttons">
<button id="copy" class="primary">Copy Here</button>
<button id="move">Move Here</button>
</div>
<div class="target-grid">
<button data-target="/data/homebrew">/data/homebrew</button>
<button data-target="/mnt/usb0">/mnt/usb0</button>
<button data-target="/mnt/ext0">/mnt/ext0</button>
<div class="target-grid" id="target-grid"></div>
<div class="target-actions">
<button id="target-current">Current</button>
<button id="open-target">Open</button>
</div>
@@ -634,23 +749,22 @@
<div id="toast" class="toast"></div>
<script>
const places = [
['Root', '/'],
['Data', '/data'],
['Homebrew', '/data/homebrew'],
['Mounts', '/mnt'],
['User', '/user'],
['System', '/system_data']
];
const state = {
path: '/data',
entries: [],
places: [
{ label: 'Root', path: '/', kind: 'root' },
{ label: 'Homebrew', path: '/data/homebrew', kind: 'homebrew' },
{ label: 'Mounts', path: '/mnt', kind: 'mounts' },
{ label: 'User', path: '/user', kind: 'user' },
{ label: 'Data', path: '/data', kind: 'internal' }
],
selected: new Set(),
detailToken: 0,
uploadBusy: false,
uploadAbort: false,
uploadXhr: null,
placesTimer: 0,
jobTimer: 0,
jobHideTimer: 0
};
@@ -724,6 +838,208 @@
return shortSize(size) + ' (' + Math.round(size).toLocaleString() + ' bytes)';
}
function storageSummary(place) {
if (!place || !place.statvfs || !Number.isFinite(place.totalBytes) || place.totalBytes <= 0) {
return '';
}
const free = Number(place.availableBytes || place.freeBytes || 0);
const pct = Number(place.usedPercent || 0);
return shortSize(free) + ' free of ' + shortSize(place.totalBytes) +
', ' + pct.toFixed(0) + '% used';
}
function placeSubtitle(place) {
return cleanPath(place.path || '/');
}
function placeButton(place, onClick) {
const button = document.createElement('button');
button.dataset.path = cleanPath(place.path);
button.title = cleanPath(place.path);
const label = document.createElement('span');
label.className = 'place-label';
label.textContent = place.label || cleanPath(place.path);
button.appendChild(label);
const meta = document.createElement('span');
meta.className = 'place-meta';
meta.textContent = placeSubtitle(place);
button.appendChild(meta);
button.addEventListener('click', onClick);
return button;
}
function renderStorageSummary() {
const box = el('storage-summary');
box.textContent = '';
const seen = new Set();
const items = [];
for (const place of state.places) {
if (!place.statvfs || Number(place.totalBytes || 0) <= 0) continue;
if (place.kind !== 'internal' && place.kind !== 'usb' && place.kind !== 'ext') continue;
const key = place.kind === 'internal' ? 'internal' : cleanPath(place.path);
if (seen.has(key)) continue;
seen.add(key);
items.push(place);
}
if (!items.length) {
const empty = document.createElement('span');
empty.textContent = 'Storage: waiting for data';
box.appendChild(empty);
return;
}
for (const place of items) {
const item = document.createElement('span');
const title = document.createElement('strong');
title.textContent = place.kind === 'internal'
? 'Data'
: cleanPath(place.path);
item.appendChild(title);
item.appendChild(document.createTextNode(': ' + (storageSummary(place) || 'unknown')));
item.title = cleanPath(place.path);
box.appendChild(item);
}
}
function targetButton(place, path) {
const button = document.createElement('button');
button.dataset.path = path;
if (place.custom) {
const label = document.createElement('span');
label.className = 'target-label';
label.textContent = place.label || path;
button.appendChild(label);
const meta = document.createElement('span');
meta.className = 'target-meta';
meta.textContent = path;
button.appendChild(meta);
} else {
button.textContent = path;
}
button.title = (place.label ? place.label + ' | ' : '') + path;
button.addEventListener('click', () => {
el('target-path').value = path;
updatePlaces();
});
return button;
}
function renderTargetButtons() {
const grid = el('target-grid');
grid.textContent = '';
const seen = new Set();
for (const path of ['/data', '/data/homebrew']) {
const place = state.places.find((item) => cleanPath(item.path) === path) ||
{ label: path, path, kind: 'target' };
grid.appendChild(targetButton(place, path));
seen.add(path);
}
for (const place of state.places) {
if (!place.custom) continue;
const path = cleanPath(place.path);
if (seen.has(path)) continue;
seen.add(path);
grid.appendChild(targetButton(place, path));
}
}
function renderPlaces() {
const root = el('places');
root.textContent = '';
const seen = new Set();
for (const place of state.places) {
const clean = cleanPath(place.path);
if (seen.has(clean)) continue;
seen.add(clean);
root.appendChild(placeButton(place, () => openPath(clean)));
}
renderTargetButtons();
renderStorageSummary();
updatePlaces();
}
async function refreshPlaces(quiet) {
try {
const data = await api('/api/fs/places');
state.places = (data.places || []).map((place) => {
place.path = cleanPath(place.path || '/');
return place;
});
} catch (err) {
state.places = [
{ label: 'Root', path: '/', kind: 'root' },
{ label: 'Homebrew', path: '/data/homebrew', kind: 'homebrew' },
{ label: 'Mounts', path: '/mnt', kind: 'mounts' },
{ label: 'User', path: '/user', kind: 'user' },
{ label: 'Data', path: '/data', kind: 'internal' }
];
if (!quiet) toast('places: ' + err.message, 'err');
}
renderPlaces();
}
function activeShortcut() {
const current = cleanPath(state.path);
let match = state.places.find((place) => place.custom && cleanPath(place.path) === current);
if (!match) {
const target = cleanPath(el('target-path').value || '');
match = state.places.find((place) => place.custom && cleanPath(place.path) === target);
}
return match || null;
}
async function addShortcut() {
const path = cleanPath(prompt('Shortcut path', state.path) || '');
if (!path || path === '/') return;
const label = prompt('Shortcut label', path.split('/').filter(Boolean).pop() || path);
if (label === null) return;
try {
await api('/api/fs/shortcut/add?path=' + encodeURIComponent(path) +
'&label=' + encodeURIComponent(label));
await refreshPlaces(false);
toast('Shortcut saved', 'ok');
} catch (err) {
toast('shortcut: ' + err.message, 'err');
}
}
async function removeShortcut() {
const match = activeShortcut();
if (!match) {
toast('Open a custom shortcut first, or put one in Target', 'err');
return;
}
if (!confirm('Remove shortcut "' + (match.label || match.path) + '"?')) return;
try {
await api('/api/fs/shortcut/delete?path=' + encodeURIComponent(match.path));
await refreshPlaces(false);
toast('Shortcut removed', 'ok');
} catch (err) {
toast('shortcut: ' + err.message, 'err');
}
}
async function customizeShortcut() {
const match = activeShortcut();
if (!match) {
toast('Open a custom shortcut first, or put one in Target', 'err');
return;
}
const label = prompt('Shortcut name', match.label || match.path);
if (label === null) return;
try {
await api('/api/fs/shortcut/rename?path=' + encodeURIComponent(match.path) +
'&label=' + encodeURIComponent(label));
await refreshPlaces(false);
toast('Shortcut updated', 'ok');
} catch (err) {
toast('shortcut: ' + err.message, 'err');
}
}
function shortDuration(seconds) {
if (!Number.isFinite(seconds) || seconds < 0) return 'unknown';
seconds = Math.round(seconds);
@@ -916,6 +1232,13 @@
document.querySelectorAll('#places button').forEach((button) => {
button.classList.toggle('active', cleanPath(button.dataset.path) === state.path);
});
const currentCustom = state.places.some((place) => {
return place.custom && cleanPath(place.path) === state.path;
});
const targetCustom = state.places.some((place) => place.custom &&
cleanPath(place.path) === cleanPath(el('target-path').value || ''));
el('remove-shortcut').disabled = !currentCustom && !targetCustom;
el('customize-shortcut').disabled = !currentCustom && !targetCustom;
}
function updateSelection() {
@@ -1318,38 +1641,15 @@
}
async function boot() {
const placeRoot = el('places');
const extra = [];
try {
const usb = await api('/api/fs/usb');
for (const mount of usb.mounts || []) {
let label = mount.path
.replace('/mnt/', '')
.replace('/data/', '')
.toUpperCase();
if (mount.kind === 'homebrew' && mount.path !== '/data/homebrew') {
label = label.replace('/HOMEBREW', ' HB');
}
extra.push([label, mount.path]);
}
} catch (err) {}
const seenPlaces = new Set();
for (const [label, path] of places.concat(extra)) {
const clean = cleanPath(path);
if (seenPlaces.has(clean)) continue;
seenPlaces.add(clean);
const button = document.createElement('button');
button.textContent = label;
button.dataset.path = clean;
button.addEventListener('click', () => openPath(clean));
placeRoot.appendChild(button);
}
renderPlaces();
await refreshPlaces(false);
el('up').addEventListener('click', () => openPath(parentPath(state.path)));
el('go').addEventListener('click', refresh);
el('refresh').addEventListener('click', refresh);
el('add-shortcut').addEventListener('click', addShortcut);
el('remove-shortcut').addEventListener('click', removeShortcut);
el('customize-shortcut').addEventListener('click', customizeShortcut);
el('path').addEventListener('keydown', (event) => {
if (event.key === 'Enter') refresh();
});
@@ -1366,11 +1666,7 @@
el('open-target').addEventListener('click', () => {
openPath(el('target-path').value);
});
document.querySelectorAll('[data-target]').forEach((button) => {
button.addEventListener('click', () => {
el('target-path').value = button.dataset.target;
});
});
el('target-path').addEventListener('input', updatePlaces);
el('job-cancel').addEventListener('click', () => {
if (state.uploadBusy) {
state.uploadAbort = true;
@@ -1401,6 +1697,9 @@
});
await refresh();
if (!state.placesTimer) {
state.placesTimer = setInterval(() => refreshPlaces(true), 8000);
}
api('/api/status').then((info) => {
const status = el('status');
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
-68
View File
@@ -1,68 +0,0 @@
# 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.
-152
View File
@@ -1,152 +0,0 @@
# 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/<path>`.
- `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.
+58 -135
View File
@@ -1,162 +1,99 @@
# BFpilot Compatibility Strategy
BFpilot now separates the stable file manager from the fragile launcher
installer path.
BFpilot now keeps the stable file manager and the fragile launcher installer as
separate payloads.
## Payload Roles
### `bfpilot.elf`
This is the main release payload. It is firmware-light and should be tested
first on every firmware and loader.
Main release payload. Test this first on every firmware and loader.
- Starts the HTTP file manager on port `5905`.
- Does not install a launcher tile.
- Does not include `src/app_installer.c`.
- Does not link `libSceAppInstUtil`.
- Does not depend on AppInstUtil, SystemService, UserService, or launcher-only
Sony libraries.
- Does not install or refresh a launcher tile.
- Does not include integrated launcher installer source.
- Does not link AppInstUtil.
- Does not depend on SystemService, UserService, AppInstUtil, or `kernel_sys`.
- Writes boot/runtime/crash diagnostics under `/data/BFpilot`.
Broad firmware support comes from avoiding imports that can fail before
`main()` on some loaders or firmware combinations.
### `bfpilot-debug.elf`
This is the same file-manager payload with extra debug notifications/logging.
It still avoids AppInstUtil and launcher installer code.
Use it when `bfpilot.elf` appears to do nothing. If the debug payload writes
`/data/BFpilot/boot.log`, it reached `main()`.
### `bfpilot-full.elf`
This build restores the 0.2.0-style integrated launcher flow for firmware and
loaders where that path worked.
- Starts BFpilot from the same file-manager process.
- Runs optional `sceNetCtlInit` and `sceUserServiceInitialize` resolution/init.
- Dynamically resolves AppInstUtil symbols instead of direct-linking
AppInstUtil.
- Attempts `/user/app/BFPL00001` install/refresh before the web server loop.
- Continues to the web server if launcher install fails.
Use this first when specifically testing home-screen tile install on firmware
11.6, because it is closest to the previous full build that installed there.
`main()` on some loader/firmware combinations.
### `bfpilot-launcher-installer.elf`
This optional payload installs or refreshes `/user/app/BFPL00001`.
Isolated optional payload for installing or refreshing `/user/app/BFPL00001`.
Run it only after `bfpilot.elf` works.
It is intentionally isolated from the file manager:
- Direct-links the working websrv-style installer dependency set:
`kernel_sys`, SystemService, UserService, and AppInstUtil.
- Initializes UserService.
- Temporarily sets authid `0x4801000000000013`.
- Validates/stages embedded `param.json` and `icon0.png`.
- Registers the tile through AppInst.
- Restores the original authid before exit.
- Logs to `/data/BFpilot/launcher-installer.log`.
- 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`.
The tile target is:
If `bfpilot.elf` works but the launcher installer fails, the issue is the
AppInstUtil/launcher path, not the file manager.
```text
http://127.0.0.1:5905/
```
### `bfpilot-launcher-installer-safe.elf`
## Why The Split Exists
This diagnostic payload avoids direct AppInstUtil imports and checks whether
AppInstUtil is already reachable through runtime symbol lookup. It is not the
force install path. Use it after the direct installer gives no notification or
log.
Working PS5 launcher projects such as ps5 websrv, Payload Manager, and
elf-arsenal use the complete AppInst service/privilege composition. Partial
approaches are unreliable:
## Launcher/AppInst Findings
- 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.
The current probes separate three cases:
- `tests/installer_enter_probe.elf` writes a marker with safe imports only.
If this works, the loader can run ordinary BFpilot-shaped ELFs.
- `tests/installer_linkonly_appinst.elf` directly imports AppInstUtil but does
not call it. If this payload does not write its marker, the loader or
firmware rejected the ELF before `main()`.
- `tests/installer_runtime_resolve_appinst.elf` has safe imports only and then
checks whether `libSceAppInstUtil.sprx` is already loaded. If
`kernel_dynlib_handle` returns `0xffffffff`, AppInstUtil is not reachable
through the safe runtime path.
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
`ps5-payload-dev/websrv`, Payload Manager by itsPLK, and elf-arsenal launcher
installers all use the same basic AppInst pattern:
- link `libSceAppInstUtil` directly;
- call `sceAppInstUtilInitialize()` directly;
- resolve only `sceAppInstUtilAppInstallTitleDir` by NID `Wudg3Xe3heE`;
- fall back to `sceAppInstUtilAppInstallAll()`.
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`.
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.
BFpilot keeps that risk out of the file manager. If the installer fails, the
file manager payload remains compatible and usable.
## Boot Marker
Every ELF calls:
Both release ELFs call:
```c
bfpilot_boot_marker(payload_name, build_mode);
```
as the first meaningful line in `main()`.
early in `main()`.
The marker:
- Prints `BFpilot BOOT: <payload_name> <build_mode> entered main`.
- Tries to create `/data/BFpilot`.
- Appends payload name, build mode, PID, and build version to
`/data/BFpilot/boot.log`.
- Attempts a notification titled `BFpilot BOOT`.
- Continues if notification fails.
- prints `BFpilot BOOT: <payload_name> <build_mode> 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
If no notification appears and no `/data/BFpilot/boot.log` entry exists, the
payload probably failed before `main()` or the loader rejected the ELF. If the
boot marker exists but the server does not start, the crash or hang is after
`main()`.
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
Startup is explicit:
`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 notifies `BFpilot reload: old server detected`.
4. BFpilot asks the old server to shut down through `/api/control/shutdown`.
5. If shutdown succeeds, the new server starts.
6. If shutdown fails or a file job is busy, BFpilot notifies `BFpilot reload blocked`,
logs the exact reason to `/data/BFpilot/log.txt`, and exits cleanly.
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.
This avoids the misleading case where reinjection looks like nothing happened.
## Alternate Port
The default web port is compile-time configurable with `BFPILOT_WEB_PORT`.
Runtime port override is supported:
The default web port is `5905`. A runtime alternate port can be passed as:
```text
--port 5906
```
Use this to test a new build while an old BFpilot instance is still running on
`5905`.
## Import Inspection
Run:
@@ -165,29 +102,15 @@ Run:
make inspect-imports
```
The target prints file size, dynamic libraries/imports, undefined symbols, and
SCE-related symbol/string hints for:
The target builds and checks only:
- `bfpilot.elf`
- `bfpilot-debug.elf`
- `bfpilot-full.elf`
- `bfpilot-launcher-installer.elf`
- `bfpilot-launcher-installer-safe.elf`
- `tests/hello_boot.elf`
- `tests/hello_http.elf`
- `tests/hello_notify.elf`
- `tests/installer_enter_probe.elf`
- `tests/installer_linkonly_appinst.elf`
- `tests/installer_runtime_resolve_appinst.elf`
It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain:
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`.
- `SceAppInstUtil`
- `sceAppInst`
- `app_installer`
- `BFPL00001`
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()`.
The supported release build contains only the file manager payload and the
isolated launcher installer.
+114 -122
View File
@@ -1,18 +1,11 @@
# BFpilot Firmware Testing
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.
Use this order on every firmware/loader combination. Do not test the launcher
installer until the file manager is stable.
Remote diagnostics are read-only. Remote write tests require:
```sh
export BF_ALLOW_PS5_WRITE=1
export BF_ALLOWED_REMOTE_ROOTS=/data/BFpilot/bench
```
Never delete remote benchmark artifacts during diagnostics. They have unique
timestamped BFpilot-owned names and can be reviewed before any later cleanup.
Remote diagnostics are read-only unless benchmark mode is explicitly enabled.
Benchmark mode writes only timestamped BFpilot-owned files under
`/data/test/bfpilot-bench` and does not delete remote files.
## 1. Build Locally
@@ -22,156 +15,155 @@ make clean all
make inspect-imports
```
Expected: all ELFs build; import inspection reports compatibility checks passed.
Expected outputs:
Failure meaning: source/toolchain/import boundary failure. Do not inject.
```text
bfpilot.elf
bfpilot-launcher-installer.elf
```
## 2. Stable File Manager
Failure meaning: local toolchain, source, or import-boundary failure. Do not
inject until this passes.
## 2. Inject File Manager
```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 notification/log: `BFpilot BOOT`; a new `bfpilot file-manager` line in
`/data/BFpilot/boot.log`; server startup lines in `/data/BFpilot/log.txt`.
Expected notification/log:
Expected endpoints: `/api/status` and `/api/diag` return HTTP 200;
`/api/status` reports `mode=file-manager`, `port=5905`, and
`diagReadOnly=true`.
- `BFpilot BOOT`
- a `bfpilot file-manager` entry in `/data/BFpilot/boot.log`
- startup lines in `/data/BFpilot/log.txt`
Expected endpoints:
- `/api/status`: HTTP 200, `mode=file-manager`, `port=5905`,
`diagReadOnly=true`
- `/api/fs/places`: HTTP 200, built-in places plus only actually mounted
USB/ext drives
- `/api/diag`: HTTP 200
Failure meaning:
- 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.
- Status works but places/diag fails: HTTP API regression.
Next action: inject the debug payload only if this stage fails.
Next action: save `diagnostics/ps5-diag-*.json` and the BFpilot logs.
## 3. Debug File Manager
## 3. Confirm UI Places And Storage
```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
Open:
```text
http://<PS5_IP>:5905/
```
Expected: `bfpilot-debug debug` boot marker and stronger startup notifications.
Expected:
Failure meaning: use `/data/BFpilot/boot.log`, `/data/BFpilot/log.txt`, and
`/data/BFpilot/crash.log` to locate the last checkpoint.
- left rail shows Root, Homebrew, Mounts, User, Data, and custom shortcuts
- no `/mnt/usb0..7` or `/mnt/ext0..7` placeholders unless the drive is really
mounted
- the top storage summary shows free/total/used percentage for Data and each
mounted drive
- custom shortcuts can be added, renamed, removed, and survive reload because
they are stored at `/data/BFpilot/shortcuts.txt`
Failure meaning:
- Placeholder USB/ext entries: mount detection regression.
- No disk space: `statvfs`/places response regression.
- Shortcuts do not persist: `/data/BFpilot/shortcuts.txt` write/read failure.
## 4. Safe Transfer Benchmark
Run only after the stable file manager passes and writes are explicitly
authorized:
Run only after the file manager passes:
```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
BF_ALLOWED_REMOTE_ROOTS=/data/test/bfpilot-bench \
python3 scripts/ps5_diag.py --bench --logs
```
Expected files: unique timestamped source/copy folders only under
`/data/BFpilot/bench`.
Expected files: timestamped source/copy folders only under
`/data/test/bfpilot-bench`.
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.
Failure meaning:
- 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.
Next action: save the generated diagnostics JSON. Do not benchmark user files.
## 5. Launcher Diagnostic Order
Only continue after stages 1-4 are stable.
### 5.1 Safe Installer
Inject `bfpilot-launcher-installer-safe.elf`.
Expected: boot marker and `/data/BFpilot/launcher-installer.log`.
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.
### 5.2 Entry Probe
Inject `tests/installer_enter_probe.elf`.
Expected file: `/data/BFpilot/installer_enter_probe.txt`.
Failure meaning: loader rejected even the safe installer-shaped probe.
### 5.3 Direct Import Probe
Inject `tests/installer_linkonly_appinst.elf`.
Expected file: `/data/BFpilot/linkonly_appinst_entered.txt`.
Failure meaning: absent marker after the entry probe passed means direct
AppInstUtil imports were rejected before `main()`.
### 5.4 Runtime Resolve Probe
Inject `tests/installer_runtime_resolve_appinst.elf`.
Expected files: `/data/BFpilot/runtime_resolve_entered.txt` and
`/data/BFpilot/runtime_resolve_appinst.log`.
Failure meaning: `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is
not reachable through the safe runtime path.
### 5.5 Exact Websrv-Pattern Probe
Build and inject `tests/installer_websrv_pattern.elf`:
For a broader file API smoke test, run:
```sh
make installer-websrv-pattern
python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" \
tests/installer_websrv_pattern.elf
PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" \
BF_ALLOW_PS5_WRITE=1 \
make ps5-smoke
```
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.
Expected files: a unique `/data/test/bfpilot-smoke-*` folder that is deleted by
the test after upload, download, copy, rename, move, and cleanup checks pass.
Failure meaning: the loader/firmware does not support the complete dependency
and privilege composition used by the isolated installer. Do not attempt tile
registration.
Expected results:
### 5.6 Isolated Installer
Inject `bfpilot-launcher-installer.elf` only when the websrv-pattern probe
passes.
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.
- upload response includes `elapsedMs`, `averageMBps`, `bytesWritten`, and
`destinationDev`
- copy job includes bytes read/written, elapsed ms, MB/s,
source/destination device IDs, return state, and errno
- `/api/fs/transfer/stats` reflects the last upload/copy metrics
Failure meaning:
- 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.
- Slow same-device copy: copy loop or internal storage path.
- Fast same-device copy but slow upload: client/LAN/HTTP receive path.
- Slow only when device IDs differ: USB/cross-filesystem path.
### 5.7 Experimental Full Build
Do not benchmark user files.
Inject `bfpilot-full.elf` last.
## 5. Launcher Installer
Expected: launcher diagnostics plus a functioning web server even if launcher
registration fails.
Run only after stages 1-4 pass:
Failure meaning: integrated dynamic path is incompatible with the environment.
Continue using `bfpilot.elf`; do not merge installer imports into it.
```sh
python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" \
bfpilot-launcher-installer.elf
PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" \
python3 scripts/ps5_diag.py --logs
```
Expected:
- `BFpilot BOOT` marker for `bfpilot-launcher-installer`
- `/data/BFpilot/launcher-installer.log`
- log line confirming title `BFPL00001`
- log line confirming deep link `http://127.0.0.1:5905/`
- UserService init return code
- authid set/restore return codes
- AppInst init return code
- asset/staging results
- final `AppInstallTitleDir` return code
Failure meaning:
- No boot marker/log: loader rejected the installer before `main()`.
- AppInst unavailable/init failed: AppInstUtil context not available on this
firmware/loader.
- Authid setup failed: privilege operation unavailable.
- Asset/staging failed: title file path or embedded metadata problem.
- Install call failed: AppInst rejected the title directory.
- Tile installed but opens the wrong URL: compare `param.json` and the logged
deep link.
## 6. Files To Save
For every failure, save:
```text
/data/BFpilot/boot.log
/data/BFpilot/log.txt
/data/BFpilot/crash.log
/data/BFpilot/launcher-installer.log
diagnostics/ps5-diag-*.json
```
Record the exact command, endpoint, HTTP status or return code, and the newest
log lines around the failure.
+3 -21
View File
@@ -105,38 +105,20 @@ for file in "$@"; do
bfpilot-launcher-installer.elf|./bfpilot-launcher-installer.elf)
continue
;;
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}"
fi
done
for clean_file in bfpilot.elf bfpilot-debug.elf; do
if [[ ! -f "${clean_file}" ]]; then
continue
fi
if "${STRINGS}" "${clean_file}" |
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 ${clean_file}" >&2
echo "inspect-imports: forbidden launcher/AppInst content in bfpilot.elf" >&2
cat "${TMP_DIR}/bfpilot-forbidden-imports.txt" >&2
exit 1
fi
done
if [[ -f tests/installer_linkonly_appinst.elf ]]; then
if ! needed_libraries tests/installer_linkonly_appinst.elf |
grep -q 'libSceAppInstUtil\.sprx'; then
echo
echo "inspect-imports: tests/installer_linkonly_appinst.elf must directly import libSceAppInstUtil.sprx" >&2
exit 1
fi
fi
if [[ -f bfpilot-launcher-installer.elf ]]; then
+7 -4
View File
@@ -48,9 +48,10 @@ def fetch_logs(base: str, out_dir: pathlib.Path, stamp: str) -> dict[str, object
return results
def allowed_remote(path: str) -> bool:
roots = os.environ.get("BF_ALLOWED_REMOTE_ROOTS", "/data/BFpilot/bench")
roots = os.environ.get("BF_ALLOWED_REMOTE_ROOTS", "/data/test/bfpilot-bench")
return any(path == root.rstrip("/") or path.startswith(root.rstrip("/") + "/")
for root in roots.split(",") if root.startswith("/data/"))
for root in roots.split(",")
if root.startswith("/data/test") or root.startswith("/data/bfpilot"))
def run_bench(base: str) -> tuple[bool, object]:
@@ -59,7 +60,7 @@ def run_bench(base: str) -> tuple[bool, object]:
"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"
root = "/data/test/bfpilot-bench"
source_dir = f"{root}/source-{stamp}"
copy_dir = f"{root}/copy-{stamp}"
filename = "bfpilot-bench-8m.bin"
@@ -120,6 +121,7 @@ def main() -> int:
out_dir.mkdir(exist_ok=True)
status_ok, status = fetch_json(base, "/api/status")
places_ok, places = fetch_json(base, "/api/fs/places")
diag_ok = False
if isinstance(status, dict) and status.get("diagReadOnly") is True:
diag_ok, diag = fetch_json(base, "/api/diag")
@@ -140,6 +142,7 @@ def main() -> int:
"baseUrl": base,
"allowPs5Write": allow_write,
"status": status,
"places": places,
"diag": diag,
}
if "--bench" in sys.argv:
@@ -152,7 +155,7 @@ def main() -> int:
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
return 0 if status_ok and places_ok and diag_ok and bench_ok else 1
if __name__ == "__main__":
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Exercise BFpilot file APIs using only BFpilot-created files under /data/test."""
from __future__ import annotations
import datetime as dt
import json
import os
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
PAYLOAD = (b"BFpilot v0.3 smoke test\n" * 2048)[:65536]
def q(value: str) -> str:
return urllib.parse.quote(value, safe="")
def safe_root(path: str) -> bool:
path = path.rstrip("/")
return (
path.startswith("/data/test/bfpilot-smoke-")
or path.startswith("/data/bfpilot/bfpilot-smoke-")
)
def request_json(base: str, path: str, method: str = "GET",
data: bytes | None = None) -> tuple[bool, object]:
url = base + path
try:
req = urllib.request.Request(url, data=data, method=method)
with urllib.request.urlopen(req, timeout=30) as response:
body = response.read()
parsed = json.loads(body)
ok = response.status == 200 and isinstance(parsed, dict) and parsed.get("ok") is not False
print(("PASS" if ok else "FAIL") + f" {method} {url} HTTP {response.status}")
return ok, parsed
except (OSError, ValueError, urllib.error.URLError) as exc:
print(f"FAIL {method} {url}: {exc}", file=sys.stderr)
return False, {"ok": False, "url": url, "error": str(exc)}
def download(base: str, path: str) -> tuple[bool, bytes]:
url = base + "/fs" + "/".join(q(part) for part in path.split("/"))
try:
with urllib.request.urlopen(url, timeout=30) as response:
data = response.read()
ok = data == PAYLOAD
print(("PASS" if ok else "FAIL") + f" GET {url} HTTP {response.status} bytes={len(data)}")
return ok, data
except (OSError, urllib.error.URLError) as exc:
print(f"FAIL GET {url}: {exc}", file=sys.stderr)
return False, b""
def expect_missing(base: str, path: str, timeout: float = 3.0) -> tuple[bool, object]:
url = base + "/api/fs/stat?path=" + q(path)
deadline = time.monotonic() + timeout
last: object = {}
while True:
try:
with urllib.request.urlopen(url, timeout=30) as response:
last = json.loads(response.read())
if time.monotonic() >= deadline:
print(f"FAIL GET {url} HTTP {response.status}: path still exists", file=sys.stderr)
return False, last
except urllib.error.HTTPError as exc:
if exc.code == 404:
print(f"PASS GET {url} HTTP 404 expected missing")
return True, {"ok": True, "missing": True, "http": exc.code}
print(f"FAIL GET {url}: HTTP {exc.code}", file=sys.stderr)
return False, {"ok": False, "http": exc.code}
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)}
time.sleep(0.2)
def wait_job(base: str, label: str) -> tuple[bool, object]:
last: object = {}
for _ in range(120):
ok, job = request_json(base, "/api/fs/job/status")
last = job
if not ok:
return False, job
if isinstance(job, dict) and not job.get("busy"):
err = str(job.get("error") or "")
if err:
print(f"FAIL {label}: job error={err}", file=sys.stderr)
return False, job
print(f"PASS {label}: elapsedMs={job.get('elapsedMs')} averageMBps={job.get('averageMBps')}")
return True, job
time.sleep(0.25)
print(f"FAIL {label}: job timeout", file=sys.stderr)
return False, last
def main() -> int:
if os.environ.get("BF_ALLOW_PS5_WRITE") != "1":
print("FAIL BF_ALLOW_PS5_WRITE must be 1 for smoke tests", file=sys.stderr)
return 2
host = os.environ.get("PS5_IP", "192.168.1.204")
port = os.environ.get("BF_WEB_PORT", "5905")
base = f"http://{host}:{port}"
parent = os.environ.get("BF_TEST_REMOTE_ROOT", "/data/test").rstrip("/")
if parent not in ("/data/test", "/data/bfpilot"):
print("FAIL BF_TEST_REMOTE_ROOT must be /data/test or /data/bfpilot", file=sys.stderr)
return 2
stamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
root = f"{parent}/bfpilot-smoke-{stamp}-{os.getpid()}"
if not safe_root(root):
print(f"FAIL unsafe smoke root {root}", file=sys.stderr)
return 2
source_file = f"{root}/source.bin"
copy_dir = f"{root}/copy"
copy_file = f"{copy_dir}/source.bin"
renamed_file = f"{copy_dir}/renamed.bin"
move_dir = f"{root}/moved"
moved_file = f"{move_dir}/renamed.bin"
checks: list[bool] = []
report: dict[str, object] = {"baseUrl": base, "root": root, "cleanup": "pending"}
for path in (root, copy_dir, move_dir):
ok, body = request_json(base, "/api/fs/mkdir?path=" + q(path))
report[f"mkdir:{path}"] = body
checks.append(ok)
if not ok:
break
if all(checks):
ok, body = request_json(
base,
"/api/fs/upload?path=" + q(root) + "&filename=" + q("source.bin"),
method="POST",
data=PAYLOAD,
)
report["upload"] = body
checks.append(ok)
if all(checks):
ok, body = request_json(base, "/api/fs/stat?path=" + q(source_file))
report["statSource"] = body
checks.append(ok and isinstance(body, dict) and body.get("size") == len(PAYLOAD))
if all(checks):
ok, _ = download(base, source_file)
checks.append(ok)
if all(checks):
ok, body = request_json(base, "/api/fs/copy?src=" + q(source_file) + "&dst=" + q(copy_dir))
report["copyStart"] = body
checks.append(ok)
if ok:
ok, job = wait_job(base, "copy")
report["copyJob"] = job
checks.append(ok)
if all(checks):
ok, _ = download(base, copy_file)
checks.append(ok)
if all(checks):
ok, body = request_json(base, "/api/fs/rename?src=" + q(copy_file) + "&dst=" + q(renamed_file))
report["rename"] = body
checks.append(ok)
if all(checks):
ok, body = request_json(base, "/api/fs/move?src=" + q(renamed_file) + "&dst=" + q(move_dir))
report["moveStart"] = body
checks.append(ok)
if ok:
ok, job = wait_job(base, "move")
report["moveJob"] = job
checks.append(ok)
if all(checks):
ok, _ = download(base, moved_file)
checks.append(ok)
ok, body = request_json(base, "/api/fs/delete?path=" + q(root) + "&recursive=1")
report["cleanupDelete"] = body
report["cleanup"] = "deleted" if ok else "failed"
checks.append(ok)
ok, body = expect_missing(base, root)
report["cleanupStat"] = body
checks.append(ok)
print(json.dumps(report, indent=2, sort_keys=True))
return 0 if all(checks) else 1
if __name__ == "__main__":
raise SystemExit(main())
-373
View File
@@ -1,373 +0,0 @@
/*
* BFpilot - install the PS5 home-screen web launcher tile.
*/
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "app_installer.h"
#include "diag.h"
#include "notify.h"
#include "sce_resolve.h"
#include "websrv.h"
#define BFPILOT_APP_TITLE_ID "BFPL00001"
#define BFPILOT_INVALID_APP_TITLE_ID "BFPILOT01"
#define BFPILOT_LEGACY_APP_TITLE_ID "BSFM00001"
#define BFPILOT_OLD_LEGACY_APP_TITLE_ID "BS5F00001"
#define BFPILOT_APP_ROOT "/user/app"
#define BFPILOT_APP_PARENT BFPILOT_APP_ROOT "/"
#define BFPILOT_DATA_DIR "/data/BFpilot"
#define BFPILOT_MARKER_PATH BFPILOT_DATA_DIR "/launcher.ok"
#define BFPILOT_INSTALL_MARKER "bfpilot-launcher-v2\n"
#define BFPILOT_APPINST_MODULE "libSceAppInstUtil.sprx"
#define BFPILOT_NID_INSTALL_TITLE_DIR "Wudg3Xe3heE"
#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 (*appinst_initialize_fn)(void);
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;
app_install_title_dir_fn install_title_dir;
appinst_install_all_fn install_all;
} appinst_api_t;
static const uint8_t g_install_marker[] = BFPILOT_INSTALL_MARKER;
static bfpilot_launcher_diag_t g_launcher_diag = {
.launcher_enabled = 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 = BFPILOT_DIAG_SKIPPED,
.launcher_final_state = "not-attempted",
};
static void
publish_launcher_diag(void) {
websrv_set_launcher_diag(&g_launcher_diag);
}
static void
reset_launcher_diag(void) {
bfpilot_launcher_diag_t diag = {
.launcher_enabled = 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 = BFPILOT_DIAG_SKIPPED,
.launcher_final_state = "resolving-appinst",
};
g_launcher_diag = diag;
publish_launcher_diag();
}
static int
resolve_optional(void **out, const char *symbol, const char *fallback_symbol) {
int rc = sce_resolve_symbol(BFPILOT_APPINST_MODULE, symbol, out);
if(rc != 0 && fallback_symbol) {
rc = sce_resolve_symbol(BFPILOT_APPINST_MODULE, fallback_symbol, out);
}
return rc;
}
static void
resolve_appinst(appinst_api_t *api) {
memset(api, 0, sizeof(*api));
int rc = resolve_optional((void **)&api->initialize,
"sceAppInstUtilInitialize", NULL);
bfpilot_log("launcher resolve Initialize %s rc=0x%08x",
api->initialize ? "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);
rc = resolve_optional((void **)&api->install_all,
"sceAppInstUtilAppInstallAll", NULL);
g_launcher_diag.install_all_resolved = api->install_all != NULL;
bfpilot_log("launcher resolve AppInstallAll %s rc=0x%08x",
api->install_all ? "ok" : "missing", rc);
publish_launcher_diag();
}
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;
bfpilot_log("launcher install AppInstallTitleDir failed rc=0x%08x", err);
} else {
bfpilot_log("launcher install AppInstallTitleDir not resolved");
}
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);
return err;
}
g_launcher_diag.launcher_install_rc = -2;
publish_launcher_diag();
bfpilot_log("launcher install AppInstallAll not resolved");
return -2;
}
static int
write_file(const char *path, const uint8_t *data, size_t size) {
FILE *file = fopen(path, "wb");
if(!file) return -1;
size_t written = fwrite(data, 1, size, file);
int close_rc = fclose(file);
return (written == size && close_rc == 0) ? 0 : -1;
}
static int
file_differs(const char *path, const uint8_t *expected, size_t expected_size) {
struct stat st;
if(stat(path, &st) != 0) return 1;
if(st.st_size < 0 || (size_t)st.st_size != expected_size) return 1;
FILE *file = fopen(path, "rb");
if(!file) return 1;
uint8_t *actual = malloc(expected_size ? expected_size : 1);
if(!actual) {
fclose(file);
return 1;
}
size_t read = fread(actual, 1, expected_size, file);
fclose(file);
int differs = read != expected_size || memcmp(actual, expected, expected_size);
free(actual);
return differs;
}
static int
mkdir_if_needed(const char *path) {
if(mkdir(path, 0755) == 0) return 0;
return errno == EEXIST ? 0 : -1;
}
static int
ensure_data_dir(void) {
if(mkdir_if_needed("/data") != 0) return -1;
return mkdir_if_needed(BFPILOT_DATA_DIR);
}
int
bfpilot_install_app_if_needed(void) {
appinst_api_t api;
char app_dir[256];
char sce_sys_dir[256];
char param_path[256];
char icon_path[256];
char msg[128];
struct stat st;
reset_launcher_diag();
resolve_appinst(&api);
g_launcher_diag.user_app_writable = -1;
publish_launcher_diag();
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");
return -1;
}
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");
return -1;
}
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);
int app_exists = stat(app_dir, &st) == 0;
int assets_changed = !app_exists ||
file_differs(param_path, bfpilot_param_json,
bfpilot_param_json_size) ||
file_differs(icon_path, bfpilot_icon0_png,
bfpilot_icon0_png_size) ||
file_differs(BFPILOT_MARKER_PATH, g_install_marker,
sizeof(g_install_marker) - 1);
if(app_exists && assets_changed) {
bfpilot_notify("BFpilot app", "Updating PS5 home-screen launcher");
} else if(app_exists) {
bfpilot_notify("BFpilot app", "Refreshing PS5 home-screen launcher");
} else {
bfpilot_notify("BFpilot app", "Installing PS5 home-screen launcher");
}
int err = api.initialize();
g_launcher_diag.appinst_init_rc = err;
publish_launcher_diag();
if(err) {
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;
}
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);
return -1;
}
if(write_file(param_path, bfpilot_param_json, bfpilot_param_json_size) != 0) {
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;
}
if(write_file(icon_path, bfpilot_icon0_png, bfpilot_icon0_png_size) != 0) {
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;
}
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);
return -1;
}
if(ensure_data_dir() != 0) {
bfpilot_log("launcher install warning failed creating %s errno=%d",
BFPILOT_DATA_DIR, errno);
} else if(write_file(BFPILOT_MARKER_PATH, g_install_marker,
sizeof(g_install_marker) - 1) != 0) {
bfpilot_log("launcher install warning failed writing %s errno=%d",
BFPILOT_MARKER_PATH, errno);
}
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;
}
-10
View File
@@ -1,10 +0,0 @@
/*
* BFpilot - PS5 home-screen launcher installer.
*
* Returns 1 when the launcher was installed/updated, 0 when it was already
* current, and -1 on failure.
*/
#pragma once
int bfpilot_install_app_if_needed(void);
+2 -2
View File
@@ -3,8 +3,8 @@
*
* This intentionally uses the same direct AppInstUtil pattern as websrv,
* Payload Manager, and ftpsrv. If a loader rejects AppInstUtil imports, this
* ELF can fail before main; use bfpilot-launcher-installer-safe.elf to prove
* that case.
* ELF can fail before main; in that case there will be no boot marker in
* /data/BFpilot/boot.log and no launcher-installer.log entry.
*/
#include <errno.h>
-296
View File
@@ -1,296 +0,0 @@
/*
* BFpilot - isolated optional launcher tile installer.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <ps5/kernel.h>
#include "boot_marker.h"
#include "notify.h"
#include "sce_resolve.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"
#define BFPILOT_APPINST_MODULE "libSceAppInstUtil.sprx"
#define BFPILOT_NID_INSTALL_TITLE_DIR "Wudg3Xe3heE"
#define BFPILOT_DIAG_SKIPPED (-2147483000)
#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 (*appinst_init_fn)(void);
typedef int (*app_install_title_dir_fn)(const char *, const char *, void *);
typedef int (*app_install_all_fn)(void *);
static int
mkdir_if_needed(const char *path) {
if(mkdir(path, 0755) == 0) return 0;
return errno == EEXIST ? 0 : -errno;
}
static void
ensure_log_dir(void) {
(void)mkdir_if_needed(BFPILOT_DATA_ROOT);
(void)mkdir_if_needed(BFPILOT_DATA_DIR);
}
static void
installer_log(const char *fmt, ...) {
char body[1024];
char line[1400];
va_list ap;
va_start(ap, fmt);
int body_n = vsnprintf(body, sizeof(body), fmt ? fmt : "", ap);
va_end(ap);
if(body_n < 0) {
snprintf(body, sizeof(body), "log format failed");
} else if((size_t)body_n >= sizeof(body)) {
body[sizeof(body) - 1] = 0;
}
int n = snprintf(line, sizeof(line), "%ld pid=%ld %s\n",
(long)time(NULL), (long)getpid(), body);
if(n < 0) return;
if((size_t)n >= sizeof(line)) n = (int)sizeof(line) - 1;
fputs(line, stdout);
fflush(stdout);
ensure_log_dir();
int fd = open(BFPILOT_LAUNCHER_LOG,
O_WRONLY | O_CREAT | O_APPEND, 0600);
if(fd >= 0) {
(void)write(fd, line, (size_t)n);
close(fd);
}
}
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;
}
int
main(void) {
bfpilot_boot_marker("bfpilot-launcher-installer", BFPILOT_BUILD_MODE);
char app_dir[256];
char sce_sys_dir[256];
char param_path[256];
char icon_path[256];
uint32_t appinst_handle = 0;
appinst_init_fn appinst_init = NULL;
app_install_title_dir_fn install_title_dir = NULL;
app_install_all_fn install_all = NULL;
int appinst_handle_rc;
int title_dir_rc = BFPILOT_DIAG_SKIPPED;
int install_all_rc = BFPILOT_DIAG_SKIPPED;
int appinst_init_rc = BFPILOT_DIAG_SKIPPED;
int final_rc = -1;
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);
installer_log("kernel_dynlib_handle %s rc=0x%08x handle=0x%08x",
BFPILOT_APPINST_MODULE, appinst_handle_rc, appinst_handle);
if(appinst_handle_rc == 0) {
installer_log("about to kernel_dynlib_dlsym sceAppInstUtilInitialize");
appinst_init = (appinst_init_fn)kernel_dynlib_dlsym(
-1, appinst_handle, "sceAppInstUtilInitialize");
installer_log("kernel_dynlib_dlsym sceAppInstUtilInitialize addr=%p",
(void *)appinst_init);
installer_log("about to kernel_dynlib_dlsym sceAppInstUtilAppInstallAll");
install_all = (app_install_all_fn)kernel_dynlib_dlsym(
-1, appinst_handle, "sceAppInstUtilAppInstallAll");
installer_log("kernel_dynlib_dlsym sceAppInstUtilAppInstallAll addr=%p",
(void *)install_all);
installer_log("about to kernel_dynlib_resolve AppInstallTitleDir nid=%s",
BFPILOT_NID_INSTALL_TITLE_DIR);
install_title_dir = (app_install_title_dir_fn)kernel_dynlib_resolve(
-1, appinst_handle, BFPILOT_NID_INSTALL_TITLE_DIR);
installer_log("kernel_dynlib_resolve AppInstallTitleDir addr=%p",
(void *)install_title_dir);
if(!install_title_dir) {
installer_log("about to kernel_dynlib_dlsym "
"sceAppInstUtilAppInstallTitleDir");
install_title_dir = (app_install_title_dir_fn)kernel_dynlib_dlsym(
-1, appinst_handle, "sceAppInstUtilAppInstallTitleDir");
installer_log("kernel_dynlib_dlsym sceAppInstUtilAppInstallTitleDir "
"addr=%p", (void *)install_title_dir);
}
} else {
installer_log("AppInstUtil unavailable without direct import; skipping "
"launcher install safely");
}
if(appinst_init) {
appinst_init_rc = appinst_init();
}
installer_log("AppInst init return code=0x%08x", appinst_init_rc);
int appinst_symbols_available =
appinst_handle_rc == 0 && appinst_init && (install_title_dir || install_all);
int can_stage_launcher = appinst_symbols_available && appinst_init_rc == 0;
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);
int mkdir_app_rc = BFPILOT_DIAG_SKIPPED;
int mkdir_sce_sys_rc = BFPILOT_DIAG_SKIPPED;
int write_param_rc = BFPILOT_DIAG_SKIPPED;
int write_icon_rc = BFPILOT_DIAG_SKIPPED;
if(can_stage_launcher) {
errno = 0;
mkdir_app_rc = mkdir_if_needed(app_dir);
installer_log("%s mkdir return code=%d errno=%d",
app_dir, mkdir_app_rc, errno);
errno = 0;
mkdir_sce_sys_rc = mkdir_if_needed(sce_sys_dir);
installer_log("%s mkdir return code=%d errno=%d",
sce_sys_dir, mkdir_sce_sys_rc, errno);
errno = 0;
write_param_rc = write_file(param_path, bfpilot_param_json,
bfpilot_param_json_size);
installer_log("%s write return code=%d errno=%d",
param_path, write_param_rc, errno);
errno = 0;
write_icon_rc = write_file(icon_path, bfpilot_icon0_png,
bfpilot_icon0_png_size);
installer_log("%s write return code=%d errno=%d",
icon_path, write_icon_rc, errno);
} else {
const char *reason = !appinst_symbols_available ?
"AppInstUtil not safely available" : "AppInst init failed";
installer_log("launcher staging skipped: %s", reason);
installer_log("%s mkdir return code=skipped", app_dir);
installer_log("%s mkdir return code=skipped", sce_sys_dir);
installer_log("%s write return code=skipped", param_path);
installer_log("%s write return code=skipped", icon_path);
}
installer_log("AppInstallTitleDir resolved=%s nid=%s",
install_title_dir ? "yes" : "no",
BFPILOT_NID_INSTALL_TITLE_DIR);
if(can_stage_launcher && mkdir_app_rc == 0 && mkdir_sce_sys_rc == 0 &&
write_param_rc == 0 && write_icon_rc == 0) {
if(install_title_dir) {
title_dir_rc = install_title_dir(BFPILOT_APP_TITLE_ID,
BFPILOT_APP_PARENT, NULL);
installer_log("AppInstallTitleDir return code=0x%08x", title_dir_rc);
} else {
installer_log("AppInstallTitleDir return code=skipped");
}
if(title_dir_rc != 0) {
if(install_all) {
install_all_rc = install_all(NULL);
installer_log("AppInstallAll fallback return code=0x%08x",
install_all_rc);
} else {
installer_log("AppInstallAll fallback return code=skipped");
}
} else {
installer_log("AppInstallAll fallback return code=skipped");
}
} else {
installer_log("AppInstallTitleDir return code=skipped");
installer_log("AppInstallAll fallback return code=skipped");
}
if(title_dir_rc == 0 || install_all_rc == 0) {
final_rc = 0;
final_state = "installed";
} else if(appinst_symbols_available) {
final_state = "failed_nonfatal";
}
installer_log("final result=%s final_rc=%d appinst_init_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_state, final_rc,
appinst_init_rc, mkdir_app_rc, mkdir_sce_sys_rc,
write_param_rc, write_icon_rc, title_dir_rc, install_all_rc);
if(final_rc == 0) {
bfpilot_notify("BFpilot launcher installer", "installed or refreshed");
return 0;
}
if(appinst_symbols_available) {
bfpilot_notify("BFpilot launcher installer",
"failed; file manager unaffected");
return 1;
}
bfpilot_notify("BFpilot launcher installer",
"unsupported on this loader; use web UI");
return 2;
}
+4 -117
View File
@@ -27,11 +27,6 @@
#include "version.h"
#include "websrv.h"
#if BFPILOT_ENABLE_LAUNCHER
#include "app_installer.h"
#include "sce_resolve.h"
#endif
#define BFPILOT_RELOAD_TOKEN "bs5fm-local-reload"
#ifndef BFPILOT_WEB_PORT
@@ -46,14 +41,6 @@
#define BFPILOT_DEBUG_NOTIFICATIONS 0
#endif
#if BFPILOT_ENABLE_LAUNCHER
typedef int (*sce_netctl_init_fn)(void);
typedef int (*sce_user_service_initialize_fn)(void *);
#define BFPILOT_NETCTL_MODULE "libSceNetCtl.sprx"
#define BFPILOT_USER_SERVICE_MODULE "libSceUserService.sprx"
#endif
static void
detect_lan_ip(char *out, size_t out_size) {
struct ifaddrs *ifaddr = NULL;
@@ -125,71 +112,6 @@ runtime_port_requested(int argc, char **argv) {
}
#if BFPILOT_ENABLE_LAUNCHER
static void
init_ps5_services(void) {
int user_prio = 256;
int netctl_rc = BFPILOT_DIAG_SKIPPED;
int user_service_rc = BFPILOT_DIAG_SKIPPED;
sce_netctl_init_fn sce_netctl_init = NULL;
sce_user_service_initialize_fn sce_user_service_initialize = NULL;
int resolve_rc = sce_resolve_symbol(BFPILOT_NETCTL_MODULE,
"sceNetCtlInit",
(void **)&sce_netctl_init);
bfpilot_log("sce resolve %s:sceNetCtlInit %s rc=0x%08x",
BFPILOT_NETCTL_MODULE,
sce_netctl_init ? "ok" : "missing", resolve_rc);
if(sce_netctl_init) {
netctl_rc = sce_netctl_init();
bfpilot_log("sceNetCtlInit rc=0x%08x", netctl_rc);
} else {
bfpilot_log("sceNetCtlInit rc=skipped");
}
resolve_rc = sce_resolve_symbol(BFPILOT_USER_SERVICE_MODULE,
"sceUserServiceInitialize",
(void **)&sce_user_service_initialize);
bfpilot_log("sce resolve %s:sceUserServiceInitialize %s rc=0x%08x",
BFPILOT_USER_SERVICE_MODULE,
sce_user_service_initialize ? "ok" : "missing", resolve_rc);
if(sce_user_service_initialize) {
user_service_rc = sce_user_service_initialize(&user_prio);
bfpilot_log("sceUserServiceInitialize rc=0x%08x", user_service_rc);
} else {
bfpilot_log("sceUserServiceInitialize rc=skipped");
}
bfpilot_diag_set_service_rcs(netctl_rc, user_service_rc);
}
#endif
static int
launcher_disabled_requested(int argc, char **argv) {
#if !BFPILOT_ENABLE_LAUNCHER
(void)argc;
(void)argv;
return 1;
#else
const char *env_value = getenv("BFPILOT_NO_LAUNCHER");
int env_enabled = env_value &&
(!strcmp(env_value, "1") ||
!strcasecmp(env_value, "true") ||
!strcasecmp(env_value, "yes") ||
!strcasecmp(env_value, "on"));
if(env_enabled) return 1;
for(int i = 1; i < argc; i++) {
if(argv && argv[i] && !strcmp(argv[i], "--no-launcher")) {
return 1;
}
}
return 0;
#endif
}
static int
local_http_get(unsigned short port, const char *path, char *out,
size_t out_size) {
@@ -384,10 +306,9 @@ main(int argc, char **argv) {
detect_lan_ip(ready.ip, sizeof(ready.ip));
ready.port = runtime_port_requested(argc, argv);
websrv_set_runtime_port(ready.port);
int launcher_disabled = launcher_disabled_requested(argc, argv);
websrv_set_runtime_diag(launcher_disabled);
websrv_set_runtime_diag(1);
bfpilot_log("runtime web_port=%u", (unsigned int)ready.port);
bfpilot_log("runtime launcher_disabled=%d", launcher_disabled);
bfpilot_log("runtime launcher=separate_payload");
puts(".----------------------------------------------.");
puts("| BFpilot |");
@@ -397,13 +318,7 @@ main(int argc, char **argv) {
puts(" active: standalone web file manager");
printf(" mode: %s\n", BFPILOT_BUILD_MODE);
puts(" scope: browse, upload, download, copy, move, delete, rename, mkdir");
if(BFPILOT_ENABLE_LAUNCHER && !launcher_disabled) {
puts(" ps5 app: BFpilot opens http://127.0.0.1:5905/");
} else if(BFPILOT_ENABLE_LAUNCHER) {
puts(" ps5 app: launcher disabled");
} else {
puts(" ps5 app: launcher installer is a separate optional payload");
}
puts(" ps5 app: launcher installer is a separate optional payload");
printf(" web ui: http://%s:%u/\n", ready.ip, (unsigned int)ready.port);
puts(" inject/deploy port: 9021");
puts("");
@@ -411,7 +326,7 @@ main(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
#if BFPILOT_DEBUG_NOTIFICATIONS || BFPILOT_ENABLE_LAUNCHER
#if BFPILOT_DEBUG_NOTIFICATIONS
int notification_rc = bfpilot_notify_test();
bfpilot_diag_set_notification_rc(notification_rc);
bfpilot_log("notification test rc=0x%08x", notification_rc);
@@ -420,39 +335,11 @@ main(int argc, char **argv) {
bfpilot_log("notification test rc=skipped");
#endif
#if BFPILOT_ENABLE_LAUNCHER
if(!launcher_disabled) {
bfpilot_checkpoint("launcher started");
bfpilot_diag_set_launcher_status("started", 0);
bfpilot_log("launcher started");
init_ps5_services();
int app_install_status = bfpilot_install_app_if_needed();
if(app_install_status >= 0) {
bfpilot_diag_set_launcher_status("ready", app_install_status);
bfpilot_log("launcher ready rc=%d", app_install_status);
puts(" ps5 app: ready");
} else {
bfpilot_diag_set_launcher_status("failed", app_install_status);
bfpilot_log("launcher failed rc=%d, continuing web server",
app_install_status);
puts(" ps5 app: install failed, continuing web server");
}
} else {
bfpilot_diag_set_service_rcs(BFPILOT_DIAG_SKIPPED, BFPILOT_DIAG_SKIPPED);
bfpilot_diag_set_launcher_status("skipped", BFPILOT_DIAG_SKIPPED);
bfpilot_log("sceNetCtlInit rc=skipped");
bfpilot_log("sceUserServiceInitialize rc=skipped");
bfpilot_log("launcher skipped: runtime flag");
puts(" ps5 app: skipped by runtime flag");
}
#else
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");
#endif
bfpilot_checkpoint("handoff check");
debug_notify("BFpilot debug", "handoff check");
-28
View File
@@ -1,28 +0,0 @@
/*
* BFpilot - optional Sony module symbol resolution.
*/
#include <stddef.h>
#include <stdint.h>
#include <ps5/kernel.h>
#include "sce_resolve.h"
int
sce_resolve_symbol(const char *module, const char *symbol, void **out) {
uint32_t handle = 0;
void *addr = NULL;
if(out) *out = NULL;
if(!module || !symbol || !out) return -1;
int rc = kernel_dynlib_handle(-1, module, &handle);
if(rc != 0) return rc;
addr = (void *)kernel_dynlib_resolve(-1, handle, symbol);
if(!addr) return -2;
*out = addr;
return 0;
}
-7
View File
@@ -1,7 +0,0 @@
/*
* BFpilot - optional Sony module symbol resolution.
*/
#pragma once
int sce_resolve_symbol(const char *module, const char *symbol, void **out);
+359 -28
View File
@@ -15,6 +15,7 @@
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -30,6 +31,11 @@
#define COPY_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE
#define UPLOAD_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE
#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
typedef struct json_buf {
@@ -164,6 +170,13 @@ mkdirs(const char *path) {
}
static void
ensure_bfpilot_dir(void) {
(void)mkdir(BFPILOT_DATA_ROOT, 0755);
(void)mkdir(BFPILOT_DATA_DIR, 0755);
}
static void
join_path(char *out, size_t out_sz, const char *dir, const char *name) {
size_t n = strlen(dir);
@@ -179,6 +192,13 @@ path_basename(const char *path) {
}
static const char *
safe_basename_label(const char *path) {
const char *base = path_basename(path);
return base && base[0] ? base : path;
}
static int
path_is_same_or_child(const char *base, const char *path) {
size_t n = strlen(base);
@@ -813,6 +833,12 @@ list_request(const http_request_t *req) {
}
static void
fs_totals(const char *path, int *ok, unsigned long long *total,
unsigned long long *free_bytes, unsigned long long *avail,
unsigned long long *used, double *used_pct);
static int
stat_request(const http_request_t *req) {
char path[1024];
@@ -824,11 +850,21 @@ stat_request(const http_request_t *req) {
if(lstat(path, &st) != 0) return serve_error(req, 404, strerror(errno));
json_buf_t b = {0};
int fs_ok = 0;
unsigned long long total = 0, free_bytes = 0, avail = 0, used = 0;
double used_pct = 0.0;
fs_totals(path, &fs_ok, &total, &free_bytes, &avail, &used, &used_pct);
if(json_append(&b, "{\"ok\":true,\"path\":") != 0 ||
json_string(&b, path) != 0 ||
json_appendf(&b, ",\"dir\":%s,\"size\":%ld,\"mtime\":%ld}",
json_appendf(&b,
",\"dir\":%s,\"size\":%ld,\"mtime\":%ld,\"dev\":%lu,"
"\"statvfs\":%s,\"totalBytes\":%llu,\"freeBytes\":%llu,"
"\"availableBytes\":%llu,\"usedBytes\":%llu,"
"\"usedPercent\":%.2f}",
S_ISDIR(st.st_mode) ? "true" : "false",
(long)st.st_size, (long)st.st_mtime) != 0) {
(long)st.st_size, (long)st.st_mtime,
(unsigned long)st.st_dev, fs_ok ? "true" : "false",
total, free_bytes, avail, used, used_pct) != 0) {
free(b.data);
return -1;
}
@@ -910,20 +946,126 @@ du_request(const http_request_t *req) {
}
typedef struct shortcut_entry {
char label[64];
char path[512];
} shortcut_entry_t;
static void
sanitize_shortcut_label(char *out, size_t out_size, const char *label,
const char *path) {
const char *src = label && label[0] ? label : safe_basename_label(path);
size_t pos = 0;
if(out_size == 0) return;
for(const unsigned char *p = (const unsigned char *)src;
*p && pos + 1 < out_size; p++) {
if(*p < 0x20 || *p == '\t') {
if(pos > 0 && out[pos - 1] != ' ') out[pos++] = ' ';
} else {
out[pos++] = (char)*p;
}
}
while(pos > 0 && out[pos - 1] == ' ') pos--;
out[pos] = 0;
if(!out[0]) snprintf(out, out_size, "%s", safe_basename_label(path));
}
static int
append_fs_place(json_buf_t *b, int *first, const char *path,
const char *kind) {
read_shortcuts(shortcut_entry_t *items, int max_items) {
FILE *file = fopen(BFPILOT_SHORTCUTS_PATH, "r");
if(!file) return 0;
int count = 0;
char line[700];
while(count < max_items && fgets(line, sizeof(line), file)) {
char *nl = strpbrk(line, "\r\n");
if(nl) *nl = 0;
char *tab = strchr(line, '\t');
if(!tab) continue;
*tab++ = 0;
if(!path_is_safe(tab)) continue;
sanitize_shortcut_label(items[count].label, sizeof(items[count].label),
line, tab);
snprintf(items[count].path, sizeof(items[count].path), "%s", tab);
count++;
}
fclose(file);
return count;
}
static int
write_shortcuts(const shortcut_entry_t *items, int count) {
ensure_bfpilot_dir();
FILE *file = fopen(BFPILOT_SHORTCUTS_TMP, "w");
if(!file) return -1;
for(int i = 0; i < count; i++) {
if(fprintf(file, "%s\t%s\n", items[i].label, items[i].path) < 0) {
fclose(file);
return -1;
}
}
if(fclose(file) != 0) return -1;
if(rename(BFPILOT_SHORTCUTS_TMP, BFPILOT_SHORTCUTS_PATH) != 0) return -1;
return 0;
}
static void
fs_totals(const char *path, int *ok, unsigned long long *total,
unsigned long long *free_bytes, unsigned long long *avail,
unsigned long long *used, double *used_pct) {
struct statvfs vfs;
*ok = 0;
*total = 0;
*free_bytes = 0;
*avail = 0;
*used = 0;
*used_pct = 0.0;
if(statvfs(path, &vfs) != 0) return;
unsigned long long block = vfs.f_frsize ? vfs.f_frsize : vfs.f_bsize;
*total = (unsigned long long)vfs.f_blocks * block;
*free_bytes = (unsigned long long)vfs.f_bfree * block;
*avail = (unsigned long long)vfs.f_bavail * block;
*used = *total > *free_bytes ? *total - *free_bytes : 0;
*used_pct = *total > 0 ? ((double)*used * 100.0) / (double)*total : 0.0;
*ok = 1;
}
static int
append_fs_place(json_buf_t *b, int *first, const char *label,
const char *path, const char *kind, int custom,
int mounted) {
struct stat st;
if(stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) return 0;
if(!*first && json_append(b, ",") != 0) return -1;
*first = 0;
if(json_append(b, "{\"path\":") != 0 ||
int fs_ok = 0;
unsigned long long total = 0, free_bytes = 0, avail = 0, used = 0;
double used_pct = 0.0;
fs_totals(path, &fs_ok, &total, &free_bytes, &avail, &used, &used_pct);
if(json_append(b, "{\"label\":") != 0 ||
json_string(b, label && label[0] ? label : safe_basename_label(path)) != 0 ||
json_append(b, ",\"path\":") != 0 ||
json_string(b, path) != 0 ||
json_append(b, ",\"kind\":") != 0 ||
json_string(b, kind ? kind : "path") != 0 ||
json_append(b, ",\"writable\":null,\"writeProbe\":\"skipped-read-only\"}") != 0) {
json_appendf(b,
",\"custom\":%s,\"mounted\":%s,\"dev\":%lu,"
"\"statvfs\":%s,\"totalBytes\":%llu,\"freeBytes\":%llu,"
"\"availableBytes\":%llu,\"usedBytes\":%llu,"
"\"usedPercent\":%.2f,"
"\"writable\":null,\"writeProbe\":\"skipped-read-only\"}",
custom ? "true" : "false", mounted ? "true" : "false",
(unsigned long)st.st_dev, fs_ok ? "true" : "false",
total, free_bytes, avail, used, used_pct) != 0) {
return -1;
}
return 0;
@@ -931,24 +1073,45 @@ append_fs_place(json_buf_t *b, int *first, const char *path,
static int
usb_request(const http_request_t *req) {
append_mount_candidate(json_buf_t *b, int *first, const char *path,
const char *kind, dev_t mnt_dev) {
struct stat st;
if(stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) return 0;
if(st.st_dev == mnt_dev) {
return 0;
}
char label[64];
snprintf(label, sizeof(label), "%s", path + strlen("/mnt/"));
for(char *p = label; *p; p++) *p = (char)toupper((unsigned char)*p);
return append_fs_place(b, first, label, path, kind, 0, 1);
}
static int
places_request(const http_request_t *req) {
json_buf_t b = {0};
if(json_append(&b, "{\"ok\":true,\"mounts\":[") != 0) return -1;
if(json_append(&b, "{\"ok\":true,\"places\":[") != 0) return -1;
int first = 1;
if(append_fs_place(&b, &first, "/data/homebrew", "homebrew") != 0) {
if(append_fs_place(&b, &first, "Root", "/", "root", 0, 1) != 0 ||
append_fs_place(&b, &first, "Homebrew", "/data/homebrew", "homebrew", 0, 1) != 0 ||
append_fs_place(&b, &first, "Mounts", "/mnt", "mounts", 0, 1) != 0 ||
append_fs_place(&b, &first, "User", "/user", "user", 0, 1) != 0 ||
append_fs_place(&b, &first, "Data", "/data", "internal", 0, 1) != 0) {
free(b.data);
return -1;
}
struct stat mnt_st;
dev_t mnt_dev = 0;
if(stat("/mnt", &mnt_st) == 0) mnt_dev = mnt_st.st_dev;
for(int i = 0; i < 8; i++) {
char path[24];
snprintf(path, sizeof(path), "/mnt/usb%d", i);
if(append_fs_place(&b, &first, path, "usb") != 0) {
free(b.data);
return -1;
}
snprintf(path, sizeof(path), "/mnt/usb%d/homebrew", i);
if(append_fs_place(&b, &first, path, "homebrew") != 0) {
if(append_mount_candidate(&b, &first, path, "usb", mnt_dev) != 0) {
free(b.data);
return -1;
}
@@ -956,16 +1119,45 @@ usb_request(const http_request_t *req) {
for(int i = 0; i < 8; i++) {
char path[24];
snprintf(path, sizeof(path), "/mnt/ext%d", i);
if(append_fs_place(&b, &first, path, "ext") != 0) {
free(b.data);
return -1;
}
snprintf(path, sizeof(path), "/mnt/ext%d/homebrew", i);
if(append_fs_place(&b, &first, path, "homebrew") != 0) {
if(append_mount_candidate(&b, &first, path, "ext", mnt_dev) != 0) {
free(b.data);
return -1;
}
}
shortcut_entry_t shortcuts[BFPILOT_MAX_SHORTCUTS];
int shortcut_count = read_shortcuts(shortcuts, BFPILOT_MAX_SHORTCUTS);
for(int i = 0; i < shortcut_count; i++) {
if(append_fs_place(&b, &first, shortcuts[i].label, shortcuts[i].path,
"custom", 1, 1) != 0) {
free(b.data);
return -1;
}
}
if(json_append(&b, "],\"mounts\":[") != 0) {
free(b.data);
return -1;
}
int mount_first = 1;
for(int i = 0; i < 8; i++) {
char path[24];
snprintf(path, sizeof(path), "/mnt/usb%d", i);
if(append_mount_candidate(&b, &mount_first, path, "usb", mnt_dev) != 0) {
free(b.data);
return -1;
}
}
for(int i = 0; i < 8; i++) {
char path[24];
snprintf(path, sizeof(path), "/mnt/ext%d", i);
if(append_mount_candidate(&b, &mount_first, path, "ext", mnt_dev) != 0) {
free(b.data);
return -1;
}
}
if(json_append(&b, "]}") != 0) {
free(b.data);
return -1;
@@ -974,6 +1166,136 @@ usb_request(const http_request_t *req) {
}
static int
shortcut_add_request(const http_request_t *req) {
char label[128], path[512];
if(!websrv_get_query_arg(req, "path", path, sizeof(path)) ||
!path_is_safe(path)) {
return serve_error(req, 400, "bad shortcut path");
}
(void)websrv_get_query_arg(req, "label", label, sizeof(label));
struct stat st;
if(stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
return serve_error(req, 404, "shortcut target is not a folder");
}
shortcut_entry_t shortcuts[BFPILOT_MAX_SHORTCUTS];
int count = read_shortcuts(shortcuts, BFPILOT_MAX_SHORTCUTS);
int index = -1;
for(int i = 0; i < count; i++) {
if(!strcmp(shortcuts[i].path, path)) {
index = i;
break;
}
}
if(index < 0) {
if(count >= BFPILOT_MAX_SHORTCUTS) {
return serve_error(req, 409, "shortcut limit reached");
}
index = count++;
}
sanitize_shortcut_label(shortcuts[index].label,
sizeof(shortcuts[index].label), label, path);
snprintf(shortcuts[index].path, sizeof(shortcuts[index].path), "%s", path);
if(write_shortcuts(shortcuts, count) != 0) {
return serve_error(req, 500, strerror(errno));
}
bfpilot_log("shortcut add label=%s path=%s", shortcuts[index].label, path);
json_buf_t b = {0};
if(json_append(&b, "{\"ok\":true,\"label\":") != 0 ||
json_string(&b, shortcuts[index].label) != 0 ||
json_append(&b, ",\"path\":") != 0 ||
json_string(&b, path) != 0 ||
json_append(&b, "}") != 0) {
free(b.data);
return -1;
}
return serve_owned(req, 200, b.data, b.len);
}
static int
shortcut_delete_request(const http_request_t *req) {
char path[512];
if(!websrv_get_query_arg(req, "path", path, sizeof(path)) ||
!path_is_safe(path)) {
return serve_error(req, 400, "bad shortcut path");
}
shortcut_entry_t shortcuts[BFPILOT_MAX_SHORTCUTS];
int count = read_shortcuts(shortcuts, BFPILOT_MAX_SHORTCUTS);
int out = 0;
int removed = 0;
for(int i = 0; i < count; i++) {
if(!strcmp(shortcuts[i].path, path)) {
removed = 1;
continue;
}
if(out != i) shortcuts[out] = shortcuts[i];
out++;
}
if(!removed) return serve_error(req, 404, "shortcut not found");
if(write_shortcuts(shortcuts, out) != 0) {
return serve_error(req, 500, strerror(errno));
}
bfpilot_log("shortcut delete path=%s", path);
json_buf_t b = {0};
if(json_append(&b, "{\"ok\":true,\"path\":") != 0 ||
json_string(&b, path) != 0 ||
json_append(&b, "}") != 0) {
free(b.data);
return -1;
}
return serve_owned(req, 200, b.data, b.len);
}
static int
shortcut_rename_request(const http_request_t *req) {
char label[128], path[512];
if(!websrv_get_query_arg(req, "path", path, sizeof(path)) ||
!path_is_safe(path)) {
return serve_error(req, 400, "bad shortcut path");
}
if(!websrv_get_query_arg(req, "label", label, sizeof(label))) {
return serve_error(req, 400, "missing shortcut label");
}
shortcut_entry_t shortcuts[BFPILOT_MAX_SHORTCUTS];
int count = read_shortcuts(shortcuts, BFPILOT_MAX_SHORTCUTS);
int index = -1;
for(int i = 0; i < count; i++) {
if(!strcmp(shortcuts[i].path, path)) {
index = i;
break;
}
}
if(index < 0) return serve_error(req, 404, "shortcut not found");
sanitize_shortcut_label(shortcuts[index].label,
sizeof(shortcuts[index].label), label, path);
if(write_shortcuts(shortcuts, count) != 0) {
return serve_error(req, 500, strerror(errno));
}
bfpilot_log("shortcut rename label=%s path=%s", shortcuts[index].label, path);
json_buf_t b = {0};
if(json_append(&b, "{\"ok\":true,\"label\":") != 0 ||
json_string(&b, shortcuts[index].label) != 0 ||
json_append(&b, ",\"path\":") != 0 ||
json_string(&b, path) != 0 ||
json_append(&b, "}") != 0) {
free(b.data);
return -1;
}
return serve_owned(req, 200, b.data, b.len);
}
static int
spawn_copy_or_move(const http_request_t *req, int is_move) {
char src[1024], dst[1024];
@@ -1157,14 +1479,18 @@ status_handler(const http_request_t *req) {
long elapsed = started_at > 0 && ref_at > started_at
? (long)(ref_at - started_at)
: 0;
long speed = elapsed > 0 && cb > 0 ? cb / elapsed : 0;
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;
}
double speed_bps = elapsed_ms > 0 && cb > 0
? ((double)cb * 1000.0) / (double)elapsed_ms
: 0.0;
long speed = speed_bps > 0.0 ? (long)speed_bps : 0;
double avg_mbps = speed_bps / (1024.0 * 1024.0);
long eta = busy && speed > 0 && tb > cb
? (long)(((double)(tb - cb) / speed_bps) + 0.999)
: 0;
json_buf_t b = {0};
if(json_appendf(&b,
@@ -1186,10 +1512,11 @@ status_handler(const http_request_t *req) {
"\"elapsedSeconds\":%ld,\"elapsedMs\":%ld,"
"\"bytesRead\":%ld,\"bytesWritten\":%ld,"
"\"sourceDev\":%lu,\"destinationDev\":%lu,\"errno\":%d,"
"\"speedBytesPerSec\":%ld,\"etaSeconds\":%ld}",
"\"speedBytesPerSec\":%ld,\"averageMBps\":%.2f,"
"\"etaSeconds\":%ld}",
tb, cb, tf, df, ff, (long)started_at, (long)ended_at,
elapsed, elapsed_ms, read_bytes, written_bytes, source_dev,
destination_dev, last_errno, speed, eta) != 0) {
destination_dev, last_errno, speed, avg_mbps, eta) != 0) {
free(b.data);
return -1;
}
@@ -1243,7 +1570,11 @@ transfer_request(const http_request_t *req, const char *url) {
if(!strcmp(url, "/api/fs/list")) return list_request(req);
if(!strcmp(url, "/api/fs/stat")) return stat_request(req);
if(!strcmp(url, "/api/fs/du")) return du_request(req);
if(!strcmp(url, "/api/fs/usb")) return usb_request(req);
if(!strcmp(url, "/api/fs/places")) return places_request(req);
if(!strcmp(url, "/api/fs/usb")) return places_request(req);
if(!strcmp(url, "/api/fs/shortcut/add")) return shortcut_add_request(req);
if(!strcmp(url, "/api/fs/shortcut/delete")) return shortcut_delete_request(req);
if(!strcmp(url, "/api/fs/shortcut/rename")) return shortcut_rename_request(req);
if(!strcmp(url, "/api/fs/copy")) return spawn_copy_or_move(req, 0);
if(!strcmp(url, "/api/fs/move")) return spawn_copy_or_move(req, 1);
if(!strcmp(url, "/api/fs/delete")) return delete_handler(req);
+1 -1
View File
@@ -5,7 +5,7 @@
#pragma once
#ifndef VERSION_TAG
#define VERSION_TAG "bfpilot-v0.2.1"
#define VERSION_TAG "bfpilot-v0.3"
#endif
#ifndef BUILD_VERSION
+4 -1
View File
@@ -453,7 +453,10 @@ diag_request(const http_request_t *req) {
"\"launcher_final_state\":\"%s\"},"
"\"notifications\":{\"mode\":\"optional-raw-debug\"},"
"\"routes\":[\"/\",\"/api/status\",\"/api/diag\","
"\"/fs\",\"/api/fs/*\",\"/api/fs/transfer/stats\"]}",
"\"/fs\",\"/api/fs/*\",\"/api/fs/places\","
"\"/api/fs/shortcut/add\",\"/api/fs/shortcut/delete\","
"\"/api/fs/shortcut/rename\","
"\"/api/fs/transfer/stats\"]}",
VERSION_TAG, BUILD_VERSION, BFPILOT_BUILD_MODE,
(long)getpid(), (long)now,
bfpilot_diag_uptime(),
-17
View File
@@ -1,17 +0,0 @@
/*
* BFpilot test payload - boot marker only.
*/
#include <unistd.h>
#include "boot_marker.h"
#include "notify.h"
int
main(void) {
bfpilot_boot_marker("tests/hello_boot", BFPILOT_BUILD_MODE);
sleep(10);
bfpilot_notify("BFpilot BOOT still alive", "tests/hello_boot");
return 0;
}
Binary file not shown.
-126
View File
@@ -1,126 +0,0 @@
/*
* BFpilot test payload - tiny HTTP status server.
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include "boot_marker.h"
#ifndef BFPILOT_HELLO_HTTP_PORT
#define BFPILOT_HELLO_HTTP_PORT 5906
#endif
static int
write_all(int fd, const void *data, size_t size) {
const char *p = data;
while(size > 0) {
ssize_t wr = send(fd, p, size, 0);
if(wr < 0) {
if(errno == EINTR) continue;
return -1;
}
if(wr == 0) return -1;
p += wr;
size -= (size_t)wr;
}
return 0;
}
static void
serve_client(int fd) {
char req[512];
ssize_t n = recv(fd, req, sizeof(req) - 1, 0);
if(n < 0) {
close(fd);
return;
}
req[n > 0 ? n : 0] = 0;
const char body[] =
"{\"ok\":true,\"name\":\"BFpilot hello_http\",\"port\":5906}";
const char not_found[] = "{\"ok\":false,\"error\":\"not found\"}";
char header[256];
if(strstr(req, "GET /api/status ") || strstr(req, "GET /api/status?")) {
int h = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %lu\r\n\r\n",
(unsigned long)(sizeof(body) - 1));
(void)write_all(fd, header, (size_t)h);
(void)write_all(fd, body, sizeof(body) - 1);
} else {
int h = snprintf(header, sizeof(header),
"HTTP/1.1 404 Not Found\r\n"
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %lu\r\n\r\n",
(unsigned long)(sizeof(not_found) - 1));
(void)write_all(fd, header, (size_t)h);
(void)write_all(fd, not_found, sizeof(not_found) - 1);
}
shutdown(fd, SHUT_RDWR);
close(fd);
}
int
main(void) {
bfpilot_boot_marker("tests/hello_http", BFPILOT_BUILD_MODE);
signal(SIGPIPE, SIG_IGN);
int srv = socket(AF_INET, SOCK_STREAM, 0);
if(srv < 0) {
printf("BFpilot hello_http socket failed errno=%d\n", errno);
return 1;
}
int on = 1;
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(BFPILOT_HELLO_HTTP_PORT);
if(bind(srv, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
printf("BFpilot hello_http bind failed errno=%d\n", errno);
close(srv);
return 1;
}
if(listen(srv, 4) != 0) {
printf("BFpilot hello_http listen failed errno=%d\n", errno);
close(srv);
return 1;
}
printf("BFpilot hello_http listening on port %u\n",
(unsigned int)BFPILOT_HELLO_HTTP_PORT);
for(;;) {
int fd = accept(srv, NULL, NULL);
if(fd < 0) {
if(errno == EINTR) continue;
printf("BFpilot hello_http accept failed errno=%d\n", errno);
break;
}
serve_client(fd);
}
close(srv);
return 0;
}
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
/*
* BFpilot test payload - notification probe.
*/
#include <stdio.h>
#include "boot_marker.h"
#include "notify.h"
int
main(void) {
bfpilot_boot_marker("tests/hello_notify", BFPILOT_BUILD_MODE);
int rc = bfpilot_notify_send("BFpilot hello_notify",
"notification probe");
printf("BFpilot hello_notify notification rc=0x%08x\n", rc);
return rc == 0 ? 0 : 1;
}
Binary file not shown.
-21
View File
@@ -1,21 +0,0 @@
/*
* BFpilot test payload - installer entry marker with safe imports only.
*/
#include "boot_marker.h"
#include "notify.h"
#include "probe_common.h"
#define INSTALLER_ENTER_MARKER "/data/BFpilot/installer_enter_probe.txt"
int
main(void) {
int marker_rc = probe_write_marker(INSTALLER_ENTER_MARKER,
"tests/installer_enter_probe");
bfpilot_boot_marker("tests/installer_enter_probe", BFPILOT_BUILD_MODE);
printf("BFpilot installer_enter_probe marker_rc=%d\n", marker_rc);
bfpilot_notify("BFpilot installer enter probe",
marker_rc == 0 ? "entered main" : "marker write failed");
return marker_rc == 0 ? 0 : 1;
}
Binary file not shown.
-30
View File
@@ -1,30 +0,0 @@
/*
* BFpilot test payload - direct AppInst import/link probe.
*
* This payload intentionally links AppInstUtil but never calls it. If the
* marker file is not written, the loader rejected the ELF before main().
*/
#include "boot_marker.h"
#include "notify.h"
#include "probe_common.h"
#define LINKONLY_APPINST_MARKER "/data/BFpilot/linkonly_appinst_entered.txt"
int sceAppInstUtilInitialize(void);
__attribute__((used)) static void * volatile g_appinst_import_probe =
(void *)&sceAppInstUtilInitialize;
int
main(void) {
int marker_rc = probe_write_marker(LINKONLY_APPINST_MARKER,
"tests/installer_linkonly_appinst");
bfpilot_boot_marker("tests/installer_linkonly_appinst", BFPILOT_BUILD_MODE);
printf("BFpilot installer_linkonly_appinst marker_rc=%d import_symbol=%p\n",
marker_rc, g_appinst_import_probe);
bfpilot_notify("BFpilot link-only AppInst",
marker_rc == 0 ? "entered main" : "marker write failed");
return marker_rc == 0 ? 0 : 1;
}
Binary file not shown.
-75
View File
@@ -1,75 +0,0 @@
/*
* BFpilot test payload - runtime AppInstUtil load/resolve probe.
*/
#include <stdint.h>
#include <stdio.h>
#include <ps5/kernel.h>
#include "boot_marker.h"
#include "notify.h"
#include "probe_common.h"
#include "version.h"
#define RUNTIME_RESOLVE_MARKER "/data/BFpilot/runtime_resolve_entered.txt"
#define RUNTIME_RESOLVE_LOG "/data/BFpilot/runtime_resolve_appinst.log"
#define APPINST_MODULE "libSceAppInstUtil.sprx"
#define NID_INSTALL_TITLE_DIR "Wudg3Xe3heE"
int
main(void) {
int marker_rc = probe_write_marker(RUNTIME_RESOLVE_MARKER,
"tests/installer_runtime_resolve_appinst");
bfpilot_boot_marker("tests/installer_runtime_resolve_appinst",
BFPILOT_BUILD_MODE);
probe_log(RUNTIME_RESOLVE_LOG,
"entered main marker_rc=%d tag=%s build=%s",
marker_rc, VERSION_TAG, BUILD_VERSION);
uint32_t handle = 0;
probe_log(RUNTIME_RESOLVE_LOG, "about to kernel_dynlib_handle %s",
APPINST_MODULE);
int handle_rc = kernel_dynlib_handle(-1, APPINST_MODULE, &handle);
probe_log(RUNTIME_RESOLVE_LOG,
"kernel_dynlib_handle %s rc=0x%08x handle=0x%08x",
APPINST_MODULE, handle_rc, handle);
if(handle_rc == 0) {
probe_log(RUNTIME_RESOLVE_LOG,
"about to kernel_dynlib_dlsym sceAppInstUtilInitialize");
intptr_t init_addr = kernel_dynlib_dlsym(
-1, handle, "sceAppInstUtilInitialize");
probe_log(RUNTIME_RESOLVE_LOG,
"kernel_dynlib_dlsym sceAppInstUtilInitialize addr=%p",
(void *)init_addr);
probe_log(RUNTIME_RESOLVE_LOG,
"about to kernel_dynlib_dlsym sceAppInstUtilAppInstallAll");
intptr_t install_all_addr = kernel_dynlib_dlsym(
-1, handle, "sceAppInstUtilAppInstallAll");
probe_log(RUNTIME_RESOLVE_LOG,
"kernel_dynlib_dlsym sceAppInstUtilAppInstallAll addr=%p",
(void *)install_all_addr);
probe_log(RUNTIME_RESOLVE_LOG,
"about to kernel_dynlib_resolve AppInstallTitleDir nid=%s",
NID_INSTALL_TITLE_DIR);
intptr_t title_dir_addr = kernel_dynlib_resolve(
-1, handle, NID_INSTALL_TITLE_DIR);
probe_log(RUNTIME_RESOLVE_LOG,
"kernel_dynlib_resolve AppInstallTitleDir nid=%s addr=%p",
NID_INSTALL_TITLE_DIR, (void *)title_dir_addr);
} else {
probe_log(RUNTIME_RESOLVE_LOG,
"AppInstUtil not already loaded; skipping dlopen because prior "
"test stopped before the dlopen result log");
}
bfpilot_notify("BFpilot runtime AppInst",
marker_rc == 0 ? "runtime resolve probe complete"
: "marker write failed");
return marker_rc == 0 ? 0 : 1;
}
Binary file not shown.
-46
View File
@@ -1,46 +0,0 @@
/*
* BFpilot probe - direct Sysmodule import, then load and resolve AppInstUtil.
*/
#include <stdint.h>
#include <ps5/kernel.h>
#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;
}
-53
View File
@@ -1,53 +0,0 @@
/*
* BFpilot probe - exact direct-import composition used by ps5 websrv.
*/
#include <stdint.h>
#include <unistd.h>
#include <ps5/kernel.h>
#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;
}
Binary file not shown.
-87
View File
@@ -1,87 +0,0 @@
/*
* BFpilot test payload helpers.
*/
#pragma once
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define BFPILOT_PROBE_DATA_ROOT "/data"
#define BFPILOT_PROBE_DATA_DIR "/data/BFpilot"
static int __attribute__((unused))
probe_mkdir_if_needed(const char *path) {
if(mkdir(path, 0755) == 0) return 0;
return errno == EEXIST ? 0 : -errno;
}
static void __attribute__((unused))
probe_ensure_data_dir(void) {
(void)probe_mkdir_if_needed(BFPILOT_PROBE_DATA_ROOT);
(void)probe_mkdir_if_needed(BFPILOT_PROBE_DATA_DIR);
}
static int __attribute__((unused))
probe_write_marker(const char *path, const char *payload_name) {
char line[512];
probe_ensure_data_dir();
int n = snprintf(line, sizeof(line),
"%ld payload=%s pid=%ld entered main\n",
(long)time(NULL),
payload_name ? payload_name : "unknown",
(long)getpid());
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_APPEND, 0600);
if(fd < 0) return -errno;
ssize_t wr = write(fd, line, (size_t)n);
int saved_errno = errno;
if(close(fd) != 0 && wr == n) return -errno;
if(wr != n) return wr < 0 ? -saved_errno : -EIO;
return 0;
}
static void __attribute__((unused))
probe_log(const char *path, const char *fmt, ...) {
char body[1024];
char line[1400];
va_list ap;
va_start(ap, fmt);
int body_n = vsnprintf(body, sizeof(body), fmt ? fmt : "", ap);
va_end(ap);
if(body_n < 0) {
snprintf(body, sizeof(body), "log format failed");
} else if((size_t)body_n >= sizeof(body)) {
body[sizeof(body) - 1] = 0;
}
int n = snprintf(line, sizeof(line), "%ld pid=%ld %s\n",
(long)time(NULL), (long)getpid(), body);
if(n < 0) return;
if((size_t)n >= sizeof(line)) n = (int)sizeof(line) - 1;
fputs(line, stdout);
fflush(stdout);
probe_ensure_data_dir();
int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
if(fd >= 0) {
(void)write(fd, line, (size_t)n);
close(fd);
}
}