Files
NotSoTypicalGamer baaee914ac games/ps5: native uninstall through the installer helper
zhttp can now uninstall a title natively, reusing the same embedded
helper that drives installs. Three changes make it work on 13.60:

- install_helper.c escalates to the SYSTEM_PROCESS auth id
  (0x4800000000000006) by writing cr_sceAuthID at ucred+0x58 via
  kernel_copyin; the SDKs
2026-10-16 21:09:07 -04:00

1195 lines
46 KiB
Makefile

# Multi-Platform FTP Server - Makefile
# Supports: Linux, macOS, PS3, PS4, PS5
# Standards: MISRA C:2012, CERT C, ISO C11
# Project information
PROJECT := zftpd
VERSION := $(shell grep -E 'define[[:space:]]+RELEASE_VERSION' include/ftp/ftp_config.h | head -n 1 | cut -d'"' -f2)
ifeq ($(strip $(VERSION)),)
VERSION := 0.0.0
endif
ARTIFACT_PREFIX ?= zftpd
HOST_ARCH := $(shell uname -m)
ifeq ($(TARGET),macos)
PLATFORM_TAG := macos-$(HOST_ARCH)
else ifeq ($(TARGET),linux)
PLATFORM_TAG := linux-$(HOST_ARCH)
else
PLATFORM_TAG := $(TARGET)
endif
# Host OS detection (for toolchain/linker compatibility)
HOST_OS := $(shell uname -s)
# Target platform (default detection)
ifeq ($(HOST_OS),Darwin)
TARGET ?= macos
else
TARGET ?= linux
endif
# Normalize target to lowercase so TARGET=PS5/PS4 still matches rules
TARGET := $(shell echo $(TARGET) | tr '[:upper:]' '[:lower:]')
# Build configuration
BUILD_TYPE ?= release
# Valid values: debug, release
PS4_PAYLOAD_SDK ?= $(abspath external/ps4-payload-sdk)
PS4_HOST ?= ps4
PS4_PORT ?= 9021
PS5_PAYLOAD_SDK ?= $(abspath external/ps5-payload-sdk)
PS5_HOST ?= ps5
PS5_PORT ?= 9021
#============================================================================
# PLATFORM DETECTION AND CONFIGURATION
#============================================================================
# Compiler selection
ifeq ($(TARGET),ps3)
CC := ppu-gcc
PLATFORM_DEFS := -DPLATFORM_PS3 -DPS3
PLATFORM_LIBS := -lnet
PLATFORM_LDFLAGS :=
endif
ifeq ($(TARGET),ps4)
ORBIS_LLVM_CONFIG := $(LLVM_CONFIG)
ifeq ($(ORBIS_LLVM_CONFIG),)
ORBIS_LLVM_CONFIG := $(shell command -v llvm-config-21 2>/dev/null || command -v llvm-config-20 2>/dev/null || command -v llvm-config-19 2>/dev/null || command -v llvm-config-18 2>/dev/null || command -v llvm-config-17 2>/dev/null || command -v llvm-config-16 2>/dev/null || command -v llvm-config-15 2>/dev/null || command -v llvm-config 2>/dev/null)
endif
ifeq ($(ORBIS_LLVM_CONFIG),)
ORBIS_LLVM_CONFIG := $(shell if command -v brew >/dev/null 2>&1; then p=$$(brew --prefix llvm 2>/dev/null); if [ -x "$$p/bin/llvm-config" ]; then echo "$$p/bin/llvm-config"; fi; fi)
endif
ifeq ($(ORBIS_LLVM_CONFIG),)
$(error llvm-config non trovato: installa LLVM (es. brew install llvm) e assicurati che llvm-config sia nel PATH oppure passa LLVM_CONFIG=/percorso/a/llvm-config)
endif
export LLVM_CONFIG := $(ORBIS_LLVM_CONFIG)
include $(PS4_PAYLOAD_SDK)/toolchain/orbis.mk
PLATFORM_DEFS := -DPLATFORM_PS4 -DPS4
# SceSysmodule is loaded at runtime with dlopen: the payload SDK does not
# ship a linkable library for it.
PLATFORM_LIBS := -lkernel -lpthread -lSceSystemService -lSceUserService
PLATFORM_LDFLAGS :=
endif
ifeq ($(TARGET),ps5)
PROSPERO_LLVM_CONFIG := $(LLVM_CONFIG)
ifeq ($(PROSPERO_LLVM_CONFIG),)
PROSPERO_LLVM_CONFIG := $(shell command -v llvm-config-21 2>/dev/null || command -v llvm-config-20 2>/dev/null || command -v llvm-config-19 2>/dev/null || command -v llvm-config-18 2>/dev/null || command -v llvm-config-17 2>/dev/null || command -v llvm-config-16 2>/dev/null || command -v llvm-config-15 2>/dev/null || command -v llvm-config 2>/dev/null)
endif
ifeq ($(PROSPERO_LLVM_CONFIG),)
PROSPERO_LLVM_CONFIG := $(shell if command -v brew >/dev/null 2>&1; then p=$$(brew --prefix llvm 2>/dev/null); if [ -x "$$p/bin/llvm-config" ]; then echo "$$p/bin/llvm-config"; fi; fi)
endif
ifeq ($(PROSPERO_LLVM_CONFIG),)
$(error llvm-config non trovato: installa LLVM (es. brew install llvm) e assicurati che llvm-config sia nel PATH oppure passa LLVM_CONFIG=/percorso/a/llvm-config)
endif
export LLVM_CONFIG := $(PROSPERO_LLVM_CONFIG)
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
PLATFORM_DEFS := -DPLATFORM_PS5 -DPS5 -D__PROSPERO__
PLATFORM_LIBS := -lkernel -lpthread -lSceNotification
PLATFORM_LDFLAGS :=
endif
ifeq ($(TARGET),linux)
CC := gcc
PLATFORM_DEFS := -DPLATFORM_LINUX -D_GNU_SOURCE
PLATFORM_LIBS := -lpthread
ifeq ($(HOST_OS),Linux)
PLATFORM_LDFLAGS := -Wl,-z,relro -Wl,-z,now
else
PLATFORM_LDFLAGS :=
endif
endif
ifeq ($(TARGET),macos)
CC := clang
PLATFORM_DEFS := -DPLATFORM_MACOS -D_DARWIN_C_SOURCE
PLATFORM_LIBS := -lpthread
PLATFORM_LDFLAGS :=
endif
# Default to GCC if no target matched
CC ?= gcc
CXX ?= c++
PLATFORM_LIBS ?= -lpthread
# Event loop implementation — kqueue for BSD/macOS/PS4/PS5, epoll for Linux
ifeq ($(TARGET),linux)
EVENT_LOOP_SRC := src/runtime/event_loop_epoll.c
else
EVENT_LOOP_SRC := src/runtime/event_loop_kqueue.c
endif
#============================================================================
# COMPILER FLAGS (SAFETY-CRITICAL STANDARDS)
#============================================================================
# C Standard
CFLAGS := -std=c11
# Warning flags (comprehensive)
CFLAGS += -Wall -Wextra -Wpedantic
CFLAGS += -Wformat=2 -Wformat-security
CFLAGS += -Wnull-dereference
CFLAGS += -Wstack-protector
CFLAGS += -Wstrict-overflow=5
CFLAGS += -Warray-bounds
CFLAGS += -Wcast-align
CFLAGS += -Wcast-qual
CFLAGS += -Wconversion
CFLAGS += -Wsign-conversion
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wmissing-prototypes
CFLAGS += -Wredundant-decls
CFLAGS += -Wshadow
CFLAGS += -Wundef
CFLAGS += -Wwrite-strings
# Treat warnings as errors in release builds
ifeq ($(BUILD_TYPE),release)
CFLAGS += -Werror
endif
# Toolchain compatibility (Clang on macOS uses different warning set)
ifeq ($(HOST_OS),Darwin)
CFLAGS += -Wno-unknown-warning-option
endif
# Security hardening flags
CFLAGS += -fstack-protector-strong
CFLAGS += -fPIE
CFLAGS += -fno-strict-aliasing
# _FORTIFY_SOURCE is glibc-specific; avoid redef warnings on non-Linux hosts
ifeq ($(HOST_OS),Linux)
CFLAGS += -D_FORTIFY_SOURCE=2
endif
# Optimization and debug flags
ifeq ($(BUILD_TYPE),debug)
CFLAGS += -O0 -g3 -DDEBUG -DFTP_DEBUG=1
CFLAGS += -fsanitize=address,undefined
LDFLAGS += -fsanitize=address,undefined
else
CFLAGS += -O2 -g -DNDEBUG -DFTP_DEBUG=0
endif
# Platform-specific flags
CFLAGS += $(PLATFORM_DEFS)
# Avoid symbol interposition when injected into host processes (PS4/PS5 payload).
ifneq ($(filter $(TARGET),ps4 ps5),)
CFLAGS += -fvisibility=hidden
endif
# PS5: disable aggressive function inlining. Without this flag the compiler
# inlines every static api_* handler into http_api_handle, creating a massive
# stack frame (~60-80 KB) that overflows the PS5 thread stack (SIGSEGV).
ifeq ($(TARGET),ps5)
CFLAGS += -fno-inline-functions
endif
# Include directories
CFLAGS += -I./include
CFLAGS += -I./include/ftp -I./include/http -I./include/platform
CFLAGS += -I./include/platform/ps5 -I./include/archive -I./include/runtime
#============================================================================
# LINKER FLAGS
#============================================================================
# Console payloads are loaded by loaders (GoldHEN binloader, elfldr) that
# expect a position-independent executable, no matter which host built them.
ifneq ($(filter $(TARGET),ps4 ps5),)
LDFLAGS += -pie
else ifeq ($(HOST_OS),Linux)
LDFLAGS += -pie
endif
LDFLAGS += $(PLATFORM_LDFLAGS)
LIBS := $(PLATFORM_LIBS)
#============================================================================
# SOURCE FILES
#============================================================================
# Source files
SOURCES := src/platform/pal_network.c
SOURCES += src/platform/fileio/basic.c
SOURCES += src/platform/fileio/sendfile.c
SOURCES += src/platform/fileio/copy.c
SOURCES += src/platform/fileio/tree.c
SOURCES += src/platform/fileio/directory.c
SOURCES += src/platform/pal_alloc.c
SOURCES += src/platform/pal_scratch.c
SOURCES += src/platform/pal_notification.c
SOURCES += src/platform/pal_disc.c
SOURCES += src/platform/pal_volume.c
SOURCES += src/platform/pal_dns_filter.c
SOURCES += src/platform/pal_filesystem.c
SOURCES += src/platform/pal_filesystem_psx.c
SOURCES += src/ftp/ftp_path.c
SOURCES += src/ftp/ftp_server.c
SOURCES += src/ftp/ftp_session.c
SOURCES += src/ftp/ftp_protocol.c
SOURCES += src/ftp/commands/control.c
SOURCES += src/ftp/commands/listing.c
SOURCES += src/ftp/commands/transfer.c
SOURCES += src/ftp/commands/filesystem.c
SOURCES += src/ftp/commands/data_connection.c
SOURCES += src/ftp/commands/extensions.c
SOURCES += src/ftp/commands/information.c
SOURCES += src/ftp/commands/transfer_params.c
SOURCES += src/ftp/ftp_buffer_pool.c
SOURCES += src/ftp/ftp_log.c
SOURCES += src/ftp/ftp_crypto.c
SOURCES += src/app/main.c
SOURCES += src/app/instance_control.c
SOURCES += src/platform/pal_resilient_server.c
SOURCES += src/ftp/ftp_instance.c
# PS5 kernel syscall hook is opt-in until its firmware-specific install path
# has been validated on real hardware. The normal payload never loads it.
ENABLE_PS5_NET_FILTER ?= 0
ifeq ($(TARGET),ps5)
CFLAGS += -DENABLE_PS5_NET_FILTER=$(ENABLE_PS5_NET_FILTER)
endif
ifeq ($(TARGET),ps5)
SOURCES += src/platform/ps5/ps5_fw_offsets.c
ifeq ($(ENABLE_PS5_NET_FILTER),1)
SOURCES += src/platform/ps5/ps5_net_filter.c
endif
endif
#============================================================================
# ZHTTPD (Web File Explorer) — compile-time toggle
# Enabled by default on consoles (PS4/PS5), disabled on PC
#============================================================================
ifneq ($(filter $(TARGET),ps4 ps5),)
ENABLE_ZHTTPD ?= 1
else
ENABLE_ZHTTPD ?= 1
endif
# Artifact/build variants (e.g., zhttp)
ifeq ($(ENABLE_ZHTTPD),1)
TEST_BINS += $(BUILD_DIR)/tests/test_http_query
TEST_BINS += $(BUILD_DIR)/tests/test_http_confinement
TEST_BINS += $(BUILD_DIR)/tests/test_notify
VARIANT_TAG := zhttp
endif
# Build output directories (variant-aware)
BUILD_DIR := build/$(TARGET)/$(BUILD_TYPE)$(if $(VARIANT_TAG),-$(VARIANT_TAG),)
OBJ_DIR := $(BUILD_DIR)/obj
DEP_DIR := $(BUILD_DIR)/dep
BIN_DIR := $(BUILD_DIR)
ARTIFACT_BASE := $(ARTIFACT_PREFIX)-$(PLATFORM_TAG)$(if $(VARIANT_TAG),-$(VARIANT_TAG),)-v$(VERSION)
ifeq ($(TARGET),macos)
OUTPUT_ELF := $(BIN_DIR)/$(ARTIFACT_BASE)
else
OUTPUT_ELF := $(BIN_DIR)/$(ARTIFACT_BASE).elf
endif
OUTPUT_BIN := $(BIN_DIR)/$(ARTIFACT_BASE).bin
OBJCOPY ?= objcopy
STRIP ?= strip
ifeq ($(ENABLE_ZHTTPD),1)
CFLAGS += -DENABLE_ZHTTPD=1
CFLAGS += -DENABLE_WEB_UPLOAD=1
# PKG installation rides along with the web interface; override with =0.
ENABLE_PKG_INSTALL ?= 1
CFLAGS += -DENABLE_PKG_INSTALL=$(ENABLE_PKG_INSTALL)
ifeq ($(TARGET),ps5)
ifeq ($(ENABLE_PKG_INSTALL),1)
# The system installer cannot be driven from a payload process, so a
# small helper program is embedded here and delivered at runtime. With
# the installer disabled the helper is neither built nor embedded.
INSTALL_HELPER_ELF := $(BUILD_DIR)/install_helper.elf
INSTALL_HELPER_BLOB := $(BUILD_DIR)/generated/install_helper_blob.c
SOURCES += $(INSTALL_HELPER_BLOB)
CFLAGS += -DZFTPD_INSTALL_HELPER=1
endif
SOURCES += src/http/games/ps5_install_helper.c
endif
ENABLE_LIBCURL ?= 1
SOURCES += $(EVENT_LOOP_SRC)
SOURCES += src/http/http_server.c
SOURCES += src/http/http_listener.c
SOURCES += src/http/http_upload.c
SOURCES += src/http/http_parser.c
SOURCES += src/http/http_response.c
SOURCES += src/http/http_response_stream.c
SOURCES += src/http/http_json.c
SOURCES += src/http/http_api.c
SOURCES += src/http/http_api_common.c
SOURCES += src/http/http_api_files.c
SOURCES += src/http/http_api_process.c
SOURCES += src/http/http_api_system.c src/http/games/common.c src/http/games/psx_install.c src/http/games/psx_appdb.c src/http/games/psx_launch.c src/http/games/admin_api.c src/http/games/metadata_api.c src/http/games/catalog.c
SOURCES += src/http/http_api_transfer.c
SOURCES += src/http/http_static.c
SOURCES += src/http/http_api_archive.c
SOURCES += src/http/http_api_dump.c
SOURCES += src/http/http_csrf.c
WEB_RESOURCE_FILES := $(shell find web -path web/legacy -prune -o -type f -print | sort)
HTTP_RESOURCES_C := $(BUILD_DIR)/generated/http/http_resources.c
SOURCES += $(HTTP_RESOURCES_C)
SOURCES += src/archive/exfat_unpacker.c
SOURCES += src/archive/pkg_unpacker.c
SOURCES += src/archive/builtin_unzip.c
SOURCES += src/archive/zip_writer.c
SOURCES += src/archive/archive_path.c
SOURCES += src/transfer/transfer_manager.c
SOURCES += src/transfer/transfer_state.c
SOURCES += src/transfer/dump_job.c
else
# Keep the switch visible in every translation unit either way.
CFLAGS += -DENABLE_ZHTTPD=0
endif
# NFS URL transfers are enabled by default for PS5 zhttp builds. Other targets
# can opt in explicitly with ENABLE_LIBNFS=1 when libnfs is installed.
ifeq ($(ENABLE_ZHTTPD),1)
ifeq ($(TARGET),ps5)
ENABLE_LIBNFS ?= 1
else
ENABLE_LIBNFS ?= 0
endif
else
ENABLE_LIBNFS := 0
endif
#============================================================================
# Enable with ENABLE_MCP=1
#============================================================================
ifeq ($(ENABLE_MCP),)
ENABLE_MCP ?= 0
endif
ifeq ($(ENABLE_MCP),1)
CFLAGS += -DENABLE_MCP=1
CFLAGS += -I./mcp/include
CFLAGS += -I./external/sJson-main/src
# JSON configuration for sJson
CFLAGS += -DJSON_MAX_DEPTH=32
CFLAGS += -DJSON_MAX_STRING_LEN=65536
CFLAGS += -DJSON_MAX_NODES=16384
SOURCES += mcp/src/mcp_protocol.c
SOURCES += mcp/src/mcp_server.c
SOURCES += mcp/src/mcp_handlers.c
SOURCES += external/sJson-main/src/sJson.c
SOURCES += $(EVENT_LOOP_SRC)
# Execution modules
SOURCES += mcp/src/mcp_execution/payload.c
SOURCES += mcp/src/mcp_execution/syscall_race.c
# Hunter modules (Zero-Day detection)
SOURCES += mcp/src/mcp_hunter/process_monitor.c
SOURCES += mcp/src/mcp_hunter/vuln_fuzzer.c
SOURCES += mcp/src/mcp_hunter/exploit_chain.c
SOURCES += mcp/src/mcp_hunter/jit_compiler.c
# Additional include path for hunter headers
CFLAGS += -I./mcp/src/mcp_hunter
$(info [INFO] MCP kernel analysis module enabled (with Zero-Day Hunter))
endif
#============================================================================
# OPTIONAL LIBRARIES — enable with ENABLE_LIBARCHIVE=1 / ENABLE_LIBCURL=1
#
# When enabled, the Makefile verifies that the required header is actually
# available. For desktop (gcc/clang) it uses a compiler probe. For
# PS4/PS5 cross-compilers the probe fails (missing sysroot headers),
# so we fall back to a simple file-existence check on bundled headers.
#============================================================================
# ── libarchive: zhttp only; auto-detect on desktop ─────────────────────
override ENABLE_LIBCURL ?= 0
ifeq ($(ENABLE_ZHTTPD),1)
ifeq ($(TARGET),ps5)
# Optional PacBrew library for non-ZIP formats; ZIP keeps the builtin reader.
override ENABLE_LIBARCHIVE ?= 0
ifeq ($(ENABLE_LIBARCHIVE),1)
PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
_HAS_PS5_ARCHIVE := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libarchive && echo 1 || echo 0)
ifneq ($(_HAS_PS5_ARCHIVE),1)
$(error PS5 archive extraction requires PacBrew ps5-payload-libarchive)
endif
CFLAGS += -DENABLE_LIBARCHIVE=1 $(shell $(PS5_PKG_CONFIG) --cflags libarchive)
LIBS += $(shell $(PS5_PKG_CONFIG) --static --libs libarchive)
$(info [INFO] PS5 archive extraction: PacBrew libarchive)
endif
else ifeq ($(TARGET),ps4)
# PS4 builds use the builtin ZIP backend.
override ENABLE_LIBARCHIVE := 0
else
# Desktop (macOS/Linux): auto-detect unless user explicitly set it to 0
ifneq ($(ENABLE_LIBARCHIVE),0)
_HAS_ARCHIVE := 0
# 1) System installation
_SYS_ARCHIVE := $(shell echo '\#include <archive.h>' | $(CC) -xc -fsyntax-only - 2>/dev/null && echo 1 || echo 0)
ifeq ($(_SYS_ARCHIVE),1)
_HAS_ARCHIVE := 1
LIBS += -larchive
$(info [INFO] libarchive: using system installation)
endif
# 2) Homebrew keg (libarchive is keg-only on macOS)
ifeq ($(_HAS_ARCHIVE),0)
ifeq ($(HOST_OS),Darwin)
_BREW_ARCHIVE_PREFIX := $(shell brew --prefix libarchive 2>/dev/null)
ifneq ($(_BREW_ARCHIVE_PREFIX),)
_HB_ARCHIVE := $(shell echo '\#include <archive.h>' | $(CC) -xc -fsyntax-only -I$(_BREW_ARCHIVE_PREFIX)/include - 2>/dev/null && echo 1 || echo 0)
ifeq ($(_HB_ARCHIVE),1)
_HAS_ARCHIVE := 1
CFLAGS += -I$(_BREW_ARCHIVE_PREFIX)/include
LDFLAGS += -L$(_BREW_ARCHIVE_PREFIX)/lib
LIBS += -larchive
$(info [INFO] libarchive: using Homebrew keg $(_BREW_ARCHIVE_PREFIX))
endif
endif
endif
endif
# 3) Bundled headers fallback (external/libarchive-3.8.6)
ifeq ($(_HAS_ARCHIVE),0)
_BUNDLED_ARCHIVE_H := $(wildcard external/libarchive-3.8.6/libarchive/archive.h)
ifneq ($(_BUNDLED_ARCHIVE_H),)
_HAS_ARCHIVE := 1
CFLAGS += -I./external/libarchive-3.8.6/libarchive
ifeq ($(wildcard external/libarchive-3.8.6-compiled/.libs/libarchive.a),)
LIBS += -larchive
else
LIBS += external/libarchive-3.8.6-compiled/.libs/libarchive.a
endif
$(info [INFO] libarchive: using bundled headers (external/libarchive-3.8.6))
endif
endif
# 4) Not found — disable
ifeq ($(_HAS_ARCHIVE),0)
$(info [INFO] libarchive not found — extraction disabled (install: brew install libarchive))
override ENABLE_LIBARCHIVE := 0
else
override ENABLE_LIBARCHIVE := 1
CFLAGS += -DENABLE_LIBARCHIVE=1
endif
endif
endif
else
override ENABLE_LIBARCHIVE := 0
endif
# ── libcurl detection ─────────────────────────────────────────────────────
# zftpd deliberately uses external libcurl instead of maintaining an HTTP/TLS
# reimplementation. PS5 consumes the official PacBrew ps5-payload-curl port.
ifeq ($(ENABLE_LIBCURL),1)
ifeq ($(TARGET),ps5)
PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
_HAS_PS5_CURL := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libcurl && echo 1 || echo 0)
ifneq ($(_HAS_PS5_CURL),1)
$(error PS5 zhttp requires PacBrew ps5-payload-curl)
endif
CFLAGS += -DENABLE_LIBCURL=1 $(shell $(PS5_PKG_CONFIG) --cflags libcurl)
LIBS += $(shell $(PS5_PKG_CONFIG) --libs libcurl)
PS5_CA_BUNDLE ?= $(PS5_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt
ifeq ($(wildcard $(PS5_CA_BUNDLE)),)
$(error PS5 libcurl CA bundle not found: $(PS5_CA_BUNDLE))
endif
GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
CURL_CA_BUNDLE_SRC := $(PS5_CA_BUNDLE)
$(info [INFO] URL downloads: external PacBrew libcurl + embedded CA bundle)
else ifeq ($(TARGET),ps4)
# PacBrew ps4-openorbis portlibs (libcurl + mbedTLS + zlib) as unpacked by
# tools/fetch_pacbrew_ps4.sh. Only the headers and the static libraries
# are used: the curl wrapper the package ships emits the OpenOrbis
# toolchain's own link flags, which do not belong in a payload-SDK link.
PS4_PACBREW_ROOT ?= $(shell for d in $(abspath external/pacbrew/ps4/openorbis) /opt/pacbrew/ps4/openorbis; do [ -f "$$d/usr/lib/libcurl.a" ] && { printf '%s' "$$d"; break; }; done)
ifneq ($(strip $(PS4_PACBREW_ROOT)),)
# The OpenOrbis build reaches errno through musl's __errno_location()
# and uses fnmatch()/execl(); the payload libc has none of them.
SOURCES += src/platform/ps4/ps4_pacbrew_compat.c
CFLAGS += -DENABLE_LIBCURL=1 -DCURL_STATICLIB -I$(PS4_PACBREW_ROOT)/usr/include
LIBS += -L$(PS4_PACBREW_ROOT)/usr/lib -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lz -lSceNet
# Same trust-store contract as PS5: HTTPS verification must not degrade
# silently, so the Mozilla bundle is embedded at build time.
PS4_CA_BUNDLE ?= $(PS4_PACBREW_ROOT)/usr/etc/ca-bundle.crt
ifeq ($(wildcard $(PS4_CA_BUNDLE)),)
$(error PS4 libcurl CA bundle not found: $(PS4_CA_BUNDLE) — re-run tools/fetch_pacbrew_ps4.sh or set PS4_CA_BUNDLE=<path>)
endif
GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
CURL_CA_BUNDLE_SRC := $(PS4_CA_BUNDLE)
$(info [INFO] URL downloads: PacBrew PS4 portlibs ($(PS4_PACBREW_ROOT)) + embedded CA bundle)
else
# A toolchain-provided pkg-config entry (or curl-config) still works.
# Probe the target environment only — a host libcurl would link code built
# for the wrong OS.
PS4_PKG_CONFIG ?= $(shell for c in $(PS4_PAYLOAD_SDK)/bin/orbis-pkg-config $(PS4_PAYLOAD_SDK)/bin/openorbis-pkg-config $(OPENORBIS)/bin/openorbis-pkg-config; do [ -x "$$c" ] && { printf '%s' "$$c"; break; }; done)
PS4_CURL_CONFIG ?= $(shell for c in $(PS4_PAYLOAD_SDK)/bin/orbis-curl-config $(PS4_PAYLOAD_SDK)/bin/curl-config $(PS4_PAYLOAD_SDK)/bin/openorbis-curl-config; do [ -x "$$c" ] && { printf '%s' "$$c"; break; }; done)
_HAS_PS4_CURL := $(shell if [ -n "$(strip $(PS4_PKG_CONFIG))" ] && $(PS4_PKG_CONFIG) --exists libcurl >/dev/null 2>&1; then echo 1; elif [ -n "$(strip $(PS4_CURL_CONFIG))" ]; then echo 1; fi)
ifneq ($(_HAS_PS4_CURL),1)
$(info [INFO] PS4 external libcurl not found — URL downloader disabled)
$(info [INFO] run tools/fetch_pacbrew_ps4.sh to install PacBrew ps4-openorbis-portlibs)
$(info [INFO] or point PS4_PKG_CONFIG / PS4_CURL_CONFIG at a target config tool)
override ENABLE_LIBCURL := 0
else
ifneq ($(strip $(PS4_PKG_CONFIG)),)
PS4_CURL_CFLAGS := $(shell $(PS4_PKG_CONFIG) --cflags libcurl)
PS4_CURL_LIBS := $(shell $(PS4_PKG_CONFIG) --static --libs libcurl)
else
PS4_CURL_CFLAGS := $(shell $(PS4_CURL_CONFIG) --cflags)
PS4_CURL_LIBS := $(shell $(PS4_CURL_CONFIG) --static-libs)
endif
CFLAGS += -DENABLE_LIBCURL=1 $(PS4_CURL_CFLAGS)
LIBS += $(PS4_CURL_LIBS)
# Same trust-store contract as PS5: the PacBrew curl package ships the
# Mozilla bundle, and HTTPS verification must not degrade silently.
PS4_CA_BUNDLE ?= $(PS4_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt
ifeq ($(wildcard $(PS4_CA_BUNDLE)),)
$(error PS4 libcurl CA bundle not found: $(PS4_CA_BUNDLE) — override with PS4_CA_BUNDLE=<path>)
endif
GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
CURL_CA_BUNDLE_SRC := $(PS4_CA_BUNDLE)
$(info [INFO] URL downloads: PacBrew PS4 libcurl + embedded CA bundle)
endif
endif
else
_HAS_CURL := $(shell echo '\#include <curl/curl.h>' | $(CC) -xc -fsyntax-only - 2>/dev/null && echo 1 || echo 0)
ifneq ($(_HAS_CURL),1)
$(info [INFO] libcurl headers not found — disabling ENABLE_LIBCURL)
override ENABLE_LIBCURL := 0
else
CFLAGS += -DENABLE_LIBCURL=1
LIBS += -lcurl
endif
endif
endif
ifeq ($(ENABLE_LIBCURL),1)
SOURCES += src/transfer/backend_curl.c
endif
# Native BitTorrent engine for magnet links. PS5 needs a libtorrent-rasterbar
# port installed into its payload SDK; host libraries must never be linked
# into a console payload.
ENABLE_LIBTORRENT ?= 0
ifeq ($(ENABLE_LIBTORRENT),1)
ifeq ($(TARGET),ps5)
PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
ifneq ($(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libtorrent-rasterbar && echo 1),1)
$(error PS5 magnet downloads require a PS5 libtorrent-rasterbar port in the payload SDK)
endif
TORRENT_CFLAGS := $(shell $(PS5_PKG_CONFIG) --cflags libtorrent-rasterbar)
TORRENT_LIBS := $(shell $(PS5_PKG_CONFIG) --static --libs libtorrent-rasterbar)
else
ifneq ($(shell pkg-config --exists libtorrent-rasterbar 2>/dev/null && echo 1),1)
$(error ENABLE_LIBTORRENT=1 requires libtorrent-rasterbar development files)
endif
TORRENT_CFLAGS := $(shell pkg-config --cflags libtorrent-rasterbar)
TORRENT_LIBS := $(shell pkg-config --libs libtorrent-rasterbar)
ifeq ($(HOST_OS),Darwin)
TORRENT_BOOST_PREFIX := $(shell brew --prefix boost 2>/dev/null)
TORRENT_CFLAGS += -I$(TORRENT_BOOST_PREFIX)/include
TORRENT_OPENSSL_PREFIX := $(shell brew --prefix openssl@3 2>/dev/null)
TORRENT_CFLAGS += -I$(TORRENT_OPENSSL_PREFIX)/include
endif
endif
CFLAGS += -DENABLE_LIBTORRENT=1
TORRENT_CXXFLAGS := -std=c++17 -Wall -Wextra -Wpedantic -Werror -O2 -fPIE \
$(PLATFORM_DEFS) -I./include $(TORRENT_CFLAGS)
ifeq ($(TARGET),ps5)
TORRENT_CXXFLAGS += -fvisibility=hidden -fno-inline-functions
endif
LIBS += $(TORRENT_LIBS)
TORRENT_OBJECT := $(OBJ_DIR)/transfer/backend_torrent.o
CXX_LINK := $(CXX)
else
CXX_LINK := $(CC)
endif
# ── libnfs detection ──────────────────────────────────────────────────────
ifeq ($(ENABLE_LIBNFS),1)
ifeq ($(TARGET),ps5)
PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
_HAS_LIBNFS := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libnfs && echo 1 || echo 0)
ifneq ($(_HAS_LIBNFS),1)
$(error PS5 NFS transfers require PacBrew ps5-payload-libnfs)
endif
CFLAGS += -DENABLE_LIBNFS=1 $(shell $(PS5_PKG_CONFIG) --cflags libnfs)
LIBS += $(shell $(PS5_PKG_CONFIG) --libs libnfs)
else
_HAS_LIBNFS := $(shell pkg-config --exists libnfs 2>/dev/null && echo 1 || echo 0)
ifneq ($(_HAS_LIBNFS),1)
$(error ENABLE_LIBNFS=1 requires libnfs development files)
endif
CFLAGS += -DENABLE_LIBNFS=1 $(shell pkg-config --cflags libnfs)
LIBS += $(shell pkg-config --static --libs libnfs)
endif
SOURCES += src/transfer/backend_nfs.c
endif
# Object files (handle both src/ and mcp/src/ paths)
OBJECTS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(filter src/%.c,$(SOURCES)))
OBJECTS += $(patsubst mcp/src/%.c,$(OBJ_DIR)/mcp/%.o,$(filter mcp/src/%.c,$(SOURCES)))
OBJECTS += $(patsubst $(BUILD_DIR)/generated/%.c,$(OBJ_DIR)/generated/%.o,$(filter $(BUILD_DIR)/generated/%.c,$(SOURCES)))
OBJECTS += $(TORRENT_OBJECT)
# FFI Object files
FFI_SOURCES := ffi/c_core/pal_ffi.c
FFI_OBJECTS := $(patsubst ffi/%.c,$(OBJ_DIR)/ffi/%.o,$(FFI_SOURCES))
# Object files without main (for unit tests and ffi library)
LIB_OBJECTS := $(filter-out $(OBJ_DIR)/app/main.o,$(OBJECTS))
# Dependency files
DEPENDS := $(patsubst $(OBJ_DIR)/%.o,$(DEP_DIR)/%.d,$(filter-out $(OBJ_DIR)/mcp/%.o,$(OBJECTS)))
DEPENDS += $(patsubst $(OBJ_DIR)/mcp/%.o,$(DEP_DIR)/mcp/%.d,$(filter $(OBJ_DIR)/mcp/%.o,$(OBJECTS)))
#============================================================================
# BUILD TARGETS
#============================================================================
.PHONY: all clean distclean install test help bin deploy deploy-i deploy-nc doctor-ps4
.PHONY: all-platforms release-all debug-all ffi ffi-java ffi-rust ffi-python resources
.PHONY: ps5-hook-blob
# ============================================================================
# PS5 NET FILTER HOOK — Kernel-safe compilation pipeline
#
# The hook functions (src/platform/ps5/ps5_net_filter_hook.c) run in ring-0 (kernel mode)
# and require special compiler flags that differ from the normal build.
#
# Pipeline:
# 1. Compile hook with kernel-safe flags → ps5_net_filter_hook.o
# 2. Extract .text.hook_connect and .text.hook_sendto sections → .bin
# 3. Generate C byte-array header → ps5_net_filter_hook_blob.h
#
# The blob header is included by ps5_net_filter.c to replace the placeholder
# byte arrays (g_hook_connect_code[], g_hook_sendto_code[]).
#
# Run manually before the PS5 build:
# make ps5-hook-blob
# ============================================================================
ifeq ($(TARGET),ps5)
HOOK_OBJ := $(OBJ_DIR)/ps5_net_filter_hook.o
HOOK_BIN := $(OBJ_DIR)/ps5_net_filter_hook.bin
HOOK_BLOB_H := $(BUILD_DIR)/generated/ps5/ps5_net_filter_hook_blob.h
# Kernel-safe compiler flags (MUST differ from normal CFLAGS)
HOOK_CFLAGS := \
-DPS5_HOOK_BUILD \
-DPLATFORM_PS5 \
-std=c11 \
-O2 \
-fno-stack-protector \
-mno-red-zone \
-fPIC \
-mcmodel=large \
-fno-plt \
-fno-common \
-fno-builtin \
-fno-exceptions \
-fomit-frame-pointer \
-I include/platform/ps5/
ps5-hook-blob: $(HOOK_BLOB_H)
@echo " [BLOB] $< generated ($(shell wc -c < $(HOOK_BIN) 2>/dev/null || echo '?') bytes)"
$(HOOK_BLOB_H): $(HOOK_BIN)
@echo " [XXD] $@"
@mkdir -p $(dir $@)
@xxd -i $< > $@
$(HOOK_BIN): $(HOOK_OBJ)
@echo " [OBJCOPY] $@"
@$(OBJCOPY) -O binary \
--only-section=.text.hook_connect \
--only-section=.text.hook_sendto \
$< $@
$(HOOK_OBJ): src/platform/ps5/ps5_net_filter_hook.c | $(OBJ_DIR)
@echo " [HOOK-CC] $<"
@$(CC) $(HOOK_CFLAGS) -c $< -o $@
endif # TARGET=ps5
resources: $(HTTP_RESOURCES_C)
$(HTTP_RESOURCES_C): tools/generate_resources.py $(WEB_RESOURCE_FILES)
@echo " [GEN] $@"
@mkdir -p $(dir $@)
@python3 tools/generate_resources.py > $@
.DEFAULT_GOAL := all
# FFI Shared Library output
ifeq ($(TARGET),macos)
FFI_OUTPUT := $(BIN_DIR)/libzftpd_ffi.dylib
FFI_LDFLAGS := -dynamiclib
else
FFI_OUTPUT := $(BIN_DIR)/libzftpd_ffi.so
FFI_LDFLAGS := -shared
endif
$(BIN_DIR) $(OBJ_DIR) $(DEP_DIR) $(BUILD_DIR)/tests $(OBJ_DIR)/ffi/c_core $(OBJ_DIR)/mcp:
@mkdir -p $@
ifeq ($(filter $(TARGET),ps4 ps5),)
all: $(OUTPUT_ELF) $(if $(ffi_langs),ffi)
else
all: $(OUTPUT_BIN) $(if $(ffi_langs),ffi)
endif
$(PROJECT): all
@true
# Link executable
$(OUTPUT_ELF): $(OBJECTS) | $(BIN_DIR)
@echo " [LD] $@"
@mkdir -p $(BIN_DIR)
@$(CXX_LINK) $(LDFLAGS) -o $@ $^ $(LIBS)
@echo "Build complete: $(PROJECT) ($(TARGET), $(BUILD_TYPE))"
# FFI Build Targets
.PHONY: ffi
ffi: $(FFI_OUTPUT)
ifneq ($(findstring java,$(ffi_langs)),)
@echo " [FFI] Building Java bindings..."
@$(MAKE) ffi-java
endif
ifneq ($(findstring rust,$(ffi_langs)),)
@echo " [FFI] Building Rust bindings..."
@$(MAKE) ffi-rust
endif
ifneq ($(findstring python,$(ffi_langs)),)
@echo " [FFI] Building Python bindings..."
@$(MAKE) ffi-python
endif
ifneq ($(findstring go,$(ffi_langs)),)
@echo " [FFI] Building Go bindings..."
@$(MAKE) ffi-go
endif
$(FFI_OUTPUT): $(LIB_OBJECTS) $(FFI_OBJECTS) | $(BIN_DIR)
@echo " [LD] $@ (Shared Library)"
@mkdir -p $(BIN_DIR)
@$(CXX_LINK) $(LDFLAGS) $(FFI_LDFLAGS) -fPIC -o $@ $(LIB_OBJECTS) $(FFI_OBJECTS) $(LIBS)
@echo "FFI C-Core built: $@"
# Build all supported platforms (best-effort: includes only toolchains found on the host).
TARGETS_ALL ?= $(shell \
echo macos; \
command -v gcc >/dev/null 2>&1 && echo linux || true; \
command -v ppu-gcc >/dev/null 2>&1 && echo ps3 || true; \
[ -d external/ps4-payload-sdk ] && echo ps4 || true; \
[ -d external/ps5-payload-sdk ] && echo ps5 || true)
# Build matrix toggles for ZHTTPD variant
ZHTTP_VARIANTS ?= 0 1
JAVA_HOME_PATH ?= $(shell /usr/libexec/java_home)
# Java FFI Target
.PHONY: ffi-java
ffi-java: $(FFI_OUTPUT)
@echo " [JAVAC] Compiling Java FFI bindings..."
@mkdir -p $(BIN_DIR)/ffi/java
@javac -J-Xint -d $(BIN_DIR)/ffi/java ffi/java/src/main/java/org/zftpd/ffi/*.java
@echo " [JAVAC] Compiling Java FFI tests..."
@javac -J-Xint -cp $(BIN_DIR)/ffi/java -d $(BIN_DIR)/ffi/java ffi/java/src/test/java/org/zftpd/ffi/*.java
@echo " [CC] Compiling JNI C wrapper..."
@$(CC) $(CFLAGS) $(FFI_LDFLAGS) -fPIC \
-I"$(JAVA_HOME_PATH)/include" \
-I"$(JAVA_HOME_PATH)/include/darwin" \
-I"./include" -I"./ffi/c_core" \
-o $(BIN_DIR)/libzftpd_ffi_java$(suffix $(FFI_OUTPUT)) \
ffi/java/src/main/c/pal_ffi_jni.c \
-L$(BIN_DIR) -lzftpd_ffi $(LIBS)
@echo " [JAVA] Running FFI tests..."
@java -Xint -Djava.library.path=$(BIN_DIR) -cp $(BIN_DIR)/ffi/java org.zftpd.ffi.FfiTests
@echo "Java FFI bindings built successfully."
# Rust FFI Target
.PHONY: ffi-rust
ffi-rust: $(FFI_OUTPUT)
@echo " [CARGO] Building Rust FFI bindings..."
@cd ffi/rust && cargo build --release
@echo " [CARGO] Running Rust FFI tests..."
ifeq ($(TARGET),macos)
@cd ffi/rust && DYLD_LIBRARY_PATH=../../build/macos/release cargo test
else
@cd ffi/rust && LD_LIBRARY_PATH=../../build/linux/release cargo test
endif
@echo "Rust FFI bindings built successfully."
# Python FFI Target
.PHONY: ffi-python
ffi-python: $(FFI_OUTPUT)
@echo " [PYTHON] Installing dependencies..."
@python3 -m pip install -q cffi pytest
@echo " [PYTHON] Running Python FFI tests..."
@cd ffi/python && PYTHONPATH=. pytest tests/
@echo "Python FFI bindings tested successfully."
# Go FFI Target
.PHONY: ffi-go
ffi-go: $(FFI_OUTPUT)
@echo " [GO] Compiling and Testing Go FFI bindings..."
ifeq ($(TARGET),macos)
@cd ffi/go/zftpd && DYLD_LIBRARY_PATH=../../../build/macos/release go test -v
else
@cd ffi/go/zftpd && LD_LIBRARY_PATH=../../../build/linux/release go test -v
endif
@echo "Go FFI bindings tested successfully."
all-platforms: release-all
release-all:
@set -e; \
for t in $(TARGETS_ALL); do \
echo "==> Building $$t (release)"; \
$(MAKE) TARGET=$$t BUILD_TYPE=release clean all; \
done
# Build all platforms in release with/without ZHTTPD (produces ELF and BIN where applicable)
release-matrix:
@set -e; \
for t in $(TARGETS_ALL); do \
for z in $(ZHTTP_VARIANTS); do \
echo "==> Building $$t (release, ENABLE_ZHTTPD=$$z)"; \
$(MAKE) TARGET=$$t BUILD_TYPE=release ENABLE_ZHTTPD=$$z \
ENABLE_LIBARCHIVE=$(ENABLE_LIBARCHIVE) \
ENABLE_LIBCURL=$(ENABLE_LIBCURL) \
clean all; \
done; \
done
debug-all:
@set -e; \
for t in $(TARGETS_ALL); do \
echo "==> Building $$t (debug)"; \
$(MAKE) TARGET=$$t BUILD_TYPE=debug clean all; \
done
# Generate the embedded trust store from PacBrew's Mozilla CA bundle (PS4/PS5).
ifneq ($(strip $(CURL_CA_BUNDLE_SRC)),)
$(OBJ_DIR)/transfer/backend_curl.o: $(GENERATED_CA_HEADER)
$(GENERATED_CA_HEADER): $(CURL_CA_BUNDLE_SRC) tools/embed_binary.py
@echo " [GEN] $@"
@python3 tools/embed_binary.py $< $@ zftpd_ca_bundle
endif
# Compile C source files
$(OBJ_DIR)/transfer/backend_torrent.o: src/transfer/backend_torrent.cpp | $(OBJ_DIR) $(DEP_DIR)
@echo " [CXX] $<"
@mkdir -p $(dir $@) $(dir $(DEP_DIR)/transfer/backend_torrent.d)
@$(CXX) $(TORRENT_CXXFLAGS) -MMD -MP -MF $(DEP_DIR)/transfer/backend_torrent.d -MT $@ -c $< -o $@
$(OBJ_DIR)/%.o: src/%.c | $(OBJ_DIR) $(DEP_DIR)
@echo " [CC] $<"
@mkdir -p $(dir $@) $(dir $(DEP_DIR)/$*.d)
@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/$*.d -MT $@ -c $< -o $@
# Compile generated C sources without writing into the source tree.
$(OBJ_DIR)/generated/%.o: $(BUILD_DIR)/generated/%.c | $(OBJ_DIR) $(DEP_DIR)
@echo " [CC] $<"
@mkdir -p $(dir $@) $(dir $(DEP_DIR)/generated/$*.d)
@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/generated/$*.d -MT $@ -c $< -o $@
# Compile FFI C source files (with -fPIC for shared library)
$(OBJ_DIR)/ffi/%.o: ffi/%.c | $(OBJ_DIR)/ffi/c_core
@echo " [CC] $< (FFI)"
@mkdir -p $(dir $@) $(dir $(DEP_DIR)/ffi/$*.d)
@$(CC) $(CFLAGS) -fPIC -MMD -MP -MF $(DEP_DIR)/ffi/$*.d -MT $@ -c $< -o $@
# Compile MCP C source files
$(OBJ_DIR)/mcp/%.o: mcp/src/%.c | $(OBJ_DIR)/mcp
@echo " [CC] $< (MCP)"
@mkdir -p $(dir $@) $(dir $(DEP_DIR)/mcp/$*.d)
@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/mcp/$*.d -MT $@ -c $< -o $@
# Include dependency files
-include $(DEPENDS)
#============================================================================
# UTILITY TARGETS
#============================================================================
# Clean build artifacts
clean:
@echo "Cleaning build artifacts..."
@rm -rf $(BUILD_DIR)
@rm -f src/*.o src/*.d
@rm -f tests/*.o tests/*.d
@rm -f $(PROJECT) $(PROJECT).elf $(PROJECT).bin
@rm -f tests/test_size
@echo "Clean complete."
# Deep clean (including configuration)
distclean: clean
@echo "Removing all generated files..."
@rm -f *~ core
@rm -rf build
@echo "Distclean complete."
# Install (simple copy for now)
install: $(OUTPUT_ELF)
@echo "Installing $(PROJECT)..."
@install -D -m 0755 $(OUTPUT_ELF) $(DESTDIR)/usr/local/bin/$(PROJECT)
@echo "Install complete."
# Run static analysis
analyze:
@echo "Running static analysis..."
@clang --analyze $(CFLAGS) $(SOURCES)
@echo "Analysis complete."
# Run tests
TEST_BINS := $(BUILD_DIR)/tests/test_size
TEST_BINS += $(BUILD_DIR)/tests/test_security
TEST_BINS += $(BUILD_DIR)/tests/test_path_security
TEST_BINS += $(BUILD_DIR)/tests/test_buffer_pool
TEST_BINS += $(BUILD_DIR)/tests/test_scratch
TEST_BINS += $(BUILD_DIR)/tests/test_alloc
TEST_BINS += $(BUILD_DIR)/tests/test_mlst_ascii
TEST_BINS += $(BUILD_DIR)/tests/test_list_flag
TEST_BINS += $(BUILD_DIR)/tests/test_instance
TEST_BINS += $(BUILD_DIR)/tests/test_instance_control
TEST_BINS += $(BUILD_DIR)/tests/test_chmod
TEST_BINS += $(BUILD_DIR)/tests/test_copy_atomic
TEST_BINS += $(BUILD_DIR)/tests/test_ftp_commands
TEST_BINS += $(BUILD_DIR)/tests/test_fileio_tree
TEST_BINS += $(BUILD_DIR)/tests/test_fileio_basic
TEST_BINS += $(BUILD_DIR)/tests/test_sendfile_console
TEST_BINS += $(BUILD_DIR)/tests/test_ps5_fw_offsets
ifeq ($(ENABLE_ZHTTPD),1)
TEST_BINS += $(BUILD_DIR)/tests/test_transfer
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_parallel
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_state
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_queue
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_orphans
ifeq ($(ENABLE_LIBTORRENT),1)
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_torrent
endif
TEST_BINS += $(BUILD_DIR)/tests/test_http_parser
TEST_BINS += $(BUILD_DIR)/tests/test_http_upload
TEST_BINS += $(BUILD_DIR)/tests/test_http_server
TEST_BINS += $(BUILD_DIR)/tests/test_http_response_stream
TEST_BINS += $(BUILD_DIR)/tests/test_http_api_common
TEST_BINS += $(BUILD_DIR)/tests/test_http_files
TEST_BINS += $(BUILD_DIR)/tests/test_http_process
TEST_BINS += $(BUILD_DIR)/tests/test_http_system
TEST_BINS += $(BUILD_DIR)/tests/test_http_games
TEST_BINS += $(BUILD_DIR)/tests/test_archive_path
TEST_BINS += $(BUILD_DIR)/tests/test_builtin_unzip
TEST_BINS += $(BUILD_DIR)/tests/test_zip_writer
TEST_BINS += $(BUILD_DIR)/tests/test_pal_volume
TEST_BINS += $(BUILD_DIR)/tests/test_http_archive
TEST_BINS += $(BUILD_DIR)/tests/test_dump_job
endif
ifeq ($(filter $(TARGET),linux macos),)
test: $(OUTPUT_BIN)
@echo "Tests skipped for TARGET=$(TARGET)"
else
test: $(OUTPUT_ELF) $(TEST_BINS)
@echo "Running tests..."
@set -e; for t in $(TEST_BINS); do ./$$t; done
endif
$(BUILD_DIR)/tests/test_instance_control: tests/test_instance_control.c src/app/instance_control.c tests/instance_control_stubs/sys/user.h tests/instance_control_stubs/sys/sysctl.h $(LIB_OBJECTS) | $(BUILD_DIR)/tests
@echo " [CC] $<"
@$(CXX_LINK) $(CFLAGS) -Itests/instance_control_stubs -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)
$(BUILD_DIR)/tests/test_sendfile_console: tests/test_sendfile_console.c src/platform/fileio/sendfile.c tests/sendfile_stubs/sys/mount.h $(LIB_OBJECTS) | $(BUILD_DIR)/tests
@echo " [CC] $<"
@$(CXX_LINK) $(CFLAGS) -Itests/sendfile_stubs -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)
$(BUILD_DIR)/tests/test_http_query: tests/test_http_query.c $(LIB_OBJECTS) | $(BUILD_DIR)/tests
@echo " [CC] $<"
@$(CXX_LINK) $(CFLAGS) -DFTP_AUTH_DELAY=0 -DFTP_PORT_ALLOW_FOREIGN_IP=1 -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)
$(BUILD_DIR)/tests/test_transfer_torrent: tests/test_transfer_torrent.cpp $(TORRENT_OBJECT) | $(BUILD_DIR)/tests
@echo " [CXX] $<"
@$(CXX) $(TORRENT_CXXFLAGS) -o $@ $< $(TORRENT_OBJECT) $(LDFLAGS) $(TORRENT_LIBS) -lpthread
$(BUILD_DIR)/tests/%: tests/%.c $(LIB_OBJECTS) | $(BUILD_DIR)/tests
@echo " [CC] $<"
@$(CXX_LINK) $(CFLAGS) -DFTP_AUTH_DELAY=0 -DFTP_PORT_ALLOW_FOREIGN_IP=1 -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)
bin: $(OUTPUT_BIN)
$(OUTPUT_BIN): $(OUTPUT_ELF) | $(BIN_DIR)
@echo " [STRIP] $@"
@command -v $(OBJCOPY) >/dev/null 2>&1 || { echo "error: '$(OBJCOPY)' not found (set OBJCOPY=... or install binutils)"; exit 1; }
ifeq ($(filter $(TARGET),ps4 ps5),)
@$(OBJCOPY) -O binary $< $@
else
@$(STRIP) --strip-unneeded -R .comment -R .GCC.command.line $< -o $@
endif
deploy: $(OUTPUT_BIN)
@if [ "$(TARGET)" = "ps4" ]; then \
command -v socat >/dev/null 2>&1 || { echo "error: 'socat' non trovato (brew install socat)"; exit 1; }; \
$(PS4_DEPLOY) -h $(PS4_HOST) -p $(PS4_PORT) $(OUTPUT_BIN); \
elif [ "$(TARGET)" = "ps5" ]; then \
$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $(OUTPUT_BIN); \
else \
echo "error: deploy supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
fi
deploy-i: $(OUTPUT_BIN)
@if [ "$(TARGET)" = "ps4" ]; then \
command -v socat >/dev/null 2>&1 || { echo "error: 'socat' non trovato (brew install socat)"; exit 1; }; \
$(PS4_DEPLOY) -h $(PS4_HOST) -p $(PS4_PORT) -i $(OUTPUT_BIN); \
elif [ "$(TARGET)" = "ps5" ]; then \
$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) -i $(OUTPUT_BIN); \
else \
echo "error: deploy-i supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
fi
deploy-nc: $(OUTPUT_BIN)
@if [ "$(TARGET)" = "ps4" ]; then \
command -v nc >/dev/null 2>&1 || { echo "error: 'nc' (netcat) non trovato"; exit 1; }; \
echo "Sending $(OUTPUT_BIN) to $(PS4_HOST):$(PS4_PORT) via nc..."; \
nc -w 10 $(PS4_HOST) $(PS4_PORT) < $(OUTPUT_BIN); \
elif [ "$(TARGET)" = "ps5" ]; then \
command -v nc >/dev/null 2>&1 || { echo "error: 'nc' (netcat) non trovato"; exit 1; }; \
echo "Sending $(OUTPUT_BIN) to $(PS5_HOST):$(PS5_PORT) via nc..."; \
nc -w 10 $(PS5_HOST) $(PS5_PORT) < $(OUTPUT_BIN); \
else \
echo "error: deploy-nc supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
fi
doctor-ps4:
@echo "TARGET=ps4 prerequisiti (macOS/Homebrew)"
@echo ""
@echo "llvm-config:"
@{ command -v llvm-config >/dev/null 2>&1 && echo " OK: $$(command -v llvm-config)" || true; }
@{ [ -x "/opt/homebrew/opt/llvm/bin/llvm-config" ] && echo " OK: /opt/homebrew/opt/llvm/bin/llvm-config" || true; }
@echo ""
@echo "ld.lld:"
@{ command -v ld.lld >/dev/null 2>&1 && echo " OK: $$(command -v ld.lld)" || true; }
@{ command -v brew >/dev/null 2>&1 && p=$$(brew --prefix lld 2>/dev/null || true) && [ -n "$$p" ] && [ -x "$$p/bin/ld.lld" ] && echo " OK: $$p/bin/ld.lld" || true; }
@echo ""
@echo "Se manca qualcosa:"
@echo " brew install llvm lld"
@echo " export PATH=\"/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/lld/bin:$$PATH\""
#============================================================================
# HELP
#============================================================================
help:
@echo "Multi-Platform FTP Server Build System"
@echo "========================================"
@echo ""
@echo "Targets:"
@echo " all - Build the FTP server (default)"
@echo " clean - Remove build artifacts"
@echo " distclean - Deep clean (remove all generated files)"
@echo " install - Install to system (requires root)"
@echo " analyze - Run static analysis (requires clang)"
@echo " test - Run test suite"
@echo " help - Display this help message"
@echo " resources - Generate embedded web resources under build/.../generated"
@echo ""
@echo "Variables:"
@echo " TARGET - Target platform (linux, macos, ps3, ps4, ps5)"
@echo " BUILD_TYPE - Build configuration (debug, release)"
@echo " ENABLE_LIBARCHIVE - Enable archive extraction (0/1, requires libarchive)"
@echo " ENABLE_LIBCURL - Enable URL downloads (0/1, requires libcurl)"
@echo " WEB_DEPLOY_DIR - Deprecated (web UI is now embedded in binary)"
@echo ""
@echo "Examples:"
@echo " make # Build for Linux (release)"
@echo " make TARGET=macos # Build for macOS"
@echo " make TARGET=ps5 # Build for PS5"
@echo " make BUILD_TYPE=debug # Build debug version"
@echo " make TARGET=ps4 BUILD_TYPE=debug # PS4 debug build"
@echo " make ENABLE_LIBARCHIVE=1 ENABLE_LIBCURL=1 # With extract + download"
@echo " make resources # Regenerate embedded web resources"
@echo ""
@echo "Current configuration:"
@echo " Target: $(TARGET)"
@echo " Build type: $(BUILD_TYPE)"
@echo " Compiler: $(CC)"
@echo " libarchive: $(ENABLE_LIBARCHIVE)"
@echo " libcurl: $(ENABLE_LIBCURL)"
@echo ""
#============================================================================
# COMPILATION DATABASE (for IDE/LSP support)
#============================================================================
compile_commands.json:
@echo "Generating compilation database..."
@bear -- make clean all
@echo "Compilation database generated."
.PHONY: compile_commands.json
#============================================================================
#============================================================================
# WEB DEPLOY — DEPRECATED
#
# All web assets are now embedded directly in the binary via
# http_resources.c (run make resources to regenerate).
# The web-deploy target is kept as a no-op for backward compatibility.
#============================================================================
web-deploy:
@echo " [WEB] web-deploy is deprecated — web UI is now embedded in the binary"
@echo " [WEB] Resources are generated automatically under build/.../generated"
@echo " [WEB] Done — $(shell find web -path web/legacy -prune -o \( -name '*.css' -o -name '*.js' \) -print | wc -l | tr -d ' ') files deployed"
#============================================================================
# Flag changes must invalidate object files.
#
# Objects do not record the flags they were built with, so toggling a feature
# (ENABLE_LIBTORRENT, ENABLE_PKG_INSTALL, ...) used to relink stale objects and
# fail with undefined symbols. The stamp is rewritten only when the flags
# actually change, and every object depends on it, so a real change rebuilds.
#============================================================================
FLAG_STAMP := $(OBJ_DIR)/.build-flags
.PHONY: FORCE
FORCE:
$(FLAG_STAMP): FORCE
@mkdir -p $(OBJ_DIR)
@printf '%s\n' '$(CFLAGS)' '$(LDFLAGS)' '$(LIBS)' > $@.tmp
@if ! cmp -s $@.tmp $@; then \
echo " [FLAGS] build flags changed — objects will be rebuilt"; \
mv -f $@.tmp $@; \
fi
@rm -f $@.tmp
$(OBJECTS) $(LIB_OBJECTS): $(FLAG_STAMP)
#============================================================================
# PS5 installer helper
#============================================================================
ifneq ($(INSTALL_HELPER_ELF),)
$(INSTALL_HELPER_ELF): src/platform/ps5/install_helper/install_helper.c | $(BIN_DIR)
@echo " [CC] $<"
@$(CXX_LINK) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS) -lSceIpmi -lSceAppInstUtil
$(INSTALL_HELPER_BLOB): $(INSTALL_HELPER_ELF) tools/gen_blob.py
@mkdir -p $(dir $@)
@python3 tools/gen_blob.py $< install_helper_elf $@
$(OBJ_DIR)/generated/install_helper_blob.o: $(INSTALL_HELPER_BLOB)
endif