Skip to content

Commit 2af5efb

Browse files
AndrewAltimitAI Agent BotclaudeAI Review Agent
authored
Add Platform SDK abstractions: 36+ high-level modules, 30 examples (#4)
* Add SDK platform abstractions: sync, cache, DMA, VFPU math, audio mixer, framebuffer, memory Push reusable platform infrastructure into the SDK so every project benefits: - psp::sync — Extract SpinMutex from debug.rs, add SpinRwLock, SPSC ring buffer, UncachedBox<T> - psp::cache — Type-safe CachedPtr/UncachedPtr with flush/invalidate conversions - psp::me::MeExecutor — High-level ME task submission with poll/wait and shared uncached state - psp::dma — DmaTransfer handle with memcpy_dma/vram_blit_dma and sceDmac syscall bindings - psp::simd — VFPU-accelerated Vec4/Mat4 math, color ops, easing functions - psp::audio_mixer — Multi-channel PCM mixer with volume/panning/fade - psp::framebuffer — DoubleBuffer, DirtyRect tracking, LayerCompositor - psp::mem — Typed partition allocators (Partition2Alloc/Partition3Alloc) preventing ME pointer misuse Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Phase 2 SDK abstractions: io, thread, input, time + Phase 1 bugfixes Bugfixes: - Fix MeExecutor::submit() race condition by storing real_task/real_arg in dedicated MeSharedState fields instead of double-writing boot_params - Fix audio_mixer volume mixing i32 overflow by using i64 intermediates - Document sceDmacMemcpy synchronous behavior in dma.rs - Remove unnecessary blanket #![allow(unsafe_op_in_unsafe_fn)] from simd.rs New modules: - psp::io - File (RAII), ReadDir iterator, read_to_vec, write_bytes, stat, create_dir, remove_file, remove_dir, rename - psp::thread - ThreadBuilder, JoinHandle, spawn() with closure trampoline, sleep_ms, current_thread_id - psp::input - Controller with press/release detection, analog deadzone - psp::time - Instant, Duration, DateTime, FrameTimer Enhanced modules: - psp::sync - Add Semaphore and EventFlag kernel primitive wrappers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Phase 3 SDK abstractions: display, power, audio, dialog, net, wlan, callback + bugfixes Code review fixes: - Wrap thread trampoline in catch_unwind for panic safety - Simplify DmaTransfer to DmaResult (remove fake async API) - Add #[repr(C, align(64))] to MeSharedState for cache coherency - Document DoubleBuffer::new() init requirement - Add vec4_length and vec4_distance to VFPU simd module New SDK modules: - psp::display - vblank sync, framebuffer get/set - psp::power - clock control, battery info, AC detection - psp::audio - AudioChannel with RAII reserve/release - psp::callback - one-call exit callback setup - psp::dialog - blocking message/confirm/error dialogs - psp::wlan - WiFi hardware status - psp::net - init/term, AP connect, TcpStream, UdpSocket, DNS resolve Update examples to use new SDK modules: - clock-speed, file-io, time, wlan, audio-tone, msg-dialog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Phase 4 SDK abstractions: gu_ext, timer, usb, config, image, font + power extensions New modules: - psp::gu_ext — GU state snapshot/restore, 2D setup, SpriteVertex, SpriteBatch - psp::timer — Alarm (one-shot closure trampoline) and VTimer (RAII wrapper) - psp::usb — USB bus control and UsbStorageMode (RAII mass storage) - psp::config — Binary key-value config persistence (RCFG format) - psp::image — Hardware JPEG decode (sceJpeg*) and software BMP 24/32-bit decode - psp::font — FontLib, Font, FontRenderer with VRAM glyph atlas (PsmT8 + CLUT) Extended: - psp::power — on_power_event (RAII callback), prevent_sleep, prevent_display_off Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 6 SDK showcase examples and expand README Platform SDK docs New examples demonstrating Phase 1-4 SDK modules: thread-sync (SpinMutex + spawn), input-analog (Controller deadzone), config-save (RCFG format), timer-alarm (Alarm + VTimer), net-http (WiFi + TcpStream), system-font (FontLib + FontRenderer + GU). README revised: "High-Level Utilities" replaced with comprehensive "Platform SDK" section (30+ modules in 9 domain categories), examples table updated with 6 new entries, structure description updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Phase 5 SDK hardening: soundness fixes, correctness improvements, 6 new modules Critical soundness fixes: UncachedBox Drop now calls drop_in_place, SpscQueue Drop drains remaining items, PartitionAlloc tracks initialization state to prevent UB on uninit Drop, Alarm restructured with AtomicU8 state machine to eliminate interrupt-context allocation and TOCTOU race between Drop and trampoline. Medium correctness fixes: JoinHandle::join returns thread exit status, dialog/connect_ap polling timeouts, config serialize overflow checks, power callback cleanup on failure, callback registration error check, audio mixer volume saturation, glyph atlas eviction bounds, SpriteBatch dcache flush, sleep_ms overflow protection, removed dead mixer code. Low-priority improvements: cached tick resolution const, deprecated enable_home_button, Default impls, null-termination asserts, doc fixes, remapf division-by-zero guard. New modules: psp::http (RAII HTTP client), psp::mp3 (hardware MP3 decoder), psp::osk (on-screen keyboard), psp::rtc (extended RTC operations), psp::savedata (system save/load dialog), psp::system_param (system settings). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace deprecated enable_home_button() with setup_exit_callback() across all examples, add 4 new SDK examples Migrate all 26 existing examples from the deprecated psp::enable_home_button() to psp::callback::setup_exit_callback().unwrap(). Add 4 new examples showcasing Phase 5 SDK modules: http-client (psp::http), savedata (psp::savedata), osk-input (psp::osk), and rtc-sysinfo (psp::rtc + psp::system_param). Update README code snippets and examples table accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CI: add -Zjson-target-spec for newer nightly toolchains Post-January 2026 nightly builds destabilized custom JSON target specs (rust-lang/rust#151534), requiring an explicit -Zjson-target-spec flag when passing a .json path to cargo --target. Without this, cargo-psp fails with "`.json` target specs require -Zjson-target-spec" in CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix soundness and correctness issues from AI code review - audio.rs: Validate buffer length in safe output_blocking/output_blocking_panning functions to prevent out-of-bounds reads by the PSP audio hardware. Store AudioFormat on AudioChannel to compute required buffer size. - audio_mixer.rs: Fix fade_out/fade_in duration capping at 256 frames by switching fade_level/fade_step to 16.16 fixed-point arithmetic, allowing accurate fades of any duration. - power.rs: Delete callback UID via sceKernelDeleteCallback on drop to prevent kernel resource leaks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CI: add pre-checkout cleanup to all workflow jobs The gemini-review, codex-review, agent-review-response, agent-failure-handler (pr-validation.yml) and create-release (main-ci.yml) jobs were missing pre-checkout cleanup. When the ci job runs Docker containers that create root-owned files in outputs/, subsequent jobs fail with EACCES when actions/checkout tries to clean the workspace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address AI review feedback (iteration 2) Automated fix by Claude in response to Gemini/Codex review. Iteration: 2/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com> * fix: address AI review feedback (iteration 3) - Fix JoinHandle::drop double-free: check thread completion via sceKernelWaitThreadEnd before deciding whether to free the closure. Previously, dropping an unjoined handle for a thread that already finished would double-free the closure (trampoline freed it, then drop freed it again). - Fix audio mixer 1-sample silence gap on loop: remove `continue` after resetting position to 0, so the current output frame reads from position 0 instead of being skipped. - Replace unsafe transmute with TryFromPrimitive in system_param: language(), date_format(), and time_format() now use safe try_from() conversion instead of transmute, preventing UB if firmware returns an unexpected enum value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: eliminate JoinHandle double-free race via shared ThreadPayload Replace the raw closure pointer with a ThreadPayload struct containing the closure and an AtomicBool flag. The trampoline sets the flag after consuming the closure, and Drop checks it after terminating the thread. This eliminates the race where a thread finishes between the zero-timeout wait check and sceKernelTerminateDeleteThread, which previously caused a double-free. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: validate thread name null termination in spawn_inner Both Gemini and Codex flagged that safe code could pass a non-null-terminated byte slice to sceKernelCreateThread, causing the kernel to read past the buffer. Add validation that name ends with \0 before proceeding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: validate stereo buffer length parity in submit_samples Reject odd-length buffers in submit_samples to prevent an out-of-bounds panic when the mixing loop reads stereo pairs (L,R) from a looping channel that wraps around on a buffer with non-even length. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: AI Agent Bot <ai-agent@localhost> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: AI Review Agent <ai-review-agent@localhost>
1 parent 2b510bb commit 2af5efb

79 files changed

Lines changed: 9750 additions & 259 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main-ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ jobs:
188188
permissions:
189189
contents: write
190190
steps:
191+
- name: Pre-checkout cleanup
192+
run: |
193+
for item in outputs target/psp-std-sysroot psp_output_file.log .git/index.lock; do
194+
if [ -d "$item" ] || [ -f "$item" ]; then
195+
docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \
196+
"rm -rf /workspace/$item" 2>/dev/null || \
197+
sudo rm -rf "$item" 2>/dev/null || true
198+
fi
199+
done
200+
191201
- name: Checkout
192202
uses: actions/checkout@v4
193203
with:

.github/workflows/pr-validation.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ jobs:
137137
outputs:
138138
status: ${{ steps.review.outputs.status }}
139139
steps:
140+
- name: Pre-checkout cleanup
141+
run: |
142+
for item in outputs target/psp-std-sysroot psp_output_file.log .git/index.lock; do
143+
if [ -d "$item" ] || [ -f "$item" ]; then
144+
docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \
145+
"rm -rf /workspace/$item" 2>/dev/null || \
146+
sudo rm -rf "$item" 2>/dev/null || true
147+
fi
148+
done
149+
140150
- name: Checkout
141151
uses: actions/checkout@v4
142152
with:
@@ -224,6 +234,16 @@ jobs:
224234
outputs:
225235
status: ${{ steps.review.outputs.status }}
226236
steps:
237+
- name: Pre-checkout cleanup
238+
run: |
239+
for item in outputs target/psp-std-sysroot psp_output_file.log .git/index.lock; do
240+
if [ -d "$item" ] || [ -f "$item" ]; then
241+
docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \
242+
"rm -rf /workspace/$item" 2>/dev/null || \
243+
sudo rm -rf "$item" 2>/dev/null || true
244+
fi
245+
done
246+
227247
- name: Checkout
228248
uses: actions/checkout@v4
229249
with:
@@ -301,6 +321,16 @@ jobs:
301321
GITHUB_TOKEN: ${{ secrets.AGENT_TOKEN }}
302322
GITHUB_REPOSITORY: ${{ github.repository }}
303323
steps:
324+
- name: Pre-checkout cleanup
325+
run: |
326+
for item in outputs target/psp-std-sysroot psp_output_file.log .git/index.lock; do
327+
if [ -d "$item" ] || [ -f "$item" ]; then
328+
docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \
329+
"rm -rf /workspace/$item" 2>/dev/null || \
330+
sudo rm -rf "$item" 2>/dev/null || true
331+
fi
332+
done
333+
304334
- name: Checkout
305335
uses: actions/checkout@v4
306336
with:
@@ -380,6 +410,16 @@ jobs:
380410
GITHUB_TOKEN: ${{ secrets.AGENT_TOKEN }}
381411
GITHUB_REPOSITORY: ${{ github.repository }}
382412
steps:
413+
- name: Pre-checkout cleanup
414+
run: |
415+
for item in outputs target/psp-std-sysroot psp_output_file.log .git/index.lock; do
416+
if [ -d "$item" ] || [ -f "$item" ]; then
417+
docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \
418+
"rm -rf /workspace/$item" 2>/dev/null || \
419+
sudo rm -rf "$item" 2>/dev/null || true
420+
fi
421+
done
422+
383423
- name: Checkout
384424
uses: actions/checkout@v4
385425
with:

Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The upstream project is maintained at a low cadence (3-5 commits/year, mostly ni
1313
psp::module!("sample_module", 1, 1);
1414

1515
fn psp_main() {
16-
psp::enable_home_button();
16+
psp::callback::setup_exit_callback().unwrap();
1717
psp::dprintln!("Hello PSP from rust!");
1818
}
1919
```
@@ -51,7 +51,88 @@ The `psp` crate provides ~825 syscall bindings covering every major PSP subsyste
5151
| **Kernel-only** | `sircs` | 1 | Infrared remote control (SIRCS protocol) |
5252
| **Kernel-only** | `codec` | 10 | Hardware video/audio codec control |
5353

54-
### High-Level Utilities
54+
### Platform SDK
55+
56+
36+ high-level modules providing safe, idiomatic Rust APIs with RAII resource management over PSP syscalls.
57+
58+
#### System & Lifecycle
59+
60+
| Module | Key API | Description |
61+
|--------|---------|-------------|
62+
| `psp::callback` | `setup_exit_callback()` | Register exit callback (spawns handler thread) |
63+
| `psp::power` | `get_clock()`, `set_clock()`, `battery_info()` | CPU/bus clock control, battery status, AC detection |
64+
| `psp::display` | `wait_vblank()`, `set_framebuf()` | VBlank sync, framebuffer management |
65+
| `psp::time` | `Instant`, `Duration`, `FrameTimer` | Microsecond timing, frame rate measurement |
66+
| `psp::timer` | `Alarm`, `VTimer` | One-shot alarms (closure-based), virtual timers |
67+
| `psp::dialog` | `message_dialog()`, `confirm_dialog()` | System message/confirmation/error dialogs |
68+
| `psp::system_param` | `language()`, `nickname()`, `timezone_offset()` | System parameter queries (language, date/time format, etc.) |
69+
| `psp::rtc` | `Tick`, `format_rfc3339()`, `day_of_week()` | Extended RTC: tick arithmetic, RFC 3339, UTC/local conversion |
70+
71+
#### Threading & Sync
72+
73+
| Module | Key API | Description |
74+
|--------|---------|-------------|
75+
| `psp::thread` | `spawn()`, `JoinHandle`, `sleep_ms()` | Thread creation with closure trampolines, join/sleep |
76+
| `psp::sync` | `SpinMutex`, `SpinRwLock`, `Semaphore`, `EventFlag` | Spinlocks, kernel semaphores, event flags, SPSC queue |
77+
78+
#### Input
79+
80+
| Module | Key API | Description |
81+
|--------|---------|-------------|
82+
| `psp::input` | `Controller`, `analog_x_f32()`, `is_pressed()` | Button press/release detection, analog deadzone normalization |
83+
| `psp::osk` | `text_input()`, `OskBuilder` | On-screen keyboard for user text input (UTF-16 handling) |
84+
85+
#### File I/O & Config
86+
87+
| Module | Key API | Description |
88+
|--------|---------|-------------|
89+
| `psp::io` | `File`, `ReadDir`, `read_to_vec()`, `write_bytes()` | RAII file handles, directory iteration, convenience I/O |
90+
| `psp::config` | `Config`, `save()`, `load()` | Key-value store with binary RCFG format (bool/i32/f32/str) |
91+
| `psp::savedata` | `Savedata`, `save()`, `load()` | PSP system save/load dialog with auto-save/auto-load modes |
92+
93+
#### Audio
94+
95+
| Module | Key API | Description |
96+
|--------|---------|-------------|
97+
| `psp::audio` | `AudioChannel`, `output_blocking()` | RAII audio channels with PCM output |
98+
| `psp::audio_mixer` | `Mixer`, `Channel` | Multi-channel PCM software mixer |
99+
| `psp::mp3` | `Mp3Decoder`, `decode_frame()` | Hardware-accelerated MP3 decoding to PCM samples |
100+
101+
#### Graphics & Rendering
102+
103+
| Module | Key API | Description |
104+
|--------|---------|-------------|
105+
| `psp::framebuffer` | `DoubleBuffer`, `LayerCompositor` | Double-buffered framebuffer, dirty-rect tracking |
106+
| `psp::gu_ext` | `setup_2d()`, `SpriteBatch`, `GuStateSnapshot` | 2D rendering helpers, sprite batching, GU state save/restore |
107+
| `psp::simd` | `Vec4`, `Mat4` | VFPU-accelerated vector/matrix math, easing, color ops |
108+
| `psp::image` | `decode_jpeg()`, `decode_bmp()`, `load_image()` | Hardware JPEG decode, BMP 24/32-bit decode, auto-detect |
109+
| `psp::font` | `FontLib`, `Font`, `FontRenderer` | System PGF font loading, VRAM glyph atlas rendering |
110+
111+
#### Networking
112+
113+
| Module | Key API | Description |
114+
|--------|---------|-------------|
115+
| `psp::net` | `TcpStream`, `UdpSocket`, `connect_ap()` | WiFi connect, TCP/UDP sockets (RAII), DNS resolution |
116+
| `psp::http` | `HttpClient`, `get()`, `post()`, `RequestBuilder` | HTTP client with RAII template/connection/request lifecycle |
117+
| `psp::wlan` | `status()`, `is_available()` | WLAN module status query |
118+
119+
#### Hardware & Memory
120+
121+
| Module | Key API | Description |
122+
|--------|---------|-------------|
123+
| `psp::dma` | `memcpy_dma()`, `vram_blit_dma()` | DMA memory copy and VRAM blitting |
124+
| `psp::cache` | `CachedPtr`, `UncachedPtr` | Cache-aware pointers, dcache flush/invalidate helpers |
125+
| `psp::mem` | `Partition2Alloc`, `Partition3Alloc` | Typed partition memory allocators |
126+
| `psp::usb` | `UsbStorageMode`, `is_connected()` | USB bus control, mass storage mode (RAII) |
127+
128+
#### Kernel-Only (requires `--features kernel`)
129+
130+
| Module | Key API | Description |
131+
|--------|---------|-------------|
132+
| `psp::me` | `MeExecutor`, `me_boot()` | Media Engine coprocessor boot/task management |
133+
| `psp::hw` | `hw_read32()`, `hw_write32()`, `Register<T>` | Memory-mapped hardware register I/O |
134+
135+
#### Standalone Utilities
55136

56137
| Module | Description |
57138
|--------|-------------|
@@ -61,8 +142,6 @@ The `psp` crate provides ~825 syscall bindings covering every major PSP subsyste
61142
| `psp::benchmark()` | Cycle-accurate benchmarking via RTC |
62143
| `psp::math` | VFPU-accelerated `sinf`/`cosf`, full libm math library |
63144
| `psp::vfpu!()` | Inline VFPU (Vector FPU) assembly macros |
64-
| `psp::hw` | Memory-mapped hardware register I/O (kernel mode) |
65-
| `psp::me` | Media Engine coprocessor boot/task management (kernel mode) |
66145
| `psp::dprintln!()` | Thread-safe debug printing via `SpinMutex` |
67146

68147
## Features
@@ -78,12 +157,12 @@ The `psp` crate provides ~825 syscall bindings covering every major PSP subsyste
78157

79158
| Example | APIs Demonstrated | Description |
80159
|---------|-------------------|-------------|
81-
| `hello-world` | `dprintln!`, `enable_home_button` | Minimal PSP program |
160+
| `hello-world` | `dprintln!`, `psp::callback` | Minimal PSP program |
82161
| `cube` | `sceGu*`, `sceGum*`, VRAM alloc | Rotating 3D cube with lighting |
83162
| `rainbow` | `sceGu*`, vertex colors | Animated color gradient |
84163
| `gu-background` | `sceGu*`, VRAM alloc | Clear screen with solid color |
85164
| `gu-debug-print` | `sceGu*`, debug font | On-screen debug text via GU |
86-
| `clock-speed` | `scePower*` | Read/set CPU and bus clock speeds |
165+
| `clock-speed` | `psp::power` | Read/set CPU and bus clock speeds |
87166
| `time` | `sceRtc*` | Read and display real-time clock |
88167
| `wlan` | `sceWlan*` | Query WLAN module status |
89168
| `msg-dialog` | `sceUtility*` | System message dialog |
@@ -95,9 +174,19 @@ The `psp` crate provides ~825 syscall bindings covering every major PSP subsyste
95174
| `vfpu-context-switching` | `vfpu!()`, threads | VFPU context save/restore across threads |
96175
| `rust-std-hello-world` | `String`, `Vec`, `std` | Standard library on PSP |
97176
| `kernel-mode` | `module_kernel!()`, NAND, volatile mem | Kernel-mode APIs (requires CFW) |
98-
| `file-io` | `sceIoOpen/Write/Read/Close` | File write and read-back |
177+
| `file-io` | `psp::io` | File write and read-back |
99178
| `screenshot` | `screenshot_bmp()`, `sceIoWrite` | Capture framebuffer to BMP file |
100-
| `audio-tone` | `sceAudioChReserve`, `sceAudioOutputBlocking` | Generate and play a sine wave |
179+
| `audio-tone` | `psp::audio::AudioChannel` | Generate and play a sine wave |
180+
| `config-save` | `psp::config`, `psp::io` | Save and load key-value settings |
181+
| `input-analog` | `psp::input`, `psp::display` | Controller input with analog deadzone |
182+
| `net-http` | `psp::net`, `psp::wlan` | Low-level raw TCP HTTP request |
183+
| `http-client` | `psp::http`, `psp::net` | High-level HTTP GET with HttpClient |
184+
| `savedata` | `psp::savedata`, `sceGu*` | Save and load game data via system dialog |
185+
| `osk-input` | `psp::osk`, `sceGu*` | On-screen keyboard text input |
186+
| `rtc-sysinfo` | `psp::rtc`, `psp::system_param` | RTC date/time and system settings |
187+
| `system-font` | `psp::font`, `psp::gu_ext` | Render text using PSP system fonts |
188+
| `thread-sync` | `psp::thread`, `psp::sync` | Spawn threads sharing a SpinMutex counter |
189+
| `timer-alarm` | `psp::timer` | One-shot alarm and virtual timer |
101190

102191
## Kernel Mode
103192

@@ -115,7 +204,7 @@ psp = { git = "https://github.com/AndrewAltimit/rust-psp", features = ["kernel"]
115204
psp::module_kernel!("MyKernelApp", 1, 0);
116205

117206
fn psp_main() {
118-
psp::enable_home_button();
207+
psp::callback::setup_exit_callback().unwrap();
119208
unsafe {
120209
let me_freq = psp::sys::scePowerGetMeClockFrequency();
121210
psp::dprintln!("ME clock: {}MHz", me_freq);
@@ -158,7 +247,7 @@ This fork adds experimental `std` support for PSP, allowing use of `String`, `Ve
158247
psp::module!("rust_std_hello_world", 1, 1);
159248

160249
fn psp_main() {
161-
psp::enable_home_button();
250+
psp::callback::setup_exit_callback().unwrap();
162251

163252
let greeting = String::from("Hello from std!");
164253
psp::dprintln!("{}", greeting);
@@ -234,7 +323,7 @@ In your `main.rs` file, set up a basic skeleton:
234323
psp::module!("sample_module", 1, 0);
235324

236325
fn psp_main() {
237-
psp::enable_home_button();
326+
psp::callback::setup_exit_callback().unwrap();
238327
psp::dprintln!("Hello PSP from rust!");
239328
}
240329
```
@@ -441,7 +530,7 @@ Tagging a commit with `v*` (e.g., `v0.1.0`) triggers a release build:
441530

442531
```
443532
rust-psp/
444-
+-- psp/ # Core PSP crate (sceGu, sceCtrl, sys bindings, vram_alloc)
533+
+-- psp/ # Core PSP crate (~825 syscall bindings + 30 SDK modules)
445534
+-- cargo-psp/ # Build tool: cross-compile + prxgen + pack-pbp -> EBOOT.PBP
446535
+-- rust-std-src/ # PSP PAL overlay for std support (merged with rust-src at build time)
447536
+-- examples/ # Sample programs (hello-world, cube, gu-background, etc.)

cargo-psp/src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,9 +529,15 @@ fn main() -> Result<()> {
529529
.arg(build_std_flag)
530530
.arg("--target")
531531
.arg(&target_arg)
532-
.arg("--message-format=json-render-diagnostics")
533-
.args(args)
534-
.stdout(Stdio::piped());
532+
.arg("--message-format=json-render-diagnostics");
533+
534+
// Newer nightlies (post Jan 2026) destabilized custom JSON target specs
535+
// and require -Zjson-target-spec when using a .json target path.
536+
if build_std {
537+
build_cmd.arg("-Z").arg("json-target-spec");
538+
}
539+
540+
build_cmd.args(args).stdout(Stdio::piped());
535541

536542
if build_std {
537543
// __CARGO_TESTS_ONLY_SRC_ROOT must point to the workspace root

0 commit comments

Comments
 (0)