Skip to main content

systemless/
machine_profile.rs

1//! Canonical guest machine profile used by Systemless's accuracy harness.
2//!
3//! Frozen reference values for "what does the guest see when it asks
4//! about the host machine" — Gestalt selectors, screen geometry, RAM
5//! size, VBL rate, etc. There's exactly one shipped profile
6//! ([`BASILISK_II_PLAY_PROFILE`]); a const alias [`REFERENCE_MACHINE_PROFILE`]
7//! exposes it under the role-name the trap dispatcher uses.
8//!
9//! Library consumers don't normally need to read this directly — the
10//! Memory Manager, Gestalt, and screen-mode init paths in
11//! [`crate::trap`] consult it on the consumer's behalf.
12
13use m68k::CpuType;
14
15/// Bag of constants describing one canonical guest machine: Gestalt
16/// selector responses, screen geometry, RAM size, VBL rate, realtime
17/// CPU MHz target.
18///
19/// Field accessors are field-name-direct (`profile.screen_width`)
20/// since downstream callers compare specific values rather than
21/// treating the profile as opaque.
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct MachineProfile {
24    /// Mac model ID — what `gestaltMachineType` returns.
25    pub model_id: i16,
26    /// Gestalt 'mach' selector response.
27    pub gestalt_machine_type: u16,
28    /// System version in BCD (e.g. 0x0753 = System 7.5.3).
29    pub system_version_bcd: u16,
30    /// Gestalt 'cput' selector — `gestaltCPU68040 = 4` per
31    /// IM:Operating System Utilities 1994.
32    pub gestalt_native_cpu_type: u32,
33    /// Gestalt 'proc' selector — `gestalt68040 = 5` (legacy numbering;
34    /// not the same scheme as `gestalt_native_cpu_type`).
35    pub gestalt_processor_type: u32,
36    /// Gestalt 'fpu ' selector — non-zero implies FPU presence.
37    pub gestalt_fpu_type: u32,
38    /// Gestalt 'mmu ' selector — MMU type.
39    pub gestalt_mmu_type: u32,
40    /// Size of the guest RAM region in bytes. Must accommodate the
41    /// largest resource fork the profile's games unpack into the heap
42    /// (Bonkheads Deluxe peaks ~30 MB during merge — see comment on
43    /// [`BASILISK_II_PLAY_PROFILE`]).
44    pub ram_size_bytes: u32,
45    /// Framebuffer width in pixels.
46    pub screen_width: u16,
47    /// Framebuffer height in pixels.
48    pub screen_height: u16,
49    /// Framebuffer depth in bits per pixel (typically 8 for indexed
50    /// 8bpp screens with the standard Mac CLUT).
51    pub screen_depth: u16,
52    /// VBL interrupt rate in Hz. 60.15 matches Compact Mac timing.
53    pub vbl_hz: f64,
54    /// Target instruction throughput for realtime frontends, in
55    /// MHz × 1,000,000 instructions/sec equivalent. Used by
56    /// `systemless`'s wall-clock pacing — non-realtime callers
57    /// (scripted harnesses, tests) ignore this.
58    pub realtime_cpu_mhz: f64,
59}
60
61impl MachineProfile {
62    /// Concrete `m68k::CpuType` corresponding to this profile.
63    /// Hardcoded to `M68040` for now — the only shipped profile is
64    /// the Basilisk-II play machine, which is a Quadra 900 (68040).
65    pub fn cpu_type(self) -> CpuType {
66        CpuType::M68040
67    }
68
69    /// True when the guest exposes an FPU via Gestalt
70    /// (`gestalt_fpu_type != 0`). Const-eval friendly so
71    /// trap-table builders can branch on it at compile time.
72    pub const fn has_fpu(self) -> bool {
73        self.gestalt_fpu_type != 0
74    }
75
76    /// Bytes per scanline for this profile's indexed screen. `rowBytes` is an
77    /// offset and may include storage beyond the visible pixels; matching the
78    /// offscreen 16-byte quantum keeps direct full-row transfers coherent.
79    /// Imaging With QuickDraw 1994, p. 4-5
80    pub const fn screen_row_bytes(self) -> u32 {
81        let bytes = (self.screen_width as u32 * self.screen_depth as u32).div_ceil(8);
82        (bytes / 16 + 1) * 16
83    }
84}
85
86/// Basilisk maps model ID 14 to a Quadra 900 / Gestalt machine type 20.
87/// `gestalt_native_cpu_type = 4` per IM:Operating System Utilities 1994
88/// (line 1439, line 2299): `gestaltCPU68040 = $004` under the
89/// `gestaltNativeCPUtype` ('cput') selector — value 5 there is
90/// `gestaltCPU68LC040`, which contradicts this profile's 68040 FPU.
91/// `gestalt_processor_type = 5` because the legacy `gestaltProcessorType`
92/// ('proc') selector uses its own numbering where `gestalt68040 = 5`
93/// (IM:OSU line 1470).
94pub const BASILISK_II_PLAY_PROFILE: MachineProfile = MachineProfile {
95    model_id: 14,
96    gestalt_machine_type: 20,
97    // Mac OS 8.1 is the last release supported on 68040 Macs and provides
98    // the late-classic Toolbox surface exposed by this HLE profile.
99    system_version_bcd: 0x0810,
100    gestalt_native_cpu_type: 4,
101    gestalt_processor_type: 5,
102    gestalt_fpu_type: 3,
103    gestalt_mmu_type: 4,
104    // Bonkheads_Deluxe peaks above 30 MB during resource-fork merge (it
105    // bundles ~669 resources, several individual chunks > 250 KB), which
106    // exhausts the 32 MB-default heap before the title even renders.
107    // 64 MB matches what real-world Power Macintosh users would have
108    // configured for that era of game and clears the OOM without
109    // cascading failures elsewhere.
110    ram_size_bytes: 64 * 1024 * 1024,
111    screen_width: 800,
112    screen_height: 600,
113    screen_depth: 8,
114    vbl_hz: 60.15,
115    realtime_cpu_mhz: 25.0,
116};
117
118pub const REFERENCE_MACHINE_PROFILE: MachineProfile = BASILISK_II_PLAY_PROFILE;
119
120/// Returns the reference machine profile, optionally overridden by
121/// `SYSTEMLESS_SCREEN_WIDTH` and `SYSTEMLESS_SCREEN_HEIGHT` environment variables.
122pub fn reference_machine_profile() -> MachineProfile {
123    let mut p = REFERENCE_MACHINE_PROFILE;
124    if let Ok(w) = std::env::var("SYSTEMLESS_SCREEN_WIDTH") {
125        if let Ok(w) = w.parse::<u16>() {
126            p.screen_width = w;
127        }
128    }
129    if let Ok(h) = std::env::var("SYSTEMLESS_SCREEN_HEIGHT") {
130        if let Ok(h) = h.parse::<u16>() {
131            p.screen_height = h;
132        }
133    }
134    p
135}