A project to bring Zig to the Sony PlayStation Portable
## Why Zig on the PSP? In the PSP programming community, many libraries, tools, and other features are written in C or C++, which as we know has its problems with writing clean, reusable, and high quality code. Given that the core objectives of Zig as a language are to allow us to create well-designed and reusable software, Zig seems like a perfect fit for integrating older PSP libraries while striving to develop higher quality software! ## Special Thanks Special thanks is given to the [Rust-PSP team](https://github.com/overdrivenpotato/rust-psp) whose efforts influenced and helped to get this project off the ground. No harm is intended, and it's thanks to you Rustaceans that fellow Ziguanas can program for the PSP. ## Requirements - **Zig 0.16.0** No legacy PSPSDK or external C toolchain is required. All build tools (`zPRXGen`, `zSFOTool`, `zPBPTool`) are written in Zig and built automatically. ## Getting Started ### 1. Add pspsdk to your project ```bash zig fetch --save=pspsdk git+https://github.com/zPSP-Dev/Zig-PSP ``` This adds the dependency to your `build.zig.zon` automatically. ### 2. Set up your build.zig ```zig const std = @import("std"); const pspsdk = @import("pspsdk"); pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); _ = pspsdk.buildPspEboot(b, .{ .name = "my_app", .root_source_file = b.path("src/main.zig"), .title = "My App Title", .optimize = optimize, }, .{}); } ``` `buildPspEboot` runs the full pipeline (Zig -> ELF -> PRX -> SFO -> PBP) and installs the output to `zig-out/bin/my_app/`. Run `zig build` to produce `EBOOT.PBP`, `app.prx`, and `app.elf`. Optional PBP asset fields are available on `PspEbootOptions`: `.icon0`, `.icon1`, `.pic0`, `.pic1`, `.snd0` (all `?std.Build.LazyPath`). The output directory defaults to the app name but can be overridden via `PspOutputOptions.dir`. ### Lower-level API for engine/framework integration If your build system needs to create and configure the executable itself (e.g. to attach engine modules or link additional dependencies), two lower-level functions are available: - **`configurePspExecutable(exe)`** — applies PSP-specific settings (linker script, entry point, relocation emission) and adds the `pspsdk` module import to an existing executable. - **`addEbootSteps(b, exe, options)`** — runs the ELF -> PRX -> SFO -> PBP pipeline on an existing PSP executable and optionally installs artifacts. ```zig const std = @import("std"); const pspsdk = @import("pspsdk"); pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const psp_target = pspsdk.getPspTarget(b); const exe = b.addExecutable(.{ .name = "main", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = psp_target, .optimize = optimize, .strip = false, .imports = &.{ .{ .name = "engine", .module = engine_mod }, }, }), }); // Apply PSP build settings (linker script, entry point, pspsdk import) pspsdk.configurePspExecutable(exe); // Run the packaging pipeline and install artifacts _ = pspsdk.addEbootSteps(b, exe, .{ .title = "My App", .output_dir = "my_app", }); } ``` `buildPspEboot` is implemented as a thin wrapper around these two functions and remains the recommended API for simple projects. ### 3. Write your app Every PSP app needs a `module_info` comptime call and the panic handler override: ```zig const std = @import("std"); const sdk = @import("pspsdk"); // Required: overrides the default panic handler which pulls in posix symbols. pub const panic = sdk.extra.debug.panic; // Required: routes std.debug.print through PSP I/O instead of posix. pub const std_options_debug_threaded_io: ?*std.Io.Threaded = null; pub const std_options_debug_io: std.Io = sdk.extra.Io.psp_io; pub fn std_options_cwd() std.Io.Dir { return .{ .handle = -1 }; } comptime { asm (sdk.extra.module.module_info("My App Name", .{ .mode = .User }, 1, 0)); } pub fn main(_: std.process.Init) !void { sdk.extra.utils.enableHBCB(); sdk.extra.debug.screenInit(); sdk.extra.debug.print("Hello from Zig!", .{}); } ``` ### API Tiers The SDK exposes PSP functions through two tiers: | Tier | Example | Description | |---|---|---| | Raw C bindings | `sdk.c.LoadExecForUser.sceKernelExitGame()` | Auto-generated stubs, one namespace per firmware module | | Zig bindings | `sdk.sceKernelExitGame()` or `sdk.kernel.exit_game()` | Idiomatic wrappers with two access routes: sce-prefixed re-exports at the package root, or snake_case sub-namespaces | Sub-namespaces: `sdk.adhoc`, `sdk.atrac3`, `sdk.audio`, `sdk.audiocodec`, `sdk.ctrl`, `sdk.display`, `sdk.dmac`, `sdk.ge`, `sdk.gu`, `sdk.gum`, `sdk.hprm`, `sdk.http`, `sdk.impose`, `sdk.internal`, `sdk.io`, `sdk.jpeg`, `sdk.kermit`, `sdk.kernel`, `sdk.mp3`, `sdk.mpeg`, `sdk.net`, `sdk.openpsid`, `sdk.power`, `sdk.reg`, `sdk.rtc`, `sdk.ssl`, `sdk.umd`, `sdk.usb`, `sdk.usbcam`, `sdk.usbstor`, `sdk.utility`, `sdk.wlan`. The utility layer lives under `sdk.extra`: `sdk.extra.debug`, `sdk.extra.module`, `sdk.extra.utils`, `sdk.extra.allocator`, `sdk.extra.vram`, `sdk.extra.Io`, `sdk.extra.constants`, `sdk.extra.net`. ## Building This Repository ```bash # Build everything (tools + examples) -- default zig build # Build only the host tools (zPRXGen, zSFOTool, zPBPTool) zig build tools # Build only examples zig build examples # Build examples as a standalone sub-project (exercises the package API) cd examples && zig build ``` Output lands in `zig-out/bin/