1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3mod cdrom;
4mod controller_mem_card;
5mod coprocessor;
6pub mod cpu;
7pub mod gpu;
8mod mdec;
9mod memory;
10mod spu;
11mod timers;
12
13#[cfg(test)]
14mod tests;
15
16use std::{
17 path::{Path, PathBuf},
18 sync::Arc,
19};
20
21use cpu::RegisterType;
22use memory::{Bios, BusLine, CpuBus, Result};
23
24pub use controller_mem_card::DigitalControllerKey;
25
26use crate::gpu::{Device, GpuFuture, Image, Queue};
27
28const MAX_CPU_CYCLES_TO_CLOCK: u32 = 2000;
29
30#[derive(Debug)]
31pub enum PsxError {
32 CouldNotLoadBios,
33 CouldNotLoadDisk(String),
34 DiskTypeNotSupported,
35}
36
37impl std::error::Error for PsxError {}
38impl std::fmt::Display for PsxError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 PsxError::CouldNotLoadBios => write!(f, "Could not load BIOS"),
42 PsxError::CouldNotLoadDisk(s) => write!(f, "Could not load disk: {}", s),
43 PsxError::DiskTypeNotSupported => write!(f, "Disk type not supported"),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy)]
49pub struct PsxConfig {
50 pub stdout_debug: bool,
51 pub fast_boot: bool,
52}
53
54pub struct Psx {
55 bus: CpuBus,
56 exe_file: Option<PathBuf>,
57 disk_available: bool,
59 config: PsxConfig,
60 cpu: cpu::Cpu,
61 excess_cpu_cycles: u32,
67 cpu_frame_cycles: u32,
68}
69
70impl Psx {
71 pub fn new<BiosPath: AsRef<Path>, DiskPath: AsRef<Path>>(
73 bios_file_path: BiosPath,
74 disk_file: Option<DiskPath>,
75 config: PsxConfig,
76 device: Arc<Device>,
77 queue: Arc<Queue>,
78 ) -> Result<Self, PsxError> {
79 let bios = Bios::from_file(bios_file_path)?;
80
81 let (exe_file, disk_file) = if let Some(disk_file) = disk_file {
84 let path = disk_file.as_ref().to_owned();
85 match path
87 .extension()
88 .unwrap()
89 .to_str()
90 .unwrap()
91 .to_ascii_lowercase()
92 .as_str()
93 {
94 "exe" => (Some(path), None),
95 "cue" => (None, Some(path)),
96 _ => {
97 return Err(PsxError::DiskTypeNotSupported);
98 }
99 }
100 } else {
101 (None, None)
103 };
104
105 Ok(Self {
106 cpu: cpu::Cpu::new(),
107 disk_available: disk_file.is_some(),
108 bus: CpuBus::new(bios, disk_file, config, device, queue)?,
109 exe_file,
110 config,
111 excess_cpu_cycles: 0,
112 cpu_frame_cycles: 0,
113 })
114 }
115
116 pub fn reset(&mut self) {
117 self.cpu.reset();
118 self.bus.reset();
119 }
120
121 #[inline(always)]
122 fn common_clock(&mut self) -> (u32, cpu::CpuState) {
123 let mut cpu_state = cpu::CpuState::Normal;
124 let mut added_clock = 0;
125 if self.excess_cpu_cycles == 0 {
126 let cpu_cycles;
129 let shell_reached;
130
131 (cpu_cycles, cpu_state) = self.cpu.clock(&mut self.bus, 56);
132 shell_reached = self.cpu.is_shell_reached();
133
134 if shell_reached && (self.config.fast_boot || self.exe_file.is_some()) {
136 if let Some(exe_file) = &self.exe_file {
137 let (pc, gp, sp_fp) = self.bus.load_exe_in_memory(exe_file);
138
139 let regs = self.cpu.registers_mut();
140 println!(
141 "Loaded EXE {} into pc: {:08x}. gp: {:08x}, sp_fp: {:08x}",
142 exe_file.display(),
143 pc,
144 gp,
145 sp_fp
146 );
147
148 assert_ne!(pc, 0, "PC value cannot be zero");
149 regs.write(RegisterType::Pc, pc);
150
151 if gp != 0 {
152 regs.write(RegisterType::Gp, gp);
153 }
154 if sp_fp != 0 {
155 regs.write(RegisterType::Sp, sp_fp);
156 regs.write(RegisterType::Fp, sp_fp);
157 }
158 } else if self.disk_available {
159 let regs = self.cpu.registers_mut();
161 regs.write(RegisterType::Pc, regs.read(RegisterType::Ra));
163 }
164 }
165
166 if cpu_cycles == 0 {
167 return (0, cpu_state);
168 }
169 self.excess_cpu_cycles = cpu_cycles + self.bus.clock_dma();
171 added_clock = self.excess_cpu_cycles;
172 }
173
174 let cpu_cycles_to_run = self.excess_cpu_cycles.min(MAX_CPU_CYCLES_TO_CLOCK);
175 self.excess_cpu_cycles -= cpu_cycles_to_run;
176 self.bus.clock_components(cpu_cycles_to_run);
177
178 (added_clock, cpu_state)
179 }
180
181 pub fn clock_based_on_audio(&mut self, max_clocks: u32) -> (bool, cpu::CpuState) {
184 const CYCLES_PER_FRAME: u32 = 564480;
186
187 let mut clocks = 0;
188
189 while self.cpu_frame_cycles < CYCLES_PER_FRAME {
190 let (added_clock, cpu_state) = self.common_clock();
191 clocks += added_clock;
192 self.cpu_frame_cycles += added_clock;
193
194 if clocks >= max_clocks || cpu_state != cpu::CpuState::Normal {
195 return (false, cpu_state);
196 }
197 }
198 self.cpu_frame_cycles -= CYCLES_PER_FRAME;
199
200 (true, cpu::CpuState::Normal)
201 }
202
203 pub fn clock_based_on_video(&mut self, max_clocks: u32) -> (bool, cpu::CpuState) {
206 let mut prev_vblank = self.bus.gpu().in_vblank();
207 let mut current_vblank = prev_vblank;
208
209 let mut clocks = 0;
210
211 while !current_vblank || prev_vblank {
212 let (added_clock, cpu_state) = self.common_clock();
213 if cpu_state != cpu::CpuState::Normal {
214 return (false, cpu_state);
215 } else {
216 clocks += added_clock;
217 if clocks >= max_clocks {
218 return (false, cpu_state);
219 }
220 }
221
222 prev_vblank = current_vblank;
223 current_vblank = self.bus.gpu().in_vblank();
224 }
225
226 (true, cpu::CpuState::Normal)
227 }
228
229 pub fn clock_full_audio_frame(&mut self) -> cpu::CpuState {
230 const CYCLES_PER_FRAME: u32 = 564480;
232
233 let mut clocks = 0;
234 while clocks < CYCLES_PER_FRAME {
235 let (added_clock, cpu_state) = self.common_clock();
236 clocks += added_clock;
237 if cpu_state != cpu::CpuState::Normal {
238 return cpu_state;
239 }
240 }
241
242 cpu::CpuState::Normal
243 }
244
245 pub fn clock_full_video_frame(&mut self) -> cpu::CpuState {
246 let mut prev_vblank = self.bus.gpu().in_vblank();
247 let mut current_vblank = prev_vblank;
248
249 while !current_vblank || prev_vblank {
250 let cpu_state = self.common_clock().1;
251 if cpu_state != cpu::CpuState::Normal {
252 return cpu_state;
253 }
254
255 prev_vblank = current_vblank;
256 current_vblank = self.bus.gpu().in_vblank();
257 }
258
259 cpu::CpuState::Normal
260 }
261
262 pub fn change_controller_key_state(&mut self, key: DigitalControllerKey, pressed: bool) {
263 self.bus
264 .controller_mem_card_mut()
265 .change_controller_key_state(key, pressed);
266 }
267
268 pub fn change_cdrom_shell_open_state(&mut self, open: bool) {
269 self.bus.cdrom_mut().change_cdrom_shell_open_state(open);
270 }
271
272 pub fn blit_to_front(
273 &mut self,
274 dest_image: Arc<Image>,
275 full_vram: bool,
276 in_future: Box<dyn GpuFuture>,
277 ) -> Box<dyn GpuFuture> {
278 self.bus
279 .gpu_mut()
280 .sync_gpu_and_blit_to_front(dest_image, full_vram, in_future)
281 }
282
283 pub fn take_audio_buffer(&mut self) -> Vec<f32> {
284 self.bus.spu_mut().take_audio_buffer()
285 }
286
287 pub fn cpu(&mut self) -> &mut cpu::Cpu {
288 &mut self.cpu
289 }
290
291 pub fn bus_read_u32(&mut self, addr: u32) -> Result<u32> {
292 if addr % 4 != 0 {
294 return Err("Unaligned memory access".to_string());
295 }
296 self.bus.read_u32(addr)
297 }
298
299 pub fn bus_read_u16(&mut self, addr: u32) -> Result<u16> {
300 if addr % 2 != 0 {
302 return Err("Unaligned memory access".to_string());
303 }
304
305 self.bus.read_u16(addr)
306 }
307
308 pub fn bus_read_u8(&mut self, addr: u32) -> Result<u8> {
309 self.bus.read_u8(addr)
310 }
311
312 pub fn print_spu_state(&self) {
313 self.bus.spu().print_state();
314 }
315}