mirror of
https://github.com/ItsBlurf/BFpilot.git
synced 2026-10-18 23:06:53 -05:00
Release BFpilot v0.3
This commit is contained in:
1 parent
bc2c0e284f
commit
43ada0e1ae
42 files changed
+1220
-2326
No files matched your search
+4
-1
@@ -1,11 +1,14 @@
|
||||
# Build output
|
||||
gen/
|
||||
build/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.elf
|
||||
__pycache__/
|
||||
diagnostics/*.json
|
||||
diagnostics/
|
||||
output/
|
||||
screenshot.png
|
||||
|
||||
# Local environment / editor noise
|
||||
.env
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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');
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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__":
|
||||
|
||||
@@ -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())
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user