Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_io/
disc_base.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2//! Where this program's data sits on the disc it was booted from.
3//!
4//! A program that owns its disc knows exactly where everything is: pack LBAs
5//! are cooked in, CD-DA tracks are numbered from 2. Put several programs on
6//! one disc and those absolutes stop being absolute. Rather than re-cook every
7//! program for every disc it might ship on, a multi-program loader hands over
8//! two numbers at boot and the SDK's disc entry points apply them.
9//!
10//! Both default to zero, so a program booted the ordinary way -- BIOS reads
11//! `SYSTEM.CNF`, runs the one EXE -- behaves exactly as it did before this
12//! module existed. Nothing needs a separate build to work on both.
13//!
14//! The handover is by register, not by an agreed memory address: there is no
15//! address the SDK can prove a game will leave alone (hl-psx already uses the
16//! scratchpad, and a headerless PSX-EXE gives no way to bound `.bss`).
17//! [`psx-rt`'s `_start`](../../psx_rt/fn._start.html) takes the loader's
18//! arguments and calls [`install`] before anything else runs.
19
20/// Marks `$a0` as a loader handover rather than boot-time register garbage.
21/// ASCII `PSXD`.
22pub const HANDOFF_MAGIC: u32 = 0x5053_5844;
23
24// Forced into `.data` rather than left in `.bss`. A chain-loader blob is
25// linked as a bare `.text` image with no `.bss` to zero, but it still reads
26// the disc through this SDK; leaving these in `.bss` would have it consult
27// uninitialised memory. Guest-only: the host builds these crates too, and
28// mach-o spells section names differently.
29#[cfg_attr(target_arch = "mips", link_section = ".data")]
30static mut LBA_OFFSET: u32 = 0;
31#[cfg_attr(target_arch = "mips", link_section = ".data")]
32static mut CDDA_TRACK_BASE: u8 = 0;
33
34/// Record the loader's handover, if `magic` says there is one.
35///
36/// A normal BIOS boot leaves whatever it likes in the argument registers, so
37/// the magic is what separates "chain-loaded, here are your bases" from
38/// "nobody told us anything". Calling this with a wrong magic is a no-op.
39///
40/// # Safety
41/// Must be called once, before any other thread of control could observe the
42/// bases -- in practice the first thing `_start` does after zeroing `.bss`.
43pub unsafe fn install(magic: u32, lba_offset: u32, cdda_track_base: u32) {
44    if magic != HANDOFF_MAGIC {
45        return;
46    }
47    // SAFETY: per this fn's `# Safety`, it runs once at boot before anything can read the bases, so
48    // these plain stores race nothing and no reference to either static is live.
49    unsafe {
50        LBA_OFFSET = lba_offset;
51        CDDA_TRACK_BASE = cdda_track_base as u8;
52    }
53}
54
55/// Sectors between the disc's LBA 0 and this program's.
56#[inline(always)]
57pub fn lba_offset() -> u32 {
58    // SAFETY: written once by `install` before anything else runs; read-only
59    // afterwards, and the guest is single-threaded.
60    unsafe { LBA_OFFSET }
61}
62
63/// CD-DA tracks belonging to programs ahead of this one on the disc.
64#[inline(always)]
65pub fn cdda_track_base() -> u8 {
66    // SAFETY: as `lba_offset`.
67    unsafe { CDDA_TRACK_BASE }
68}
69
70/// Translate a disc-relative LBA into a physical one.
71#[inline(always)]
72pub fn shift_lba(lba: u32) -> u32 {
73    lba.wrapping_add(lba_offset())
74}
75
76/// Translate a program-relative CD-DA track number into a physical one.
77#[inline(always)]
78pub fn shift_track(track: u8) -> u8 {
79    track.wrapping_add(cdda_track_base())
80}