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}