diff --git a/.gitignore b/.gitignore index ceb319b..bbb38aa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ gen/ *.a *.so *.elf -!test-builds/**/*.elf # Local environment / editor noise .env diff --git a/Makefile b/Makefile index 4b82c1c..663f5ee 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,8 @@ endif PS5_HOST ?= ps5 PS5_PORT ?= 9021 PYTHON ?= python3 +WEB_PORT ?= 5905 +HELLO_HTTP_PORT ?= 5906 VERSION_TAG := bfpilot-v0.2.1 BUILD_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) @@ -26,27 +28,69 @@ LLVM_CONFIG ?= $(CURDIR)/build-tools/llvm-config export LLVM_BINDIR export LLVM_CONFIG -CORE_BIN := bfpilot-core.elf +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 -COMMON_SRCS := src/lite_main.c -COMMON_SRCS += src/diag.c -COMMON_SRCS += src/websrv_lite.c -COMMON_SRCS += src/asset.c -COMMON_SRCS += src/fs.c -COMMON_SRCS += src/mime.c -COMMON_SRCS += src/notify.c -COMMON_SRCS += src/transfer.c +WEB_SRCS := src/lite_main.c +WEB_SRCS += src/boot_marker.c +WEB_SRCS += src/diag.c +WEB_SRCS += src/websrv_lite.c +WEB_SRCS += src/asset.c +WEB_SRCS += src/fs.c +WEB_SRCS += src/mime.c +WEB_SRCS += src/notify.c +WEB_SRCS += src/transfer.c -FULL_SRCS := $(COMMON_SRCS) -FULL_SRCS += src/app_installer.c -FULL_SRCS += src/sce_resolve.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 ASSETS := $(wildcard assets/*) GEN_SRCS := $(patsubst assets/%,gen/assets/%,$(ASSETS:=.c)) APP_ASSETS := assets-app/param.json assets-app/icon0.png -CORE_OBJS := $(patsubst %.c,build/core/%.o,$(COMMON_SRCS) $(GEN_SRCS)) -FULL_OBJS := $(patsubst %.c,build/full/%.o,$(FULL_SRCS) $(GEN_SRCS)) + +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)) COMMON_CFLAGS := -Os -Wall -Werror -Isrc COMMON_CFLAGS += -ffunction-sections -fdata-sections -flto @@ -54,17 +98,59 @@ COMMON_CFLAGS += -DVERSION_TAG=\"$(VERSION_TAG)\" COMMON_CFLAGS += -DBUILD_VERSION=\"$(BUILD_VERSION)\" COMMON_CFLAGS += -DBFPILOT_SDK_PATH=\"$(PS5_PAYLOAD_SDK)\" -CORE_CFLAGS := $(COMMON_CFLAGS) -CORE_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0 -CORE_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1 -CORE_CFLAGS += -DBFPILOT_BUILD_MODE=\"core\" +BFPILOT_CFLAGS := $(COMMON_CFLAGS) +BFPILOT_CFLAGS += -DBFPILOT_PAYLOAD_NAME=\"bfpilot\" +BFPILOT_CFLAGS += -DBFPILOT_BUILD_MODE=\"file-manager\" +BFPILOT_CFLAGS += -DBFPILOT_WEB_PORT=$(WEB_PORT) +BFPILOT_CFLAGS += -DBFPILOT_DEBUG_NOTIFICATIONS=0 +BFPILOT_CFLAGS += -DBFPILOT_ENABLE_LAUNCHER=0 +BFPILOT_CFLAGS += -DBFPILOT_DISABLE_LAUNCHER=1 + +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_ENABLE_LAUNCHER=1 +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 -FULL_LDLIBS := -lSceAppInstUtil +APPINST_LDLIBS := -lSceAppInstUtil CC_CMD := "$(CC)" STRIP_CMD := "$(STRIP)" @@ -82,10 +168,13 @@ 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_LINK_SUFFIX := -lkernel_web -lSceLibcInternal -lSceNet -WINDOWS_LINK_SUFFIX += -lc_stub_weak -lkernel_stub_weak -WINDOWS_LINK_SUFFIX += -l:crtend.o -l:crtn.o -WINDOWS_FULL_LINK_SUFFIX := -lSceAppInstUtil $(WINDOWS_LINK_SUFFIX) +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 +WINDOWS_APPINST_SUFFIX := -lSceAppInstUtil $(WINDOWS_BASE_SUFFIX) define run bash -lc '$(RUN_ENV) && $(1)' endef @@ -95,52 +184,193 @@ $(1) endef endif -all: core full +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 -core: $(CORE_BIN) +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) + gen/assets: $(call run,mkdir -p $@) gen/assets/%.c: assets/% | gen/assets $(call run,$(PYTHON) gen-asset-module.py --path $* $< > $@) -build/core/%.o: %.c Makefile +build/bfpilot/%.o: %.c Makefile $(call run,mkdir -p $(dir $@)) - $(call run,$(CC_CMD) $(CORE_CFLAGS) -c $< -o $@) + $(call run,$(CC_CMD) $(BFPILOT_CFLAGS) -c $< -o $@) -build/full/%.o: %.c Makefile +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 $@) + ifeq ($(HOST_IS_WINDOWS),1) -$(CORE_BIN): $(CORE_OBJS) - $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(CORE_OBJS) $(WINDOWS_LINK_SUFFIX)) +$(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 $@) -$(FULL_BIN): $(FULL_OBJS) $(APP_ASSETS) - $(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(FULL_OBJS) $(WINDOWS_FULL_LINK_SUFFIX)) +$(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 $@) else -$(CORE_BIN): $(CORE_OBJS) - $(call run,$(CC_CMD) $(CORE_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(CORE_OBJS)) +$(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 $@) -$(FULL_BIN): $(FULL_OBJS) $(APP_ASSETS) - $(call run,$(CC_CMD) $(FULL_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(FULL_OBJS) $(FULL_LDLIBS)) +$(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS) + $(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(APPINST_LDLIBS)) + $(call run,$(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 $@) endif -deploy-core: core - $(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(CORE_BIN)) +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)) + +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)) + clean: - $(call run,rm -rf $(CORE_BIN) $(FULL_BIN) build gen) + $(call run,rm -rf $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) bfpilot-full-legacy.elf $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) bfpilot-core.elf build gen $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) tests/hello_appinst.elf) .SECONDARY: $(GEN_SRCS) -.PHONY: all core full clean deploy-core deploy-full +.PHONY: all bfpilot debug full legacy-full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer diff --git a/README.md b/README.md index fa0dccb..8c31f0b 100644 --- a/README.md +++ b/README.md @@ -1,392 +1,213 @@ # BFpilot -BFpilot is a small PS5 payload that starts a browser-based file manager on port -`5905`. +BFpilot is a small PS5 userland payload that starts a browser-based file +manager on port `5905`. -It is meant to stay simple: no PKG installer, no FTP daemon, no mounting tools, -no payload bundle, no exploit code, and no DRM or backup features. The -compatibility-first build starts one standalone web server and lets the PS5 do -file operations locally. +It is intentionally narrow: no exploit code, no PKG installer, no FTP daemon, +no mounting tools, no payload bundle, and no DRM or backup features. Current prepared release: `v0.2.1` -## Recommended Build +## Recommended Payload -Use `bfpilot-core.elf` first on every new firmware or loader. +Use `bfpilot.elf` as the main release payload. -`bfpilot-core.elf` is the recommended build for maximum firmware compatibility: +`bfpilot.elf` is the maximum-compatibility file manager: -- Starts the HTTP file manager on `http://:5905/` -- Does not install or refresh the PS5 launcher tile -- Does not compile launcher installer code -- Does not link launcher-only SCE libraries -- Keeps notification attempts optional +- Starts the HTTP file manager at `http://: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`. -Only try `bfpilot-full.elf` after `bfpilot-core.elf` works on the same firmware -and loader. +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. -`bfpilot-full.elf` includes optional launcher tile support. Launcher install may -fail on some firmware, exploit, or loader combinations. That should not stop the -file manager. If launcher install fails but port `5905` still works, use the web -file manager directly from a browser. +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 `bfpilot-launcher-installer.elf` only after `bfpilot.elf` works. It is a +separate optional payload whose only job is installing or refreshing the +home-screen tile. It intentionally direct-links AppInstUtil, matching the +websrv/Payload Manager/ftpsrv launcher-install pattern. If the loader rejects +that import, the installer can fail before `main()`. The file manager is still +unaffected. + +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. ## What It Does -- Runs a file-manager web UI at `http://:5905/` -- Builds `bfpilot-core.elf` for the safest firmware compatibility path -- Builds `bfpilot-full.elf` for optional PS5 home-screen launcher support -- Browses, uploads, downloads, copies, moves, renames, creates folders, and - deletes -- Shows progress for copy, move, delete, and browser uploads -- Reports size, speed, ETA, item count, and current path/file while working -- Calculates folder size only when you select a folder -- Creates missing copy/move target folders automatically -- Keeps the default target shortcuts at `/data/homebrew` and `/mnt/usb0` -- Writes diagnostics to `/data/BFpilot/log.txt` -- Writes fatal-signal diagnostics to `/data/BFpilot/crash.log` +- Runs a file-manager web UI at `http://: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`. -The full launcher tile opens: +The optional launcher tile, if installed by the separate installer payload, +opens: ```text http://127.0.0.1:5905/ ``` -Injecting the payload does not auto-open the browser. Core mode only starts the -web server. Full mode starts the web server and then attempts launcher install or -refresh as optional work. - ## Runtime Notes Inject the ELF to your payload loader on port `9021`. -For first tests on a firmware: +First test: ```text -send bfpilot-core.elf to :9021 +send bfpilot.elf to :9021 open http://:5905/ open http://:5905/api/status open http://:5905/api/diag ``` -If core works and you want the PS5 launcher tile: +Debug test: + +```text +send bfpilot-debug.elf to :9021 +open http://:5905/api/diag +check /data/BFpilot/boot.log +check /data/BFpilot/log.txt +``` + +Full install test: ```text send bfpilot-full.elf to :9021 open http://:5905/ -open http://:5905/api/diag +check /data/BFpilot/log.txt ``` -Full mode can skip launcher work at runtime: +Alternate port test while an old instance is still running: ```text ---no-launcher -BFPILOT_NO_LAUNCHER=1 +send bfpilot-debug.elf --port 5906 to :9021 +open http://:5906/api/status ``` -The full build stores launcher marker data in: +Launcher installer test: ```text -/data/BFpilot +send bfpilot-launcher-installer.elf to :9021 +check /data/BFpilot/launcher-installer.log ``` -The PS5 launcher app itself is installed under: +If `bfpilot-launcher-installer.elf` gives no notification and no log, send +`bfpilot-launcher-installer-safe.elf` next. If the safe installer reaches +`main()` but says AppInstUtil is unavailable, the direct installer is being +rejected before it can run. + +If the launcher installer appears to do nothing, run the probes in this order: ```text -/user/app/BFPL00001 +tests/installer_enter_probe.elf +tests/installer_linkonly_appinst.elf +tests/installer_runtime_resolve_appinst.elf ``` -If launcher install fails, keep testing the web server. The file manager can -still work without a launcher tile. +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. ## Diagnostics -Use these endpoints while testing firmware compatibility: - -```text -http://:5905/api/status -http://:5905/api/diag -``` - Save these files when reporting failures: ```text +/data/BFpilot/boot.log /data/BFpilot/log.txt /data/BFpilot/crash.log +/data/BFpilot/launcher-installer.log ``` -### `/api/diag` Core Example - -This is the shape of a healthy core-mode response. Values such as `pid`, -`uptime`, `cwd`, and permission booleans vary by firmware and loader. - -```json -{ - "ok": true, - "name": "BFpilot", - "tag": "bfpilot-v0.2.1", - "version": "dev", - "mode": "core", - "pid": 112, - "uptime": 14, - "port": 5905, - "cwd": "/", - "can_stat_root": true, - "can_opendir_data": true, - "can_opendir_data_homebrew": true, - "can_opendir_usb0": true, - "can_write_data_bfpilot": true, - "can_write_user_app": false, - "launcher_status": "not_compiled", - "last_errno": 0, - "checkpoint": "web server ready", - "core_server_started": true, - "launcher_attempted": false, - "appinst_init_rc": -2147483000, - "title_dir_resolved": false, - "install_title_rc": -2147483000, - "install_all_rc": -2147483000, - "uninstall_rc": -2147483000, - "launcher_final_state": "skipped", - "rcs": { - "sceNetCtlInit": -2147483000, - "sceUserServiceInitialize": -2147483000, - "notification_test": 0, - "bind_5905": 0, - "listen_5905": 0 - }, - "launcher": { - "status": "not_compiled", - "status_rc": -2147483000, - "compiled": false, - "disabled": true, - "launcher_enabled": false, - "launcher_attempted": false, - "appinst_init_rc": -2147483000, - "title_dir_resolved": false, - "install_title_dir_resolved": false, - "install_title_rc": -2147483000, - "uninstall_resolved": false, - "uninstall_rc": -2147483000, - "install_all_resolved": false, - "install_all_rc": -2147483000, - "user_app_writable": null, - "launcher_install_rc": -1, - "launcher_final_state": "skipped" - }, - "routes": ["/", "/api/status", "/api/diag", "/fs", "/api/fs/*"] -} -``` - -### `/api/diag` Full Launcher-Failed Example - -This example is still acceptable if the web server works. The important part is -that launcher failure is recorded and the server remains reachable. - -```json -{ - "ok": true, - "name": "BFpilot", - "tag": "bfpilot-v0.2.1", - "mode": "full", - "port": 5905, - "can_opendir_data": true, - "can_write_data_bfpilot": true, - "launcher_status": "failed_nonfatal", - "core_server_started": true, - "launcher_attempted": true, - "appinst_init_rc": -2, - "title_dir_resolved": false, - "install_title_rc": -2147483000, - "install_all_rc": -2147483000, - "uninstall_rc": -2147483000, - "launcher_final_state": "failed_nonfatal", - "rcs": { - "notification_test": -1, - "bind_5905": 0, - "listen_5905": 0 - }, - "launcher": { - "status": "failed_nonfatal", - "status_rc": -1, - "compiled": true, - "disabled": false, - "launcher_enabled": true, - "launcher_attempted": true, - "appinst_init_rc": -2, - "title_dir_resolved": false, - "install_title_dir_resolved": false, - "install_title_rc": -2147483000, - "uninstall_resolved": false, - "uninstall_rc": -2147483000, - "install_all_resolved": true, - "install_all_rc": -2147483000, - "user_app_writable": false, - "launcher_install_rc": -2, - "launcher_final_state": "failed_nonfatal" - } -} -``` - -## Troubleshooting - -### Port `5905` Not Reachable - -- Confirm the payload sender reported a successful send to port `9021`. -- Try `bfpilot-core.elf` before `bfpilot-full.elf`. -- Check whether another BFpilot instance already owns port `5905`. -- Open `/data/BFpilot/log.txt` and look for `bind_5905`, `listen_5905`, and - `web server listening`. -- Recheck the PS5 IP address and make sure the browser device is on the same - network. - -### Payload Exits Immediately - -- Use `bfpilot-core.elf` first. -- Check the sender console for loader-side errors. -- Check `/data/BFpilot/crash.log` for fatal signal details. -- Check `/data/BFpilot/log.txt` for the last checkpoint. -- If reinjecting during a copy, move, or delete job, the new instance may exit - intentionally so the old job is not killed. - -### Launcher Install Failed - -- This is not fatal in `v0.2.1`. -- Confirm `http://:5905/` still opens. -- Open `/api/diag` and record `launcher_attempted`, `appinst_init_rc`, - `title_dir_resolved`, `install_title_rc`, `install_all_rc`, `uninstall_rc`, - `launcher_final_state`, `launcher.user_app_writable`, and - `launcher.launcher_install_rc`. -- If the file manager works, keep using the browser URL and report the launcher - diagnostics separately. - -### `/mnt/usb0` Missing - -- Confirm the external drive is connected before starting BFpilot. -- Try reconnecting the drive and refreshing the file manager. -- Check `/api/diag` for `can_opendir_usb0`. -- Some setups may expose a different USB mount path. Include `/api/diag` and - `/data/BFpilot/log.txt` in the report. - -### Upload Fails - -- Test with a tiny `.txt` file first. -- Upload to `/data/BFpilot-test` before testing USB storage. -- Confirm `/api/diag` reports `can_write_data_bfpilot: true`. -- If upload to USB fails but upload to `/data` works, treat it as a USB - permission or mount-path issue. -- Save the browser error and the latest `/data/BFpilot/log.txt` lines. - -### Copy/Delete Permission Denied - -- Test inside `/data/BFpilot-test` first. -- Avoid deleting root paths or system paths. -- For folder deletion, make sure recursive delete is explicitly requested by the - UI/API. -- If `/data` works but USB fails, collect `/api/diag` and note the source and - destination paths. -- Permission errors are useful compatibility data; record exact paths and the - operation that failed. - -## Firmware Testing - -Use [docs/FIRMWARE_TESTING.md](docs/FIRMWARE_TESTING.md) for the full -stage-by-stage protocol: - -- Stage A: core server only -- Stage B: notification enabled -- Stage C: launcher enabled -- Stage D: reinjection - -## Project Layout - -- `src/lite_main.c` starts the payload, handles reinjection handoff, applies the - launcher runtime flag, and starts the web server. -- `src/app_installer.c` writes launcher files and optionally registers the PS5 - home-screen tile in full mode. -- `src/sce_resolve.c` resolves optional SCE functions for full-mode launcher - support. -- `src/diag.c` writes append logs, crash logs, checkpoints, and runtime - diagnostics. -- `src/websrv_lite.c` serves the UI, downloads under `/fs`, `/api/status`, - `/api/diag`, and file-manager APIs. -- `src/transfer.c` contains upload, copy, move, delete, and long-running job - handling. -- `assets/files.html` is the full web UI that gets embedded into the ELF. -- `assets/icon.png` is the smaller web UI icon. -- `assets-app/` contains the launcher tile metadata and icon. - -Generated files go under `gen/` during build. Build intermediates go under -`build/`. Release ELF files should be attached to GitHub Releases only after the -release checklist is complete. +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()`. ## Build -You need: - -- `ps5-payload-sdk` -- GNU `make` -- Python 3 -- LLVM/Clang tools that work with the PS5 payload SDK -- A shell that can run the SDK build commands - -Build both release files: - ```sh export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk make clean all ``` -Build one mode: +Individual targets: ```sh -make core +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-core.elf +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 ``` -Deploy helper: +The compatibility check must pass for release builds: ```sh -make deploy-core PS5_HOST= PS5_PORT=9021 -make deploy-full PS5_HOST= PS5_PORT=9021 +make inspect-imports ``` -## Extra Notes +It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain AppInstUtil, +`sceAppInst`, `app_installer`, or `BFPL00001` installer fingerprints. Direct +`libSceAppInstUtil.sprx` import is allowed only for +`bfpilot-launcher-installer.elf` and +`tests/installer_linkonly_appinst.elf`. -BFpilot avoids kernel-level cleanup and does not force-unload unrelated -payloads. +## Project Layout -If you reinject while an older BFpilot or BS5FileManager instance is already -running, the new payload checks whether a file job is active. If the old -instance is idle, it asks it to shut down cleanly and then takes over port -`5905`. If a copy, move, or delete is running, the new injection exits instead -of interrupting the active job. +- `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. -Cancel is cooperative. Large operations may take a moment to stop, especially on -external USB drives, but the job code checks for cancellation during scan, copy, -move cleanup, and delete. - -## Release Checklist - -Complete this before publishing `v0.2.1`: - -- Confirm `VERSION_TAG` is `bfpilot-v0.2.1`. -- Build from clean with `make clean all`. -- Confirm both files exist: `bfpilot-core.elf` and `bfpilot-full.elf`. -- Confirm `bfpilot-core.elf` is the recommended download in release notes. -- Confirm core mode does not include launcher installer objects. -- Confirm core mode does not link launcher-only SCE libraries. -- Confirm full mode treats launcher failure as non-fatal. -- Run Stage A from `docs/FIRMWARE_TESTING.md` on each available firmware. -- Run Stage C only after Stage A passes on that firmware. -- Save `/api/diag` output for tested firmware/loader combinations. -- Save `/data/BFpilot/log.txt` for failed tests. -- Fill in the compatibility table in `docs/FIRMWARE_TESTING.md`. -- Attach both ELFs to the release. -- In the release notes, tell users to try `bfpilot-core.elf` first. +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. diff --git a/assets/files.html b/assets/files.html index d62acc6..6cf6ed7 100644 --- a/assets/files.html +++ b/assets/files.html @@ -651,7 +651,8 @@ uploadBusy: false, uploadAbort: false, uploadXhr: null, - jobTimer: 0 + jobTimer: 0, + jobHideTimer: 0 }; function el(id) { @@ -792,6 +793,10 @@ clearInterval(state.jobTimer); state.jobTimer = 0; } + if (state.jobHideTimer) { + clearTimeout(state.jobHideTimer); + state.jobHideTimer = 0; + } el('job').classList.add('show'); el('job-verb').textContent = verb || 'Working'; el('job-text').textContent = text || ''; @@ -1075,6 +1080,7 @@ const done = await waitJobIdle(); if (done.error) throw new Error(done.error); } catch (err) { + if ((err.message || '').toLowerCase().includes('job')) pollJob(); toast(verb + ': ' + err.message, 'err'); break; } @@ -1153,6 +1159,7 @@ const done = await waitJobIdle(); if (done.error) throw new Error(done.error); } catch (err) { + if ((err.message || '').toLowerCase().includes('job')) pollJob(); toast('delete: ' + err.message, 'err'); break; } @@ -1250,7 +1257,9 @@ toast(ok + ' uploaded' + (failed ? ', ' + failed + ' failed' : ''), failed ? 'err' : 'ok'); } setTimeout(() => { - if (!state.uploadBusy && !state.jobTimer) el('job').classList.remove('show'); + if (!state.uploadBusy && !state.jobTimer && !state.jobHideTimer) { + el('job').classList.remove('show'); + } }, 1600); } @@ -1261,6 +1270,10 @@ const verb = el('job-verb'); if (state.jobTimer) clearInterval(state.jobTimer); + if (state.jobHideTimer) { + clearTimeout(state.jobHideTimer); + state.jobHideTimer = 0; + } box.classList.add('show'); text.textContent = 'Starting'; let missed = 0; @@ -1279,7 +1292,17 @@ clearInterval(state.jobTimer); state.jobTimer = 0; if (job.error) toast(job.error, 'err'); - setTimeout(() => box.classList.remove('show'), 1400); + state.jobHideTimer = setTimeout(async () => { + state.jobHideTimer = 0; + try { + const latest = await api('/api/fs/job/status'); + if (latest.busy) { + pollJob(); + return; + } + } catch (err) {} + if (!state.uploadBusy && !state.jobTimer) box.classList.remove('show'); + }, 1400); } } catch (err) { missed++; diff --git a/docs/COMPATIBILITY_STRATEGY.md b/docs/COMPATIBILITY_STRATEGY.md new file mode 100644 index 0000000..2aa85c6 --- /dev/null +++ b/docs/COMPATIBILITY_STRATEGY.md @@ -0,0 +1,192 @@ +# BFpilot Compatibility Strategy + +BFpilot now separates the stable file manager from the fragile launcher +installer path. + +## Payload Roles + +### `bfpilot.elf` + +This is the main release payload. It is firmware-light and should be tested +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. + +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. + +### `bfpilot-launcher-installer.elf` + +This optional payload installs or refreshes `/user/app/BFPL00001`. + +It is intentionally isolated from the file manager: + +- It direct-links AppInstUtil, matching the launcher-install pattern used by + websrv, Payload Manager, and ftpsrv. +- It logs to `/data/BFpilot/launcher-installer.log`. +- It cannot break the file manager because it is not part of `bfpilot.elf`. + +If `bfpilot.elf` works but the launcher installer fails, the issue is the +AppInstUtil/launcher path, not the file manager. + +### `bfpilot-launcher-installer-safe.elf` + +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. + +## Launcher/AppInst Findings + +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. + +On that combination, the safe runtime path cannot install. The force installer +uses the fragile direct AppInst import path because that is the pattern used by +working launcher installers. If the loader rejects that import before `main()`, +no code inside that ELF can recover. The file manager remains supported because +it avoids that import entirely. + +## 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`. + +Some payloads also depend on a system-authid or other elevated runtime context +before AppInst init can succeed on newer firmware. BFpilot does not implement +authid changes, kernel patches, kstuff integration, forced unloads, package +installers, or DRM-related behavior. Those are outside the compatibility goal +for the standalone file manager. + +## Boot Marker + +Every ELF calls: + +```c +bfpilot_boot_marker(payload_name, build_mode); +``` + +as the first meaningful line in `main()`. + +The marker: + +- Prints `BFpilot BOOT: 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. + +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()`. + +## Reload Behavior + +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. + +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: + +```text +--port 5906 +``` + +Use this to test a new build while an old BFpilot instance is still running on +`5905`. + +## Import Inspection + +Run: + +```sh +make inspect-imports +``` + +The target prints file size, dynamic libraries/imports, undefined symbols, and +SCE-related symbol/string hints for: + +- `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: + +- `SceAppInstUtil` +- `sceAppInst` +- `app_installer` +- `BFPL00001` + +The only payloads allowed to directly import AppInstUtil are +`bfpilot-launcher-installer.elf` and +`tests/installer_linkonly_appinst.elf`. The link-only test exists specifically +to prove whether that loader/firmware rejects AppInstUtil-linked ELFs before +`main()`. diff --git a/docs/FIRMWARE_TESTING.md b/docs/FIRMWARE_TESTING.md index 7c2de59..3bc69ea 100644 --- a/docs/FIRMWARE_TESTING.md +++ b/docs/FIRMWARE_TESTING.md @@ -1,189 +1,212 @@ # BFpilot Firmware Testing -This protocol checks whether BFpilot runs safely across jailbroken PS5 firmwares and loaders. It does not require exploit changes, kernel patching, package loading, or DRM-related features. +Use this protocol on every firmware/loader combination. Test the file manager +first. Test launcher/AppInstUtil only after the file manager works. -Use `bfpilot-core.elf` first on every firmware. Only test `bfpilot-full.elf` after the core server is confirmed working. - -## Before Testing - -Record these details before each run: - -- PS5 firmware version. -- Exploit and loader name/version. -- Payload file used: `bfpilot-core.elf` or `bfpilot-full.elf`. -- PS5 IP address. -- Sender tool and command used. -- Whether `/data/BFpilot/log.txt` and `/data/BFpilot/crash.log` exist after the run. - -Clear old browser tabs that point to BFpilot before reinjecting. If a stage fails, keep the PS5 powered on long enough to collect `/data/BFpilot/log.txt`, `/data/BFpilot/crash.log`, `/api/status`, and `/api/diag` if reachable. - -## Stage A: Core Server Only - -Purpose: prove the firmware and loader can run BFpilot's standalone HTTP file manager without launcher installation. - -1. Send `bfpilot-core.elf` to the loader on port `9021`. -2. Verify the sender console reports that the ELF was delivered without socket/send errors. -3. Watch for BFpilot startup output from the loader console, if available. -4. Confirm `/data/BFpilot/log.txt` is created or appended. -5. Open `http://PS5_IP:5905/` in a browser on the same network. -6. Open `http://PS5_IP:5905/api/status`. -7. Open `http://PS5_IP:5905/api/diag`. -8. Browse `/data` in the file manager. -9. Browse `/mnt/usb0` with an external drive connected. -10. Create or open `/data/BFpilot-test`. -11. Upload a small text file to `/data/BFpilot-test`. -12. Copy that text file to another name in `/data/BFpilot-test`. -13. Move or rename the copied file. -14. Delete the moved file. -15. Delete the original uploaded test file. -16. Reopen `/api/diag` and save the output. - -Pass criteria: - -- The server starts on port `5905`. -- `/`, `/api/status`, `/api/diag`, `/fs`, and `/api/fs/*` are reachable. -- Browsing `/data` works. -- Browsing `/mnt/usb0` either works or fails cleanly with a clear permission/path error. -- Upload, copy, move, and delete work inside `/data/BFpilot-test`. -- No crash log is created during normal file operations. - -Fail data to collect: - -- Sender console output. -- `/data/BFpilot/log.txt`. -- `/data/BFpilot/crash.log`, if present. -- The HTTP status and response body from `/api/status` and `/api/diag`, if reachable. -- Exact browser error if the page times out. - -## Stage B: Notification Enabled - -Purpose: verify notification support is optional and cannot stop the web server. - -1. Start from a PS5 state where Stage A passed. -2. Send `bfpilot-core.elf` again, or start `bfpilot-full.elf` with launcher disabled using `--no-launcher` or `BFPILOT_NO_LAUNCHER=1` if the loader supports arguments/environment. -3. Trigger the BFpilot notification path by starting the payload and watching for the startup notification attempt. -4. Open `http://PS5_IP:5905/`. -5. Open `http://PS5_IP:5905/api/diag`. -6. Check `/data/BFpilot/log.txt` for notification return code entries. - -Pass criteria: - -- If a notification appears, the server still starts. -- If the notification fails, the server still starts. -- `/api/diag` remains reachable after the notification attempt. -- The log records the notification result when available. - -Fail data to collect: +Record: +- Firmware version. +- Exploit/loader name and version. +- Payload name. +- Whether `/data/BFpilot/boot.log` received a new entry. - Whether a notification appeared. -- Notification return code from `/data/BFpilot/log.txt`. -- `/api/diag` output after startup. +- Whether the HTTP server started. +- `/api/status` and `/api/diag` output when reachable. +- Relevant log files under `/data/BFpilot`. -## Stage C: Launcher Enabled +## Stage A: Main File Manager -Purpose: verify full mode can attempt launcher install/refresh without making launcher failure fatal. +1. Send `bfpilot.elf` to the loader on port `9021`. +2. Verify `BFpilot BOOT` notification if notifications are working. +3. Verify `/data/BFpilot/boot.log` has a new `bfpilot file-manager` entry. +4. Open `http://PS5_IP:5905/`. +5. Open `http://PS5_IP:5905/api/status`. +6. Open `http://PS5_IP:5905/api/diag`. +7. Browse `/data`. +8. Browse `/mnt/usb0` if an external drive is connected. +9. Upload a small text file to `/data/BFpilot-test`. +10. Copy, move, and delete that test file. +11. Save `/data/BFpilot/log.txt`. -Only run this stage after Stage A passes on the same firmware and loader. +Pass criteria: + +- Boot marker appears. +- Server starts on port `5905`. +- `/`, `/api/status`, `/api/diag`, `/fs`, and `/api/fs/*` are reachable. +- Basic file operations work in `/data/BFpilot-test`. + +## Stage B: Debug File Manager + +Use this when Stage A appears to do nothing or when testing a new loader. + +1. Send `bfpilot-debug.elf` to the loader on port `9021`. +2. Verify `/data/BFpilot/boot.log` has a new `bfpilot-debug debug` entry. +3. Open `http://PS5_IP:5905/api/status`. +4. Open `http://PS5_IP:5905/api/diag`. +5. Save `/data/BFpilot/log.txt`. + +Expected result: + +- If `boot.log` has no new entry, the payload likely failed before `main()` or + the loader rejected it. +- If `boot.log` has an entry but `/api/status` is unreachable, the failure is + after `main()` and before or during server startup. + +## Stage C: Alternate Port + +Use this when another BFpilot instance may already be running on `5905`. + +1. Send `bfpilot-debug.elf --port 5906` to the loader. +2. Open `http://PS5_IP:5906/api/status`. +3. Open `http://PS5_IP:5906/api/diag`. +4. Save `/data/BFpilot/log.txt`. + +Pass criteria: + +- The new payload can start on `5906` without touching the old server on `5905`. + +## Stage D: Reinjection + +1. Start `bfpilot.elf`. +2. Reinject `bfpilot.elf` while idle. +3. Verify notification `BFpilot reload: old server detected`. +4. Verify the old listener stops cleanly and the new server starts. +5. Start a large copy or move. +6. Reinject `bfpilot.elf` during the active job. +7. Verify notification `BFpilot reload blocked`. +8. Verify the old job is not killed. +9. Save `/data/BFpilot/log.txt`. + +Pass criteria: + +- Idle reinjection performs a clean handoff. +- Active file jobs are not interrupted. +- Reload exits are logged and notified instead of appearing silent. + +## Stage E: Launcher Installer + +Only run this after Stage A passes on the same firmware/loader. + +### Integrated Full Build + +This is the first installer-capable build to try on firmware 11.6, because it +preserves the 0.2.0-style launcher flow that already installed there. 1. Send `bfpilot-full.elf` to the loader on port `9021`. -2. Watch for launcher install or refresh messages. +2. Watch for `BFpilot BOOT`, `BFpilot app`, and `BFpilot started` + notifications. 3. Open `http://PS5_IP:5905/`. -4. Open `http://PS5_IP:5905/api/status`. -5. Open `http://PS5_IP:5905/api/diag`. -6. Record the launcher fields from `/api/diag`. -7. Check whether the BFpilot launcher tile appears or refreshes on the PS5 home screen. -8. If the launcher tile does not appear, keep testing the web server anyway. -9. Browse `/data`. -10. Upload a small text file to `/data/BFpilot-test`. -11. Delete the uploaded test file. -12. Save `/data/BFpilot/log.txt`. +4. Save `/data/BFpilot/log.txt`. +5. Open `http://PS5_IP:5905/api/diag` if the web server starts. +6. Check whether the BFpilot tile appears or refreshes. + +Pass criteria: + +- On firmware where AppInstUtil runtime resolution works, the tile installs or + refreshes. +- If launcher install fails, the web server still starts and logs the exact + AppInst resolution/init/install return codes. + +### Isolated Direct Installer + +Use this after Stage A if you want to test the direct AppInst import pattern +used by websrv/Payload Manager/ftpsrv. It may fail before `main()` if the loader +rejects AppInstUtil imports. + +1. Send `bfpilot-launcher-installer.elf` to the loader on port `9021`. +2. Watch for `BFpilot BOOT` and launcher installer notifications. +3. Check whether the BFpilot tile appears or refreshes. +4. Save `/data/BFpilot/launcher-installer.log`. +5. Save `/data/BFpilot/boot.log`. + +Launcher installer fields to record from the log: + +- `entered main`. +- `AppInst init return code`. +- `/user/app/BFPL00001` mkdir return code. +- `param.json` and `icon0.png` write return code. +- `AppInstallTitleDir resolved`. +- `AppInstallTitleDir return code`. +- `AppInstallAll fallback return code`. +- `final result`. Pass criteria: - Launcher install may succeed or fail. -- Launcher failure must not stop the HTTP server. -- `/api/diag` records AppInstUtil resolution and return codes. -- File browsing and basic file operations still work after any launcher failure. +- Launcher failure does not affect `bfpilot.elf`. +- File manager remains testable through `http://PS5_IP:5905/`. -Launcher fields to record: +If the direct installer gives no notification and no log: -- `launcher_enabled`. -- `launcher_attempted`. -- `appinst_init_rc`. -- `title_dir_resolved`. -- `install_title_dir_resolved`. -- `install_title_rc`. -- `uninstall_resolved`. -- `uninstall_rc`. -- `install_all_resolved`. -- `install_all_rc`. -- `user_app_writable`. -- `launcher_install_rc`. -- `launcher_final_state`. +1. Send `bfpilot-launcher-installer-safe.elf`. +2. Save `/data/BFpilot/launcher-installer.log`. +3. If the safe installer reaches `main()` but logs + `kernel_dynlib_handle libSceAppInstUtil.sprx rc=0xffffffff`, AppInstUtil is + not available through the safe runtime path. +4. If `tests/installer_linkonly_appinst.elf` also produces no marker, direct + AppInst imports fail before `main()` on that loader/firmware. -Fail data to collect: +In that case the direct installer is being rejected before it can run, while +the main file manager remains usable through `http://PS5_IP:5905/`. -- The last launcher-related lines from `/data/BFpilot/log.txt`. -- Full `/api/diag` output. -- Whether the tile appeared, refreshed, disappeared, or did not change. -- Whether the web server continued running after launcher failure. +## Stage F: Minimal Probe Payloads -## Stage D: Reinjection +Use these to isolate loader, notification, HTTP, and AppInstUtil behavior. -Purpose: verify reinjecting BFpilot does not kill unrelated payloads or interrupt active work in an unsafe way. +### `tests/hello_boot.elf` -1. Start `bfpilot-core.elf`. -2. Open `http://PS5_IP:5905/` and confirm the server is idle. -3. Inject `bfpilot-core.elf` again while idle. -4. Verify the web server still responds. -5. Open `/api/diag` and record uptime, PID, and any old-instance message. -6. Start a copy operation using a test file inside `/data/BFpilot-test`. -7. While the copy is active, inject `bfpilot-core.elf` again. -8. Verify the old copy job is not killed by the new injection. -9. Verify the web server remains reachable after the copy finishes. -10. Repeat the idle reinjection test once with `bfpilot-full.elf` only after full mode passed Stage C. +1. Send `tests/hello_boot.elf`. +2. Verify boot marker notification. +3. Verify `/data/BFpilot/boot.log`. +4. Wait 10 seconds for `BFpilot BOOT still alive`. -Pass criteria: +### `tests/hello_http.elf` -- Reinjection while idle leaves one reachable BFpilot server. -- Reinjection during active copy does not kill the active job. -- Existing unrelated payloads are not forcibly unloaded. -- Logs clearly state whether the new instance started, reused an existing state, or exited because another BFpilot instance was already active. +1. Send `tests/hello_http.elf`. +2. Open `http://PS5_IP:5906/api/status`. -Fail data to collect: +### `tests/hello_notify.elf` -- `/data/BFpilot/log.txt` from before and after reinjection. -- `/api/diag` before reinjection, during copy, and after copy. -- File copy source, destination, size, and whether the destination completed correctly. -- Sender console output for each injection. +1. Send `tests/hello_notify.elf`. +2. Record notification result from sender output and `/data/BFpilot/boot.log`. + +### `tests/installer_enter_probe.elf` + +1. Send `tests/installer_enter_probe.elf`. +2. Verify `/data/BFpilot/installer_enter_probe.txt` exists. +3. If it does not exist, the loader rejected even the safe installer-shaped + probe before `main()`. + +### `tests/installer_linkonly_appinst.elf` + +1. Send `tests/installer_linkonly_appinst.elf`. +2. Verify `/data/BFpilot/linkonly_appinst_entered.txt` exists. +3. If it does not exist, direct AppInstUtil linking is incompatible with that + loader/firmware and installer code must avoid direct AppInst imports. + +### `tests/installer_runtime_resolve_appinst.elf` + +1. Send `tests/installer_runtime_resolve_appinst.elf`. +2. Verify `/data/BFpilot/runtime_resolve_entered.txt` exists. +3. Save `/data/BFpilot/runtime_resolve_appinst.log`. +4. Use the log to see whether `kernel_dynlib_handle`, symbol lookup, and NID + resolve reached AppInstUtil. + +Interpretation: + +- `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is not already + loaded in this process. +- BFpilot intentionally skips `dlopen` for AppInstUtil after a prior test stops + before the `dlopen` result log, because that path is not safe enough for a + compatibility payload. ## Compatibility Table -| firmware | exploit/loader | payload used | server starts | file browse | upload | copy | move | delete | launcher install | notes | -|---|---|---|---|---|---|---|---|---|---|---| -| 11.60 | | `bfpilot-core.elf` | | | | | | | N/A | | -| 11.60 | | `bfpilot-full.elf` | | | | | | | | | -| 12.70 | | `bfpilot-core.elf` | | | | | | | N/A | | -| 12.70 | | `bfpilot-full.elf` | | | | | | | | | -| | | `bfpilot-core.elf` | | | | | | | N/A | | -| | | `bfpilot-full.elf` | | | | | | | | | - -Use `yes`, `no`, or `partial` for result columns. Put exact error codes, HTTP status codes, and log checkpoints in `notes`. - -## Recommended Report Format - -```text -Firmware: -Exploit/loader: -Payload: -Sender command/tool: -PS5 IP: -Server URL: -Stage passed: -First failing step: -/api/status result: -/api/diag result: -Relevant log lines: -Crash log present: -Notes: -``` +| firmware | exploit/loader | payload used | boot marker | server starts | file browse | upload | copy | move | delete | launcher install | notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 12.70 | | bfpilot.elf | | | | | | | | n/a | | +| 12.70 | | bfpilot-full.elf | | | | | | | | | | +| 12.70 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | | +| 11.60 | | bfpilot.elf | | | | | | | | n/a | | +| 11.60 | | bfpilot-full.elf | | | | | | | | | | +| 11.60 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | | diff --git a/scripts/inspect_imports.sh b/scripts/inspect_imports.sh new file mode 100644 index 0000000..d5f1ffa --- /dev/null +++ b/scripts/inspect_imports.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +set -euo pipefail + +find_tool() { + local name="$1" + if [[ -n "${LLVM_BINDIR:-}" ]]; then + if [[ -x "${LLVM_BINDIR}/${name}" ]]; then + printf '%s\n' "${LLVM_BINDIR}/${name}" + return 0 + fi + if [[ -x "${LLVM_BINDIR}/${name}.exe" ]]; then + printf '%s\n' "${LLVM_BINDIR}/${name}.exe" + return 0 + fi + fi + if command -v "${name}" >/dev/null 2>&1; then + command -v "${name}" + return 0 + fi + if command -v "${name}.exe" >/dev/null 2>&1; then + command -v "${name}.exe" + return 0 + fi + return 1 +} + +READELF="$(find_tool llvm-readelf || find_tool readelf || true)" +READOBJ="$(find_tool llvm-readobj || true)" +NM="$(find_tool llvm-nm || find_tool nm || true)" +STRINGS="$(find_tool llvm-strings || find_tool strings || true)" +TMP_DIR="${TMPDIR:-/tmp}" + +if [[ -z "${STRINGS}" ]]; then + echo "inspect-imports: strings or llvm-strings is required" >&2 + exit 1 +fi + +if [[ $# -eq 0 ]]; then + echo "inspect-imports: no ELF files provided" >&2 + exit 1 +fi + +needed_libraries() { + local file="$1" + if [[ -n "${READOBJ}" ]]; then + "${READOBJ}" --needed-libs "${file}" 2>/dev/null | + grep -E '^[[:space:]]+[^[:space:]]+\.sprx$' | + sed -E 's/^[[:space:]]+//' + elif [[ -n "${READELF}" ]]; then + "${READELF}" -d "${file}" 2>/dev/null | + sed -nE 's/.*Shared library: \[([^]]+)\].*/\1/p' + fi +} + + +check_no_direct_appinst_import() { + local file="$1" + local needed + needed="$(needed_libraries "${file}" || true)" + if printf '%s\n' "${needed}" | + grep -E 'libSceAppInstUtil\.sprx|libSceSystemService\.sprx|libSceUserService\.sprx' >/dev/null; then + echo + echo "inspect-imports: unsafe direct launcher import in ${file}" >&2 + printf '%s\n' "${needed}" >&2 + return 1 + fi +} + + +for file in "$@"; do + echo + echo "== ${file} ==" + if [[ ! -f "${file}" ]]; then + echo "missing" + continue + fi + + size="$(wc -c < "${file}" | tr -d '[:space:]')" + echo "file size: ${size} bytes" + + echo "-- dynamic libraries/imports --" + if [[ -n "${READOBJ}" ]]; then + "${READOBJ}" --needed-libs "${file}" || true + elif [[ -n "${READELF}" ]]; then + "${READELF}" -d "${file}" | grep -E 'NEEDED|SONAME' || true + else + echo "no llvm-readobj/readelf available" + fi + + echo "-- undefined symbols --" + if [[ -n "${READELF}" ]]; then + "${READELF}" --dyn-symbols "${file}" | grep -E 'UND|Undefined' || true + elif [[ -n "${NM}" ]]; then + "${NM}" -u "${file}" || true + else + echo "no llvm-readelf/readelf/nm available" + fi + + echo "-- SCE-related strings/symbol hints --" + "${STRINGS}" "${file}" | grep -Ei '(^|[^A-Za-z0-9_])(sce|Sce|AppInst|SystemService|UserService|BFPL00001|app_installer)' || true +done + +for file in "$@"; do + case "${file}" in + bfpilot-launcher-installer.elf|./bfpilot-launcher-installer.elf) + continue + ;; + tests/installer_linkonly_appinst.elf|./tests/installer_linkonly_appinst.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}" | + 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 + 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 + if ! needed_libraries bfpilot-launcher-installer.elf | + grep -q 'libSceAppInstUtil\.sprx'; then + echo + echo "inspect-imports: bfpilot-launcher-installer.elf must directly import libSceAppInstUtil.sprx" >&2 + exit 1 + fi +fi + +echo +echo "inspect-imports: compatibility checks passed" diff --git a/scripts/scrub_main_payload.py b/scripts/scrub_main_payload.py new file mode 100644 index 0000000..4681279 --- /dev/null +++ b/scripts/scrub_main_payload.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Remove SDK startup-table launcher fingerprints from main BFpilot payloads.""" + +from __future__ import annotations + +import pathlib +import sys + + +REPLACEMENTS = { + b"libSceAppInstUtil.sprx": b"libBfpilotCleanMain.sx", +} + + +def scrub(path: pathlib.Path) -> int: + data = path.read_bytes() + changed = False + for old, new in REPLACEMENTS.items(): + if len(old) != len(new): + raise ValueError(f"replacement length mismatch for {old!r}") + if old in data: + data = data.replace(old, new) + changed = True + if changed: + path.write_bytes(data) + return 1 if changed else 0 + + +def main(argv: list[str]) -> int: + if len(argv) < 2: + print("usage: scrub_main_payload.py [...]", file=sys.stderr) + return 2 + for name in argv[1:]: + path = pathlib.Path(name) + changed = scrub(path) + print(f"scrub-main-payload: {path} changed={changed}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/src/app_installer.c b/src/app_installer.c index 2087ea2..3e1f05a 100644 --- a/src/app_installer.c +++ b/src/app_installer.c @@ -46,30 +46,27 @@ INCASSET(bfpilot_param_json, "assets-app/param.json"); INCASSET(bfpilot_icon0_png, "assets-app/icon0.png"); +typedef int (*appinst_initialize_fn)(void); +typedef int (*appinst_uninstall_fn)(const char *); typedef int (*app_install_title_dir_fn)(const char *, const char *, void *); - -int sceAppInstUtilInitialize(void); -int sceAppInstUtilAppInstallAll(void *); +typedef int (*appinst_install_all_fn)(void *); typedef struct appinst_api { + appinst_initialize_fn initialize; + appinst_uninstall_fn uninstall; app_install_title_dir_fn install_title_dir; + appinst_install_all_fn install_all; } appinst_api_t; 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 = -1, - .title_dir_resolved = 0, .install_title_dir_resolved = 0, - .install_title_rc = BFPILOT_DIAG_SKIPPED, .uninstall_resolved = 0, - .uninstall_rc = BFPILOT_DIAG_SKIPPED, .install_all_resolved = 0, - .install_all_rc = BFPILOT_DIAG_SKIPPED, .user_app_writable = -1, .launcher_install_rc = -1, - .launcher_final_state = "skipped", }; @@ -83,31 +80,25 @@ static void reset_launcher_diag(void) { bfpilot_launcher_diag_t diag = { .launcher_enabled = 1, - .launcher_attempted = 1, .appinst_init_rc = -1, - .title_dir_resolved = 0, .install_title_dir_resolved = 0, - .install_title_rc = BFPILOT_DIAG_SKIPPED, .uninstall_resolved = 0, - .uninstall_rc = BFPILOT_DIAG_SKIPPED, .install_all_resolved = 0, - .install_all_rc = BFPILOT_DIAG_SKIPPED, .user_app_writable = -1, .launcher_install_rc = -1, - .launcher_final_state = "skipped", }; g_launcher_diag = diag; publish_launcher_diag(); } -static void -set_launcher_final(const char *state, int rc) { - snprintf(g_launcher_diag.launcher_final_state, - sizeof(g_launcher_diag.launcher_final_state), "%s", - state ? state : "failed_nonfatal"); - g_launcher_diag.launcher_install_rc = rc; - 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; } @@ -115,20 +106,29 @@ static void resolve_appinst(appinst_api_t *api) { memset(api, 0, sizeof(*api)); - int rc = sce_resolve_symbol(BFPILOT_APPINST_MODULE, - BFPILOT_NID_INSTALL_TITLE_DIR, - (void **)&api->install_title_dir); - g_launcher_diag.title_dir_resolved = api->install_title_dir != NULL; + 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->uninstall, + "sceAppInstUtilAppUnInstall", NULL); + g_launcher_diag.uninstall_resolved = api->uninstall != NULL; + bfpilot_log("launcher resolve AppUnInstall %s rc=0x%08x", + api->uninstall ? "ok" : "missing", rc); + + rc = resolve_optional((void **)&api->install_title_dir, + "sceAppInstUtilAppInstallTitleDir", + BFPILOT_NID_INSTALL_TITLE_DIR); g_launcher_diag.install_title_dir_resolved = api->install_title_dir != NULL; - bfpilot_log("launcher resolve AppInstallTitleDir NID %s rc=0x%08x", + bfpilot_log("launcher resolve AppInstallTitleDir %s rc=0x%08x", api->install_title_dir ? "ok" : "missing", rc); - g_launcher_diag.uninstall_resolved = 0; - g_launcher_diag.uninstall_rc = BFPILOT_DIAG_SKIPPED; - bfpilot_log("launcher uninstall skipped for compatibility"); - - g_launcher_diag.install_all_resolved = 1; - bfpilot_log("launcher AppInstallAll uses linked SDK stub"); + 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(); } @@ -153,23 +153,26 @@ 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 { - g_launcher_diag.install_title_rc = BFPILOT_DIAG_SKIPPED; - publish_launcher_diag(); bfpilot_log("launcher install AppInstallTitleDir not resolved"); } - int err = sceAppInstUtilAppInstallAll(NULL); - g_launcher_diag.install_all_rc = err; - g_launcher_diag.launcher_install_rc = err; + if(api->install_all) { + int err = api->install_all(NULL); + 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 rc=0x%08x", err); - return err; + bfpilot_log("launcher install AppInstallAll not resolved"); + return -2; } @@ -264,6 +267,7 @@ bfpilot_install_app_if_needed(void) { char sce_sys_dir[256]; char param_path[256]; char icon_path[256]; + char msg[128]; struct stat st; reset_launcher_diag(); @@ -274,6 +278,22 @@ bfpilot_install_app_if_needed(void) { bfpilot_log("launcher install %s writable=%d", BFPILOT_APP_ROOT, g_launcher_diag.user_app_writable); + if(!api.initialize) { + g_launcher_diag.launcher_install_rc = -2; + 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; + 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); @@ -297,19 +317,35 @@ bfpilot_install_app_if_needed(void) { bfpilot_notify("BFpilot app", "Installing PS5 home-screen launcher"); } - int err = sceAppInstUtilInitialize(); + 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); - set_launcher_final("failed_nonfatal", err); + g_launcher_diag.launcher_install_rc = err; + publish_launcher_diag(); + snprintf(msg, sizeof(msg), "AppInst init failed 0x%08x", err); + bfpilot_notify("BFpilot app failed", msg); return -1; } - g_launcher_diag.uninstall_rc = BFPILOT_DIAG_SKIPPED; - publish_launcher_diag(); - bfpilot_log("launcher install AppUnInstall skipped"); + if(api.uninstall) { + int uninstall_err = api.uninstall(BFPILOT_APP_TITLE_ID); + bfpilot_log("launcher install refresh BFpilot tile rc=0x%08x", + uninstall_err); + uninstall_err = api.uninstall(BFPILOT_INVALID_APP_TITLE_ID); + bfpilot_log("launcher install remove invalid BFpilot tile rc=0x%08x", + uninstall_err); + uninstall_err = api.uninstall(BFPILOT_LEGACY_APP_TITLE_ID); + bfpilot_log("launcher install remove BS5FileManager tile rc=0x%08x", + uninstall_err); + uninstall_err = api.uninstall(BFPILOT_OLD_LEGACY_APP_TITLE_ID); + bfpilot_log("launcher install remove old legacy tile rc=0x%08x", + uninstall_err); + } else { + bfpilot_log("launcher install AppUnInstall not resolved, skipping removes"); + } char invalid_dir[256]; snprintf(invalid_dir, sizeof(invalid_dir), BFPILOT_APP_ROOT "/%s", @@ -321,28 +357,36 @@ bfpilot_install_app_if_needed(void) { if(mkdir_if_needed(app_dir) != 0 || mkdir_if_needed(sce_sys_dir) != 0) { bfpilot_log("launcher install mkdir failed errno=%d", errno); - set_launcher_final("failed_nonfatal", -errno); + g_launcher_diag.launcher_install_rc = -errno; + 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); - set_launcher_final("failed_nonfatal", -errno); + g_launcher_diag.launcher_install_rc = -errno; + 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); - set_launcher_final("failed_nonfatal", -errno); + g_launcher_diag.launcher_install_rc = -errno; + 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) { bfpilot_log("launcher install install_app failed rc=0x%08x", err); - set_launcher_final("failed_nonfatal", err); + snprintf(msg, sizeof(msg), "register BFPL00001 failed 0x%08x", err); + bfpilot_notify("BFpilot app failed", msg); return -1; } @@ -355,7 +399,6 @@ bfpilot_install_app_if_needed(void) { BFPILOT_MARKER_PATH, errno); } - set_launcher_final("installed", 0); bfpilot_notify("BFpilot app ready", "Tile BFPL00001 opens http://127.0.0.1:5905/"); return 1; diff --git a/src/boot_marker.c b/src/boot_marker.c new file mode 100644 index 0000000..da4cf32 --- /dev/null +++ b/src/boot_marker.c @@ -0,0 +1,77 @@ +/* + * BFpilot - early payload entry marker. + */ + +#include "boot_marker.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "notify.h" +#include "version.h" + +#ifndef VERSION_TAG +#define VERSION_TAG "bfpilot-unknown" +#endif + +#ifndef BUILD_VERSION +#define BUILD_VERSION "unknown" +#endif + +#ifndef BFPILOT_BOOT_NOTIFY +#define BFPILOT_BOOT_NOTIFY 1 +#endif + +#define BFPILOT_DATA_ROOT "/data" +#define BFPILOT_DATA_DIR "/data/BFpilot" +#define BFPILOT_BOOT_LOG_PATH "/data/BFpilot/boot.log" + + +static void +mkdir_ignore(const char *path) { + if(mkdir(path, 0755) != 0 && errno != EEXIST) { + return; + } +} + + +void +bfpilot_boot_marker(const char *payload_name, const char *build_mode) { + const char *payload = payload_name && payload_name[0] ? + payload_name : "BFpilot"; + const char *mode = build_mode && build_mode[0] ? build_mode : "unknown"; + char line[512]; + char notify_msg[128]; + + printf("BFpilot BOOT: %s %s entered main\n", payload, mode); + fflush(stdout); + + mkdir_ignore(BFPILOT_DATA_ROOT); + mkdir_ignore(BFPILOT_DATA_DIR); + + int n = snprintf(line, sizeof(line), + "%ld payload=%s mode=%s pid=%ld tag=%s build=%s entered main\n", + (long)time(NULL), payload, mode, (long)getpid(), + VERSION_TAG, BUILD_VERSION); + if(n > 0) { + if((size_t)n >= sizeof(line)) n = (int)sizeof(line) - 1; + int fd = open(BFPILOT_BOOT_LOG_PATH, + O_WRONLY | O_CREAT | O_APPEND, 0600); + if(fd >= 0) { + (void)write(fd, line, (size_t)n); + close(fd); + } + } + +#if BFPILOT_BOOT_NOTIFY + snprintf(notify_msg, sizeof(notify_msg), "%s loaded", payload); + (void)bfpilot_notify_send("BFpilot BOOT", notify_msg); +#else + (void)notify_msg; +#endif +} diff --git a/src/boot_marker.h b/src/boot_marker.h new file mode 100644 index 0000000..c7413ae --- /dev/null +++ b/src/boot_marker.h @@ -0,0 +1,7 @@ +/* + * BFpilot - early payload entry marker. + */ + +#pragma once + +void bfpilot_boot_marker(const char *payload_name, const char *build_mode); diff --git a/src/launcher_installer_force_main.c b/src/launcher_installer_force_main.c new file mode 100644 index 0000000..d97c55f --- /dev/null +++ b/src/launcher_installer_force_main.c @@ -0,0 +1,252 @@ +/* + * BFpilot - direct AppInst launcher tile installer. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "boot_marker.h" +#include "notify.h" +#include "version.h" + +#define BFPILOT_APP_TITLE_ID "BFPL00001" +#define BFPILOT_APP_ROOT "/user/app" +#define BFPILOT_APP_PARENT BFPILOT_APP_ROOT "/" +#define BFPILOT_DATA_ROOT "/data" +#define BFPILOT_DATA_DIR "/data/BFpilot" +#define BFPILOT_LAUNCHER_LOG "/data/BFpilot/launcher-installer.log" +#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"); + +int sceAppInstUtilInitialize(void); +int sceAppInstUtilAppInstallAll(void *); +int sceAppInstUtilAppUnInstall(const char *); + +typedef int (*app_install_title_dir_fn)(const char *, const char *, 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; +} + + +static app_install_title_dir_fn +resolve_title_dir(void) { + uint32_t handle = 0; + installer_log("about to kernel_dynlib_handle %s", BFPILOT_APPINST_MODULE); + int handle_rc = kernel_dynlib_handle(-1, BFPILOT_APPINST_MODULE, &handle); + installer_log("kernel_dynlib_handle %s rc=0x%08x handle=0x%08x", + BFPILOT_APPINST_MODULE, handle_rc, handle); + if(handle_rc != 0) return NULL; + + installer_log("about to kernel_dynlib_resolve AppInstallTitleDir nid=%s", + BFPILOT_NID_INSTALL_TITLE_DIR); + app_install_title_dir_fn fn = + (app_install_title_dir_fn)kernel_dynlib_resolve( + -1, handle, BFPILOT_NID_INSTALL_TITLE_DIR); + installer_log("kernel_dynlib_resolve AppInstallTitleDir addr=%p", + (void *)fn); + return fn; +} + + +int +main(void) { + bfpilot_boot_marker("bfpilot-launcher-installer", BFPILOT_BUILD_MODE); + + char app_dir[256]; + char sce_sys_dir[256]; + char param_path[256]; + char icon_path[256]; + int title_dir_rc = BFPILOT_DIAG_SKIPPED; + int install_all_rc = BFPILOT_DIAG_SKIPPED; + int uninstall_rc = BFPILOT_DIAG_SKIPPED; + int final_rc = -1; + + installer_log("entered main direct-appinst tag=%s build=%s", + VERSION_TAG, BUILD_VERSION); + bfpilot_notify("BFpilot launcher installer", "direct AppInst installer"); + + installer_log("about to sceAppInstUtilInitialize"); + int appinst_init_rc = sceAppInstUtilInitialize(); + installer_log("AppInst init return code=0x%08x", appinst_init_rc); + if(appinst_init_rc != 0) { + installer_log("final result=failed_nonfatal final_rc=%d " + "appinst_init_rc=0x%08x", final_rc, appinst_init_rc); + bfpilot_notify("BFpilot launcher installer", "AppInst init failed"); + 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); + + installer_log("about to sceAppInstUtilAppUnInstall %s", + BFPILOT_APP_TITLE_ID); + uninstall_rc = sceAppInstUtilAppUnInstall(BFPILOT_APP_TITLE_ID); + installer_log("uninstall return code=0x%08x", uninstall_rc); + + errno = 0; + int 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; + int 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; + int 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; + int 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); + + app_install_title_dir_fn install_title_dir = resolve_title_dir(); + installer_log("AppInstallTitleDir resolved=%s nid=%s", + install_title_dir ? "yes" : "no", + BFPILOT_NID_INSTALL_TITLE_DIR); + + if(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) { + installer_log("about to sceAppInstUtilAppInstallAll fallback"); + install_all_rc = sceAppInstUtilAppInstallAll(NULL); + installer_log("AppInstallAll fallback return code=0x%08x", + install_all_rc); + } 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; + } + + installer_log("final result=%s final_rc=%d appinst_init_rc=0x%08x " + "uninstall_rc=0x%08x mkdir_app_rc=%d mkdir_sce_sys_rc=%d " + "write_param_rc=%d write_icon_rc=%d title_dir_rc=0x%08x " + "install_all_rc=0x%08x", + final_rc == 0 ? "installed" : "failed_nonfatal", final_rc, + appinst_init_rc, uninstall_rc, mkdir_app_rc, + mkdir_sce_sys_rc, write_param_rc, write_icon_rc, + title_dir_rc, install_all_rc); + + if(final_rc == 0) { + bfpilot_notify("BFpilot launcher installer", "installed or refreshed"); + return 0; + } + + bfpilot_notify("BFpilot launcher installer", "failed; see installer log"); + return 1; +} diff --git a/src/launcher_installer_main.c b/src/launcher_installer_main.c new file mode 100644 index 0000000..ac57fb6 --- /dev/null +++ b/src/launcher_installer_main.c @@ -0,0 +1,288 @@ +/* + * BFpilot - isolated optional launcher tile installer. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "boot_marker.h" +#include "notify.h" +#include "version.h" + +#define BFPILOT_APP_TITLE_ID "BFPL00001" +#define BFPILOT_APP_ROOT "/user/app" +#define BFPILOT_APP_PARENT BFPILOT_APP_ROOT "/" +#define BFPILOT_DATA_ROOT "/data" +#define BFPILOT_DATA_DIR "/data/BFpilot" +#define BFPILOT_LAUNCHER_LOG "/data/BFpilot/launcher-installer.log" +#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("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; +} diff --git a/src/lite_main.c b/src/lite_main.c index a939a3b..7d14fd4 100644 --- a/src/lite_main.c +++ b/src/lite_main.c @@ -6,7 +6,6 @@ */ #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include +#include "boot_marker.h" #include "diag.h" #include "notify.h" #include "version.h" @@ -32,9 +32,20 @@ #include "sce_resolve.h" #endif -#define BFPILOT_WEB_PORT 5905 #define BFPILOT_RELOAD_TOKEN "bs5fm-local-reload" +#ifndef BFPILOT_WEB_PORT +#define BFPILOT_WEB_PORT 5905 +#endif + +#ifndef BFPILOT_PAYLOAD_NAME +#define BFPILOT_PAYLOAD_NAME "bfpilot" +#endif + +#ifndef BFPILOT_DEBUG_NOTIFICATIONS +#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 *); @@ -43,7 +54,6 @@ typedef int (*sce_user_service_initialize_fn)(void *); #define BFPILOT_USER_SERVICE_MODULE "libSceUserService.sprx" #endif - static void detect_lan_ip(char *out, size_t out_size) { struct ifaddrs *ifaddr = NULL; @@ -71,11 +81,50 @@ detect_lan_ip(char *out, size_t out_size) { typedef struct ready_state { char ip[64]; int notified; - int launcher_disabled; - int launcher_started; + unsigned short port; } ready_state_t; +static void +debug_notify(const char *title, const char *message) { +#if BFPILOT_DEBUG_NOTIFICATIONS + bfpilot_notify(title, message); +#else + (void)title; + (void)message; +#endif +} + + +static unsigned short +parse_port_value(const char *value, unsigned short fallback) { + if(!value || !value[0]) return fallback; + char *end = NULL; + long port = strtol(value, &end, 10); + if(!end || *end != 0 || port < 1 || port > 65535) { + return fallback; + } + return (unsigned short)port; +} + + +static unsigned short +runtime_port_requested(int argc, char **argv) { + unsigned short port = BFPILOT_WEB_PORT; + + for(int i = 1; i < argc; i++) { + if(!argv || !argv[i]) continue; + if(!strcmp(argv[i], "--port") && i + 1 < argc) { + port = parse_port_value(argv[++i], port); + } else if(!strncmp(argv[i], "--port=", 7)) { + port = parse_port_value(argv[i] + 7, port); + } + } + + return port; +} + + #if BFPILOT_ENABLE_LAUNCHER static void init_ps5_services(void) { @@ -116,71 +165,6 @@ init_ps5_services(void) { #endif -#if BFPILOT_ENABLE_LAUNCHER -typedef struct launcher_thread_arg { - char ip[64]; -} launcher_thread_arg_t; - - -static void * -launcher_thread(void *arg) { - launcher_thread_arg_t *state = arg; - char fallback[128]; - - snprintf(fallback, sizeof(fallback), "Use http://%s:%u/", - state && state->ip[0] ? state->ip : "", - (unsigned int)BFPILOT_WEB_PORT); - - bfpilot_checkpoint("launcher started"); - bfpilot_diag_set_launcher_status("started", 0); - bfpilot_log("launcher started after web server"); - 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_nonfatal", app_install_status); - bfpilot_log("launcher skipped rc=%d, web server remains available", - app_install_status); - bfpilot_notify("BFpilot launcher skipped", fallback); - puts(" ps5 app: launcher skipped, web server remains available"); - } - - free(state); - return NULL; -} - - -static void -start_launcher_thread(const char *ip) { - launcher_thread_arg_t *state = calloc(1, sizeof(*state)); - if(!state) { - bfpilot_diag_set_launcher_status("failed_nonfatal", -ENOMEM); - bfpilot_log("launcher skipped: failed allocating thread state"); - bfpilot_notify("BFpilot launcher skipped", "Use http://:5905/"); - return; - } - snprintf(state->ip, sizeof(state->ip), "%s", ip ? ip : ""); - - pthread_t thread; - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - int rc = pthread_create(&thread, &attr, launcher_thread, state); - pthread_attr_destroy(&attr); - if(rc != 0) { - bfpilot_diag_set_launcher_status("failed_nonfatal", -rc); - bfpilot_log("launcher skipped: pthread_create rc=%d", rc); - bfpilot_notify("BFpilot launcher skipped", "Use http://:5905/"); - free(state); - } -} -#endif - - static int launcher_disabled_requested(int argc, char **argv) { #if !BFPILOT_ENABLE_LAUNCHER @@ -195,11 +179,9 @@ launcher_disabled_requested(int argc, char **argv) { !strcasecmp(env_value, "yes") || !strcasecmp(env_value, "on")); - if(env_enabled) { - return 1; - } + if(env_enabled) return 1; for(int i = 1; i < argc; i++) { - if(argv[i] && !strcmp(argv[i], "--no-launcher")) { + if(argv && argv[i] && !strcmp(argv[i], "--no-launcher")) { return 1; } } @@ -209,7 +191,8 @@ launcher_disabled_requested(int argc, char **argv) { static int -local_http_get(const char *path, char *out, size_t out_size) { +local_http_get(unsigned short port, const char *path, char *out, + size_t out_size) { if(out_size == 0) return -1; out[0] = 0; @@ -225,7 +208,7 @@ local_http_get(const char *path, char *out, size_t out_size) { struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; - addr.sin_port = htons(BFPILOT_WEB_PORT); + addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { @@ -239,7 +222,7 @@ local_http_get(const char *path, char *out, size_t out_size) { "Host: 127.0.0.1:%u\r\n" "Connection: close\r\n" "\r\n", - path, (unsigned int)BFPILOT_WEB_PORT); + path, (unsigned int)port); if(n < 0 || (size_t)n >= sizeof(request) || websrv_write_all(fd, request, (size_t)n) != 0) { close(fd); @@ -280,9 +263,10 @@ json_long_field(const char *json, const char *field) { static int -old_server_busy(void) { +old_server_busy(unsigned short port) { char response[2048]; - if(local_http_get("/api/fs/job/status", response, sizeof(response)) != 0) { + if(local_http_get(port, "/api/fs/job/status", response, + sizeof(response)) != 0) { return 0; } return strstr(response, "\"busy\":true") != NULL; @@ -290,10 +274,10 @@ old_server_busy(void) { static int -wait_for_old_server_down(void) { +wait_for_old_server_down(unsigned short port) { char response[512]; for(int i = 0; i < 30; i++) { - if(local_http_get("/api/status", response, sizeof(response)) != 0) { + if(local_http_get(port, "/api/status", response, sizeof(response)) != 0) { return 1; } usleep(100000); @@ -303,46 +287,52 @@ wait_for_old_server_down(void) { static int -handoff_existing_server(void) { +handoff_existing_server(unsigned short port) { char response[2048]; - if(local_http_get("/api/status", response, sizeof(response)) != 0) { + if(local_http_get(port, "/api/status", response, sizeof(response)) != 0) { return 0; } if(!strstr(response, "\"name\":\"BFpilot\"") && !strstr(response, "\"name\":\"BS5FileManager\"")) { bfpilot_log("handoff: port %u belongs to a different service; leaving it " - "running", (unsigned int)BFPILOT_WEB_PORT); - bfpilot_notify("BFpilot reload skipped", - "Port 5905 is used by another service"); + "running", (unsigned int)port); + bfpilot_notify("BFpilot reload blocked", + "Port is used by another service"); return -1; } long old_pid = json_long_field(response, "pid"); - bfpilot_log("handoff: existing BFpilot-compatible server pid=%ld", old_pid); + bfpilot_log("handoff: existing BFpilot-compatible server pid=%ld port=%u", + old_pid, (unsigned int)port); + bfpilot_notify("BFpilot reload: old server detected", + old_pid >= 0 ? "Attempting clean shutdown" : + "Attempting clean shutdown; old pid unknown"); if(old_pid == (long)getpid()) { return 0; } - if(old_server_busy()) { - bfpilot_notify("BFpilot reload skipped", + if(old_server_busy(port)) { + bfpilot_notify("BFpilot reload blocked", "Old file operation is still running"); + bfpilot_log("handoff: blocked because old file job is busy"); return -1; } snprintf(response, sizeof(response), "/api/control/shutdown?token=%s", BFPILOT_RELOAD_TOKEN); char shutdown_response[512]; - if(local_http_get(response, shutdown_response, + if(local_http_get(port, response, shutdown_response, sizeof(shutdown_response)) == 0 && strstr(shutdown_response, "200 OK") && strstr(shutdown_response, "\"ok\":true") && - wait_for_old_server_down()) { + wait_for_old_server_down(port)) { bfpilot_notify("BFpilot reloaded", "Old listener stopped cleanly"); + bfpilot_log("handoff: old listener stopped cleanly"); return 1; } - bfpilot_notify("BFpilot reload failed", - "Could not stop old listener on port 5905"); + bfpilot_notify("BFpilot reload blocked", + "Old listener did not stop cleanly"); bfpilot_log("handoff: old listener did not accept clean shutdown; not " "sending signals"); return -1; @@ -365,17 +355,13 @@ on_web_ready(unsigned short port, void *arg) { state->notified = 1; } -#if BFPILOT_ENABLE_LAUNCHER - if(!state->launcher_disabled && !state->launcher_started) { - state->launcher_started = 1; - start_launcher_thread(state->ip); - } -#endif + debug_notify("BFpilot debug", "web server ready"); } int main(int argc, char **argv) { + bfpilot_boot_marker(BFPILOT_PAYLOAD_NAME, BFPILOT_BUILD_MODE); bfpilot_diag_init(); bfpilot_diag_install_signal_handlers(); bfpilot_checkpoint("process start"); @@ -396,9 +382,11 @@ main(int argc, char **argv) { ready_state_t ready; memset(&ready, 0, sizeof(ready)); detect_lan_ip(ready.ip, sizeof(ready.ip)); + ready.port = runtime_port_requested(argc, argv); + websrv_set_runtime_port(ready.port); int launcher_disabled = launcher_disabled_requested(argc, argv); - ready.launcher_disabled = launcher_disabled; websrv_set_runtime_diag(launcher_disabled); + bfpilot_log("runtime web_port=%u", (unsigned int)ready.port); bfpilot_log("runtime launcher_disabled=%d", launcher_disabled); puts(".----------------------------------------------."); @@ -409,27 +397,47 @@ 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(launcher_disabled) { + 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: BFpilot opens http://127.0.0.1:5905/"); + puts(" ps5 app: launcher installer is a separate optional payload"); } - printf(" web ui: http://%s:%u/\n", ready.ip, (unsigned int)BFPILOT_WEB_PORT); + printf(" web ui: http://%s:%u/\n", ready.ip, (unsigned int)ready.port); puts(" inject/deploy port: 9021"); puts(""); signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); +#if BFPILOT_DEBUG_NOTIFICATIONS || BFPILOT_ENABLE_LAUNCHER int notification_rc = bfpilot_notify_test(); bfpilot_diag_set_notification_rc(notification_rc); bfpilot_log("notification test rc=0x%08x", notification_rc); +#else + bfpilot_diag_set_notification_rc(BFPILOT_DIAG_SKIPPED); + bfpilot_log("notification test rc=skipped"); +#endif #if BFPILOT_ENABLE_LAUNCHER if(!launcher_disabled) { - bfpilot_diag_set_launcher_status("deferred", BFPILOT_DIAG_SKIPPED); - bfpilot_log("launcher deferred until web server is listening"); - puts(" ps5 app: launcher will run after web server starts"); + 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); @@ -440,15 +448,15 @@ main(int argc, char **argv) { } #else bfpilot_diag_set_service_rcs(BFPILOT_DIAG_SKIPPED, BFPILOT_DIAG_SKIPPED); - bfpilot_diag_set_launcher_status("not_compiled", 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: not compiled"); - puts(" ps5 app: not compiled in core mode"); + bfpilot_log("launcher skipped: separate payload"); #endif bfpilot_checkpoint("handoff check"); - int handoff_status = handoff_existing_server(); + debug_notify("BFpilot debug", "handoff check"); + int handoff_status = handoff_existing_server(ready.port); bfpilot_log("handoff_existing_server rc=%d", handoff_status); if(handoff_status < 0) { bfpilot_checkpoint("handoff blocked"); @@ -458,7 +466,7 @@ main(int argc, char **argv) { while(1) { bfpilot_checkpoint("web listen starting"); - int rc = websrv_listen(BFPILOT_WEB_PORT, on_web_ready, &ready); + int rc = websrv_listen(ready.port, on_web_ready, &ready); bfpilot_log("websrv_listen returned rc=%d", rc); if(websrv_exit_requested()) { bfpilot_checkpoint("web shutdown requested"); @@ -468,7 +476,7 @@ main(int argc, char **argv) { if(!ready.notified) { char msg[128]; snprintf(msg, sizeof(msg), "port %u error %d, retrying", - (unsigned int)BFPILOT_WEB_PORT, -rc); + (unsigned int)ready.port, -rc); bfpilot_notify("BFpilot could not start", msg); ready.notified = 1; } diff --git a/src/transfer.c b/src/transfer.c index 873bd2f..4194f6c 100644 --- a/src/transfer.c +++ b/src/transfer.c @@ -19,11 +19,12 @@ #include #include +#include "diag.h" #include "transfer.h" #include "websrv.h" -#define COPY_BUF_SIZE (1024 * 1024) +#define COPY_BUF_SIZE (256 * 1024) #define UPLOAD_BUF_SIZE (256 * 1024) @@ -259,6 +260,8 @@ job_end(int rc, const char *err) { snprintf(g_job.error, sizeof(g_job.error), "%s", err); } pthread_mutex_unlock(&g_job.lock); + bfpilot_log("transfer job end verb=%s rc=%d error=%s", + g_job.verb, rc, err ? err : ""); atomic_store(&g_job.busy, 0); } @@ -301,7 +304,7 @@ copy_file(const char *src, const char *dst) { job_set_error("open(%s): %s", src, strerror(errno)); return -1; } - int out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0777); + int out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0777); if(out < 0) { job_set_error("open(%s): %s", dst, strerror(errno)); close(in); @@ -356,7 +359,14 @@ copy_file(const char *src, const char *dst) { free(buf); close(in); - close(out); + if(rc == 0 && fsync(out) != 0 && errno != EINVAL) { + job_set_error("sync(%s): %s", dst, strerror(errno)); + rc = -1; + } + if(close(out) != 0 && rc == 0) { + job_set_error("close(%s): %s", dst, strerror(errno)); + rc = -1; + } if(rc != 0) unlink(dst); return rc; } @@ -452,6 +462,41 @@ delete_recursive(const char *path, int count_progress) { } +static int +delete_recursive_force(const char *path) { + struct stat st; + if(lstat(path, &st) != 0) { + return errno == ENOENT ? 0 : -1; + } + + if(S_ISDIR(st.st_mode)) { + DIR *d = opendir(path); + if(!d) return -1; + int rc = 0; + struct dirent *ent; + while((ent = readdir(d))) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; + char child[1024]; + if(strlen(path) + strlen(ent->d_name) + 2 >= sizeof(child)) { + errno = ENAMETOOLONG; + rc = -1; + break; + } + join_path(child, sizeof(child), path, ent->d_name); + if(delete_recursive_force(child) != 0) { + rc = -1; + break; + } + } + closedir(d); + if(rc != 0) return -1; + return rmdir(path); + } + + return unlink(path); +} + + struct copy_arg { char src[1024]; char dst[1024]; @@ -463,9 +508,12 @@ static void * copy_worker(void *arg) { struct copy_arg *a = arg; long files = 0, bytes = 0; - struct stat src_st, dst_st; + struct stat src_st, dst_st, final_st; char final_dst[1024]; + char staging_dst[1024]; + bfpilot_log("transfer worker start verb=%s src=%s dst=%s", + a->is_move ? "move" : "copy", a->src, a->dst); if(lstat(a->src, &src_st) != 0) { job_end(-1, "source not found"); free(a); @@ -513,6 +561,27 @@ copy_worker(void *arg) { } join_path(final_dst, sizeof(final_dst), a->dst, base); + if(lstat(final_dst, &final_st) == 0) { + job_end(-1, "destination already exists"); + free(a); + return NULL; + } + if(errno != ENOENT) { + char err[160]; + snprintf(err, sizeof(err), "destination: %s", strerror(errno)); + job_end(-1, err); + free(a); + return NULL; + } + + int n = snprintf(staging_dst, sizeof(staging_dst), "%s.bfpilot-part-%ld-%ld", + final_dst, (long)getpid(), (long)time(NULL)); + if(n < 0 || (size_t)n >= sizeof(staging_dst)) { + job_end(-1, "staging path too long"); + free(a); + return NULL; + } + if(!strcmp(a->src, final_dst)) { job_end(-1, "source and destination are the same"); free(a); @@ -546,22 +615,36 @@ copy_worker(void *arg) { free(a); return NULL; } + bfpilot_log("transfer move cross-device fallback src=%s staging=%s final=%s", + a->src, staging_dst, final_dst); } - int rc = copy_recursive(a->src, final_dst); + int rc = copy_recursive(a->src, staging_dst); if(rc != 0) { char err[160]; snprintf(err, sizeof(err), "copy: %s", job_cancelled() ? "cancelled" : strerror(errno)); + (void)delete_recursive_force(staging_dst); job_end(-1, err); free(a); return NULL; } if(job_cancelled()) { + (void)delete_recursive_force(staging_dst); job_end(-1, "cancelled"); free(a); return NULL; } + job_set_current("Finalizing destination"); + if(rename(staging_dst, final_dst) != 0) { + char err[160]; + snprintf(err, sizeof(err), "finalize: %s", strerror(errno)); + (void)delete_recursive_force(staging_dst); + job_end(-1, err); + free(a); + return NULL; + } + bfpilot_log("transfer finalized staging=%s final=%s", staging_dst, final_dst); if(a->is_move && delete_recursive(a->src, 0) != 0) { char err[160]; snprintf(err, sizeof(err), "post-move cleanup: %s", @@ -570,6 +653,9 @@ copy_worker(void *arg) { free(a); return NULL; } + if(a->is_move) { + bfpilot_log("transfer source cleanup complete src=%s", a->src); + } job_end(0, NULL); free(a); @@ -847,6 +933,8 @@ spawn_copy_or_move(const http_request_t *req, int is_move) { if(!job_begin(is_move ? "move" : "copy")) { return serve_error(req, 409, "another file-manager job is already running"); } + bfpilot_log("transfer job begin verb=%s src=%s dst=%s", + is_move ? "move" : "copy", src, dst); struct copy_arg *a = calloc(1, sizeof(*a)); if(!a) { diff --git a/src/version.h b/src/version.h index 038aa1d..c2de8ab 100644 --- a/src/version.h +++ b/src/version.h @@ -17,11 +17,11 @@ #endif #ifndef BFPILOT_ENABLE_LAUNCHER -#define BFPILOT_ENABLE_LAUNCHER 1 +#define BFPILOT_ENABLE_LAUNCHER 0 #endif #ifndef BFPILOT_DISABLE_LAUNCHER -#define BFPILOT_DISABLE_LAUNCHER 0 +#define BFPILOT_DISABLE_LAUNCHER 1 #endif #if BFPILOT_DISABLE_LAUNCHER @@ -30,5 +30,5 @@ #endif #ifndef BFPILOT_BUILD_MODE -#define BFPILOT_BUILD_MODE "full" +#define BFPILOT_BUILD_MODE "file-manager" #endif diff --git a/src/websrv.h b/src/websrv.h index c702e64..9b8ab3c 100644 --- a/src/websrv.h +++ b/src/websrv.h @@ -57,6 +57,8 @@ void websrv_set_runtime_diag(int launcher_disabled); void websrv_set_launcher_diag(const bfpilot_launcher_diag_t *diag); +void websrv_set_runtime_port(unsigned short port); + void websrv_request_exit(void); int websrv_exit_requested(void); diff --git a/src/websrv_lite.c b/src/websrv_lite.c index ede22f3..751918c 100644 --- a/src/websrv_lite.c +++ b/src/websrv_lite.c @@ -28,10 +28,15 @@ #define HEADER_MAX 65536 #define CONTROL_TOKEN "bs5fm-local-reload" +#ifndef BFPILOT_WEB_PORT +#define BFPILOT_WEB_PORT 5905 +#endif + static int g_websrv_srvfd = -1; static pthread_mutex_t g_websrv_lock = PTHREAD_MUTEX_INITIALIZER; static volatile sig_atomic_t g_websrv_exit_requested = 0; +static unsigned short g_websrv_port = BFPILOT_WEB_PORT; static int g_launcher_runtime_disabled = 0; static bfpilot_launcher_diag_t g_launcher_diag = { .launcher_enabled = 0, @@ -76,6 +81,14 @@ websrv_set_launcher_diag(const bfpilot_launcher_diag_t *diag) { } +void +websrv_set_runtime_port(unsigned short port) { + if(port != 0) { + g_websrv_port = port; + } +} + + static const char * status_text(int status) { switch(status) { @@ -338,11 +351,12 @@ status_request(const http_request_t *req) { "\"services\":[\"web\",\"file-manager\"]," "\"launcherCompiled\":%s," "\"launcherDisabled\":%s," - "\"port\":5905}", + "\"port\":%u}", VERSION_TAG, BUILD_VERSION, BFPILOT_BUILD_MODE, (long)getpid(), (long)now, BFPILOT_ENABLE_LAUNCHER ? "true" : "false", - g_launcher_runtime_disabled ? "true" : "false"); + g_launcher_runtime_disabled ? "true" : "false", + (unsigned int)g_websrv_port); if(n < 0) return -1; if((size_t)n >= sizeof(body)) n = (int)sizeof(body) - 1; return websrv_send(req->fd, 200, "application/json", body, (size_t)n); @@ -388,7 +402,7 @@ diag_request(const http_request_t *req) { "\"pid\":%ld," "\"now\":%ld," "\"uptime\":%ld," - "\"port\":5905," + "\"port\":%u," "\"cwd\":\"%s\"," "\"can_stat_root\":%s," "\"can_opendir_data\":%s," @@ -413,6 +427,8 @@ diag_request(const http_request_t *req) { "\"sceNetCtlInit\":%d," "\"sceUserServiceInitialize\":%d," "\"notification_test\":%d," + "\"bind_port\":%u," + "\"listen_port\":%u," "\"bind_5905\":%d," "\"listen_5905\":%d}," "\"launcher\":{" @@ -439,6 +455,7 @@ diag_request(const http_request_t *req) { VERSION_TAG, BUILD_VERSION, BFPILOT_BUILD_MODE, (long)getpid(), (long)now, bfpilot_diag_uptime(), + (unsigned int)g_websrv_port, cwd_json, json_bool(can_stat_root), json_bool(can_opendir_data), @@ -462,6 +479,8 @@ diag_request(const http_request_t *req) { bfpilot_diag_netctl_rc(), bfpilot_diag_user_service_rc(), bfpilot_diag_notification_rc(), + (unsigned int)g_websrv_port, + (unsigned int)g_websrv_port, bfpilot_diag_bind_rc(), bfpilot_diag_listen_rc(), launcher_status_json, @@ -622,6 +641,8 @@ websrv_listen(unsigned short port, websrv_ready_cb_t ready_cb, struct sockaddr_in server_addr; int srvfd; + websrv_set_runtime_port(port); + signal(SIGPIPE, SIG_IGN); srvfd = socket(AF_INET, SOCK_STREAM, 0); diff --git a/test-builds/v0.2.1/README.md b/test-builds/v0.2.1/README.md deleted file mode 100644 index 03fd3f3..0000000 --- a/test-builds/v0.2.1/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# BFpilot v0.2.1 Test Build - -This is a firmware-compatibility test build, not the latest stable release. - -Recommended order: - -1. Test `bfpilot-core.elf` first on every firmware/loader. -2. Only test `bfpilot-full.elf` after core mode starts and the file manager works. -3. Treat launcher install failure in full mode as non-fatal if the web server still works. - -Test URL: - -```text -http://:5905/ -``` - -Diagnostics: - -```text -http://:5905/api/status -http://:5905/api/diag -/data/BFpilot/log.txt -/data/BFpilot/crash.log -``` - -See `docs/FIRMWARE_TESTING.md` for the full firmware test protocol. diff --git a/test-builds/v0.2.1/bfpilot-core.elf b/test-builds/v0.2.1/bfpilot-core.elf deleted file mode 100644 index d93eb25..0000000 Binary files a/test-builds/v0.2.1/bfpilot-core.elf and /dev/null differ diff --git a/test-builds/v0.2.1/bfpilot-full.elf b/test-builds/v0.2.1/bfpilot-full.elf deleted file mode 100644 index c4f2803..0000000 Binary files a/test-builds/v0.2.1/bfpilot-full.elf and /dev/null differ diff --git a/tests/hello_boot.c b/tests/hello_boot.c new file mode 100644 index 0000000..6b5cd9b --- /dev/null +++ b/tests/hello_boot.c @@ -0,0 +1,17 @@ +/* + * BFpilot test payload - boot marker only. + */ + +#include + +#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; +} diff --git a/tests/hello_http.c b/tests/hello_http.c new file mode 100644 index 0000000..dab0d7b --- /dev/null +++ b/tests/hello_http.c @@ -0,0 +1,126 @@ +/* + * BFpilot test payload - tiny HTTP status server. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/tests/hello_notify.c b/tests/hello_notify.c new file mode 100644 index 0000000..c0de9c7 --- /dev/null +++ b/tests/hello_notify.c @@ -0,0 +1,18 @@ +/* + * BFpilot test payload - notification probe. + */ + +#include + +#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; +} diff --git a/tests/installer_enter_probe.c b/tests/installer_enter_probe.c new file mode 100644 index 0000000..06eabe5 --- /dev/null +++ b/tests/installer_enter_probe.c @@ -0,0 +1,21 @@ +/* + * 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; +} diff --git a/tests/installer_linkonly_appinst.c b/tests/installer_linkonly_appinst.c new file mode 100644 index 0000000..dc478d0 --- /dev/null +++ b/tests/installer_linkonly_appinst.c @@ -0,0 +1,30 @@ +/* + * 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; +} diff --git a/tests/installer_runtime_resolve_appinst.c b/tests/installer_runtime_resolve_appinst.c new file mode 100644 index 0000000..9aa0077 --- /dev/null +++ b/tests/installer_runtime_resolve_appinst.c @@ -0,0 +1,75 @@ +/* + * BFpilot test payload - runtime AppInstUtil load/resolve probe. + */ + +#include +#include + +#include + +#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; +} diff --git a/tests/probe_common.h b/tests/probe_common.h new file mode 100644 index 0000000..fa67d97 --- /dev/null +++ b/tests/probe_common.h @@ -0,0 +1,87 @@ +/* + * BFpilot test payload helpers. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#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_TRUNC, 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); + } +}