1use core::fmt;
10use core::ops::Range;
11use std::io::Read;
12use bitflags::bitflags;
13
14use spectrusty_core::z80emu::{*, z80::*};
15use spectrusty_core::clock::{VideoTs, FTs};
16use spectrusty_core::chip::{
17 ControlUnit, MemoryAccess, ReadEarMode,
18 Ula128MemFlags, Ula3CtrlFlags, ScldCtrlFlags
19};
20use spectrusty_core::video::{Video, BorderColor};
21use spectrusty_core::memory::{ZxMemory, ZxMemoryError};
22use spectrusty_peripherals::ay::AyRegister;
23
24#[non_exhaustive]
25#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)]
26pub enum ComputerModel {
27 Spectrum16,
28 Spectrum48,
29 SpectrumNTSC,
30 Spectrum128,
31 SpectrumPlus2,
32 SpectrumPlus2A,
33 SpectrumPlus3,
34 SpectrumPlus3e,
35 SpectrumSE,
36 TimexTC2048,
37 TimexTC2068,
38 TimexTS2068,
39}
40
41bitflags! {
42 #[derive(Default)]
43 pub struct Extensions: u64 {
44 const NONE = 0x0000_0000_0000_0000;
45 const IF1 = 0x0000_0000_0000_0001;
46 const PLUS_D = 0x0000_0000_0000_0002;
47 const DISCIPLE = 0x0000_0000_0000_0004;
48 const SAM_RAM = 0x0000_0000_0000_0008;
49 const ULA_PLUS = 0x0000_0000_0000_0010;
50 const TR_DOS = 0x0000_0000_0000_0020;
51 const RESERVED = 0xFFFF_FFFF_FFFF_FFC0;
52 }
53}
54
55#[non_exhaustive]
56#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)]
57pub enum JoystickModel {
58 Kempston,
59 Sinclair1,
60 Sinclair2,
61 Cursor,
62 Fuller,
63}
64
65#[non_exhaustive]
66#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)]
67pub enum Ay3_891xDevice {
68 Ay128k,
70 Melodik,
72 FullerBox,
74 Timex,
76}
77
78#[non_exhaustive]
79#[derive(Debug,Clone,PartialEq,Eq)]
80pub enum CpuModel {
81 NMOS(Z80NMOS),
82 CMOS(Z80CMOS),
83 BM1(Z80BM1),
84}
85
86impl<F: Flavour> From<CpuModel> for Z80<F>
87 where F: From<NMOS> + From<CMOS> + From<BM1>
88{
89 fn from(cpu: CpuModel) -> Self {
90 match cpu {
91 CpuModel::NMOS(z80) => z80.into_flavour(),
92 CpuModel::CMOS(z80) => z80.into_flavour(),
93 CpuModel::BM1(z80) => z80.into_flavour(),
94 }
95 }
96}
97
98impl From<CpuModel> for Z80Any {
99 fn from(cpu: CpuModel) -> Self {
100 match cpu {
101 CpuModel::NMOS(z80) => Z80Any::NMOS(z80),
102 CpuModel::CMOS(z80) => Z80Any::CMOS(z80),
103 CpuModel::BM1(z80) => Z80Any::BM1(z80),
104 }
105 }
106}
107
108impl From<Z80Any> for CpuModel {
109 fn from(cpu: Z80Any) -> Self {
110 match cpu {
111 Z80Any::NMOS(z80) => CpuModel::NMOS(z80),
112 Z80Any::CMOS(z80) => CpuModel::CMOS(z80),
113 Z80Any::BM1(z80) => CpuModel::BM1(z80),
114 }
115 }
116}
117
118#[non_exhaustive]
120#[derive(Debug,Clone,PartialEq,Eq,Hash)]
121pub enum MemoryRange {
122 Rom(Range<usize>),
125 Ram(Range<usize>),
128 Interface1Rom,
130 PlusDRom,
132 DiscipleRom,
134 MultifaceRom,
136 SamRamRom(Range<usize>),
139}
140
141pub trait SnapshotCreator {
143 fn model(&self) -> ComputerModel;
144 fn extensions(&self) -> Extensions;
145 fn cpu(&self) -> CpuModel;
146 fn current_clock(&self) -> FTs;
147 fn border_color(&self) -> BorderColor;
148 fn issue(&self) -> ReadEarMode;
149 fn memory_ref(&self, range: MemoryRange) -> Result<&[u8], ZxMemoryError>;
150 fn joystick(&self) -> Option<JoystickModel> { None }
151 fn ay_state(&self, _choice: Ay3_891xDevice) -> Option<(AyRegister, &[u8;16])> { None }
152 fn ula128_flags(&self) -> Ula128MemFlags { unimplemented!() }
153 fn ula3_flags(&self) -> Ula3CtrlFlags { unimplemented!() }
154 fn timex_flags(&self) -> ScldCtrlFlags { unimplemented!() }
155 fn timex_memory_banks(&self) -> u8 { unimplemented!() }
156 fn is_interface1_rom_paged_in(&self) -> bool { unimplemented!() }
158 fn is_plus_d_rom_paged_in(&self) -> bool { unimplemented!() }
159 fn is_disciple_rom_paged_in(&self) -> bool { unimplemented!() }
160 fn is_tr_dos_rom_paged_in(&self) -> bool { unimplemented!() }
161}
162
163bitflags! {
164 #[derive(Default)]
165 pub struct SnapshotResult: u64 {
166 const OK = 0x0000_0000_0000_0000;
167 const MODEL_NSUP = 0x0000_0000_0000_0001;
168 const EXTENSTION_NSUP = 0x0000_0000_0000_0010;
169 const CPU_MODEL_NSUP = 0x0000_0000_0000_0100;
170 const JOYSTICK_NSUP = 0x0000_0000_0000_1000;
171 const SOUND_CHIP_NSUP = 0x0000_0000_0001_0000;
172 const KEYB_ISSUE_NSUP = 0x0000_0000_0010_0000;
173 }
174}
175
176pub trait SnapshotLoader {
183 type Error: Into<Box<(dyn std::error::Error + Send + Sync + 'static)>>;
185 fn select_model(
191 &mut self,
192 model: ComputerModel,
193 extensions: Extensions,
194 border: BorderColor,
195 issue: ReadEarMode,
196 ) -> Result<(), Self::Error>;
197 fn read_into_memory<R: Read>(&mut self, range: MemoryRange, reader: R) -> Result<(), ZxMemoryError>;
201 fn assign_cpu(&mut self, cpu: CpuModel);
205 fn set_clock(&mut self, tstates: FTs);
209 fn write_port(&mut self, port: u16, data: u8);
213 fn select_joystick(&mut self, _joystick: JoystickModel) {}
217 fn setup_ay(&mut self, _choice: Ay3_891xDevice, _reg_selected: AyRegister, _reg_values: &[u8;16]) {}
225 fn interface1_rom_paged_in(&mut self) { unimplemented!() }
233 fn plus_d_rom_paged_in(&mut self) { unimplemented!() }
241 fn tr_dos_rom_paged_in(&mut self) { unimplemented!() }
249}
250
251pub fn is_cpu_safe_for_snapshot<C: Cpu>(cpu: &C) -> bool {
253 !cpu.is_after_prefix()
254}
255
256pub fn ensure_cpu_is_safe_for_snapshot<C: Cpu, M: ControlUnit + Video + MemoryAccess>(
267 cpu: &mut C,
268 chip: &mut M
269 )
270{
271 loop {
272 if cpu.is_halt() {
273 return
274 }
275 else if cpu.is_after_prefix() {
276 match chip.memory_ref().read(cpu.get_pc()) {
277 0xFD|0xDD => return,
278 _ => {}
279 }
280 }
281 else if cpu.is_after_ei() {
282 let VideoTs { vc, hc } = chip.current_video_ts();
283 if vc != 0 || !(-1..=31).contains(&hc) {
284 return
285 }
286 }
287 else {
288 return
289 }
290 let _ = chip.execute_single_step::<_,CpuDebugFn>(cpu, None);
291 }
292}
293
294impl ComputerModel {
295 pub fn frame_tstates(self) -> FTs {
297 use ComputerModel::*;
298 match self {
299 SpectrumNTSC => {
300 59136
301 }
302 Spectrum16|Spectrum48|
303 TimexTC2048|TimexTC2068|TimexTS2068 => {
304 69888
305 }
306 Spectrum128|SpectrumPlus2|
307 SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
308 SpectrumSE=> {
309 70908
310 }
311 }
312 }
313 pub fn applicable_issue(self, issue: ReadEarMode) -> ReadEarMode {
315 use ComputerModel::*;
316 match self {
317 Spectrum16|Spectrum48|SpectrumNTSC|
318 Spectrum128|SpectrumPlus2
319 => issue,
320 _ => ReadEarMode::Clear
321 }
322 }
323
324 pub fn validate_extensions(self, ext: Extensions) -> Result<(), Extensions> {
325 use ComputerModel::*;
326 match self {
327 Spectrum128|SpectrumPlus2|SpectrumSE if ext.intersects(Extensions::SAM_RAM) => Err(Extensions::SAM_RAM),
328 SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e
329 if ext.intersects(Extensions::SAM_RAM|Extensions::IF1|Extensions::PLUS_D|Extensions::DISCIPLE) => {
330 Err(ext & (Extensions::SAM_RAM|Extensions::IF1|Extensions::PLUS_D|Extensions::DISCIPLE))
331 }
332 TimexTC2068|TimexTS2068 if ext.intersects(Extensions::SAM_RAM) => Err(Extensions::SAM_RAM),
333 _ => Ok(())
334 }
335 }
336}
337
338impl From<ComputerModel> for &str {
339 fn from(model: ComputerModel) -> Self {
340 use ComputerModel::*;
341 match model {
342 Spectrum16 => "ZX Spectrum 16k",
343 Spectrum48 => "ZX Spectrum 48k",
344 SpectrumNTSC => "ZX Spectrum NTSC",
345 Spectrum128 => "ZX Spectrum 128k",
346 SpectrumPlus2 => "ZX Spectrum +2",
347 SpectrumPlus2A => "ZX Spectrum +2A",
348 SpectrumPlus3 => "ZX Spectrum +3",
349 SpectrumPlus3e => "ZX Spectrum +3e",
350 SpectrumSE => "ZX Spectrum SE",
351 TimexTC2048 => "Timex TC2048",
352 TimexTC2068 => "Timex TC2068",
353 TimexTS2068 => "Timex TS2068",
354 }
355 }
356}
357
358impl From<&'_ ComputerModel> for &'static str {
359 fn from(model: &ComputerModel) -> Self {
360 (*model).into()
361 }
362}
363
364impl fmt::Display for ComputerModel {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 <&str>::from(self).fmt(f)
367 }
368}
369
370impl fmt::Display for Extensions {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 if self.intersects(Extensions::IF1) {
373 f.write_str(" + IF1")?;
374 }
375 if self.intersects(Extensions::ULA_PLUS) {
376 f.write_str(" + ULAPlus")?;
377 }
378 if self.intersects(Extensions::PLUS_D) {
379 f.write_str(" + MGT+D")?;
380 }
381 if self.intersects(Extensions::DISCIPLE) {
382 f.write_str(" + DISCiPLE")?;
383 }
384 if self.intersects(Extensions::SAM_RAM) {
385 f.write_str(" + SamRam")?;
386 }
387 Ok(())
388 }
389}