1use core::mem::size_of;
48use std::convert::TryInto;
49use std::io::{self, ErrorKind, Error, Read, Write, Seek, SeekFrom, Result};
50
51use spectrusty_core::{
52 chip::{Ula128MemFlags, ReadEarMode},
53 memory::ZxMemory,
54 video::BorderColor,
55 z80emu::{Cpu, Prefix, StkReg16, CpuFlags, InterruptMode, Z80NMOS}
56};
57
58use crate::{StructRead, StructWrite};
59use super::snapshot::*;
60
61#[derive(Clone, Copy, Debug, Default)]
62#[repr(C)]
63#[repr(packed)]
64struct SnaHeader {
65 i: u8,
66 hl_alt: [u8;2],
67 de_alt: [u8;2],
68 bc_alt: [u8;2],
69 f_alt: u8,
70 a_alt: u8,
71 hl: [u8;2],
72 de: [u8;2],
73 bc: [u8;2],
74 iy: [u8;2],
75 ix: [u8;2],
76 iffs: u8,
77 r: u8,
78 f: u8,
79 a: u8,
80 sp: [u8;2],
81 im: u8,
82 border: u8
83}
84
85#[derive(Clone, Copy, Debug, Default)]
86#[repr(C)]
87#[repr(packed)]
88struct SnaHeader128 {
89 pc: [u8;2],
90 port_data: u8,
91 trdos_rom: u8
92}
93
94unsafe impl StructRead for SnaHeader {}
96unsafe impl StructRead for SnaHeader128 {}
97unsafe impl StructWrite for SnaHeader {}
98unsafe impl StructWrite for SnaHeader128 {}
99
100pub const SNA_LENGTH: u64 = 49179;
102
103const PAGE_SIZE: usize = 0x4000;
104
105fn read_header<R: Read, C: Cpu>(rd: R, cpu: &mut C) -> Result<BorderColor> {
106 let sna = SnaHeader::read_new_struct(rd)?;
107 cpu.reset();
108 cpu.set_i(sna.i);
109 cpu.set_reg16(StkReg16::HL, u16::from_le_bytes(sna.hl_alt));
110 cpu.set_reg16(StkReg16::DE, u16::from_le_bytes(sna.de_alt));
111 cpu.set_reg16(StkReg16::BC, u16::from_le_bytes(sna.bc_alt));
112 cpu.exx();
113 cpu.set_acc(sna.a_alt);
114 cpu.set_flags(CpuFlags::from_bits_truncate(sna.f_alt));
115 cpu.ex_af_af();
116 cpu.set_reg16(StkReg16::HL, u16::from_le_bytes(sna.hl));
117 cpu.set_reg16(StkReg16::DE, u16::from_le_bytes(sna.de));
118 cpu.set_reg16(StkReg16::BC, u16::from_le_bytes(sna.bc));
119 cpu.set_index16(Prefix::Yfd, u16::from_le_bytes(sna.iy));
120 cpu.set_index16(Prefix::Xdd, u16::from_le_bytes(sna.ix));
121 let iff = sna.iffs & (1<<2) != 0;
122 cpu.set_iffs(iff, iff);
123 cpu.set_r(sna.r);
124 cpu.set_acc(sna.a);
125 cpu.set_flags(CpuFlags::from_bits_truncate(sna.f));
126 cpu.set_sp(u16::from_le_bytes(sna.sp));
127 cpu.set_im(sna.im.try_into().map_err(|_| {
128 Error::new(ErrorKind::InvalidData, "Not a proper SNA block: invalid interrupt mode")
129 })?);
130 sna.border.try_into().map_err(|e| Error::new(ErrorKind::InvalidData, e))
131}
132
133pub fn read_sna48<R: Read, M: ZxMemory, C: Cpu>(
143 mut rd: R,
144 cpu: &mut C,
145 mem: &mut M
146 ) -> Result<BorderColor>
147{
148 let border = read_header(rd.by_ref(), cpu)?;
149 let sp = cpu.get_sp();
150 cpu.set_sp(sp.wrapping_add(2));
151 cpu.inc_r();
152 cpu.inc_r(); mem.load_into_mem(0x4000..=0xFFFF, rd).map_err(|_| {
154 Error::new(ErrorKind::InvalidData, "SNA: needs at least 48k RAM memory")
155 })?;
156 cpu.set_pc(mem.read16(sp));
157 Ok(border)
158}
159
160pub fn load_sna<R: Read + Seek, S: SnapshotLoader>(
169 mut rd: R,
170 loader: &mut S
171 ) -> Result<()>
172{
173 let cur_pos = rd.seek(SeekFrom::Current(0))?;
174 let end_pos = rd.seek(SeekFrom::Current(SNA_LENGTH as i64))?;
175 if end_pos - cur_pos != SNA_LENGTH {
176 return Err(Error::new(ErrorKind::InvalidData, "SNA: wrong size of the supplied stream"));
177 }
178
179 let mut sna_ext = SnaHeader128::default();
180 let ext_read = sna_ext.read_struct_or_nothing(rd.by_ref())?;
181
182 rd.seek(SeekFrom::Start(cur_pos))?;
183
184 if !ext_read {
185 return load_sna48(rd, loader)
186 }
187
188 let mut cpu = Z80NMOS::default();
189 let border = read_header(rd.by_ref(), &mut cpu)?;
190 cpu.set_pc(u16::from_le_bytes(sna_ext.pc));
191
192 let extensions = if sna_ext.trdos_rom != 0 {
193 Extensions::TR_DOS
194 }
195 else {
196 Extensions::NONE
197 };
198 let model = ComputerModel::Spectrum128;
199 loader.select_model(model, extensions, border, ReadEarMode::Issue3)
200 .map_err(|e| Error::new(ErrorKind::Other, e))?;
201
202 let index48 = [5, 2];
203 let last_page = Ula128MemFlags::from_bits_truncate(sna_ext.port_data)
204 .last_ram_page_bank();
205 for page in index48.iter().chain(
206 Some(&last_page).filter(|n| !index48.contains(n))
207 ) {
208 loader.read_into_memory(
209 MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE), rd.by_ref()
210 )?;
211 }
212
213 rd.seek(SeekFrom::Current(size_of::<SnaHeader128>() as i64))?;
214
215 for page in (0..8).filter(|n| !index48.contains(n) && *n != last_page) {
216 loader.read_into_memory(
217 MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE), rd.by_ref()
218 )?;
219 }
220 loader.assign_cpu(CpuModel::NMOS(cpu));
221 loader.write_port(0x7ffd, sna_ext.port_data);
222 if sna_ext.trdos_rom == 1 {
223 loader.tr_dos_rom_paged_in();
224 }
225 Ok(())
226}
227
228pub fn load_sna48<R: Read, S: SnapshotLoader>(
237 mut rd: R,
238 loader: &mut S
239 ) -> Result<()>
240{
241 let mut cpu = Z80NMOS::default();
242 let border = read_header(rd.by_ref(), &mut cpu)?;
243 let sp = cpu.get_sp();
244 if sp < 0x4000 || sp == 0xFFFF {
245 return Err(Error::new(ErrorKind::InvalidData, "SNA: can't determine the PC address"))
246 }
247 cpu.set_sp(sp.wrapping_add(2));
248 cpu.inc_r();
249 cpu.inc_r(); let model = ComputerModel::Spectrum48;
252 loader.select_model(model, Default::default(), border, ReadEarMode::Issue3)
253 .map_err(|e| Error::new(ErrorKind::Other, e))?;
254
255 let pc_offset = sp as usize - 0x4000;
256 loader.read_into_memory(MemoryRange::Ram(0..pc_offset), rd.by_ref())?;
257 let mut pc = [0u8;2];
258 rd.read_exact(&mut pc)?;
259 cpu.set_pc(u16::from_le_bytes(pc));
260 let rest_offset = pc_offset + 2;
261 if rest_offset < 0xC000 {
262 loader.read_into_memory(MemoryRange::Ram(rest_offset..0xC000), rd)?;
263 }
264 loader.assign_cpu(CpuModel::NMOS(cpu));
265 Ok(())
266}
267
268fn make_header<C: Cpu>(cpu: &C) -> SnaHeader {
269 let mut sna = SnaHeader {
270 i: cpu.get_i(),
271 hl_alt: cpu.get_alt_reg16(StkReg16::HL).to_le_bytes(),
272 de_alt: cpu.get_alt_reg16(StkReg16::DE).to_le_bytes(),
273 bc_alt: cpu.get_alt_reg16(StkReg16::BC).to_le_bytes(),
274 hl: cpu.get_reg16(StkReg16::HL).to_le_bytes(),
275 de: cpu.get_reg16(StkReg16::DE).to_le_bytes(),
276 bc: cpu.get_reg16(StkReg16::BC).to_le_bytes(),
277 iy: cpu.get_index16(Prefix::Yfd).to_le_bytes(),
278 ix: cpu.get_index16(Prefix::Xdd).to_le_bytes(),
279 r: cpu.get_r(),
280 im: match cpu.get_im() {
281 InterruptMode::Mode0 => 0,
282 InterruptMode::Mode1 => 1,
283 InterruptMode::Mode2 => 2,
284 },
285 sp: cpu.get_sp().to_le_bytes(),
286 ..Default::default()
287 };
288 (sna.a_alt, sna.f_alt) = cpu.get_alt_reg2(StkReg16::AF);
289 (sna.a, sna.f) = cpu.get_reg2(StkReg16::AF);
290 let (iff1, _) = cpu.get_iffs();
291 sna.iffs = (iff1 as u8) << 2;
292 sna
293}
294
295pub fn save_sna<C: SnapshotCreator, W: Write>(
302 snapshot: &C,
303 mut wr: W
304 ) -> Result<SnapshotResult>
305{
306 use ComputerModel::*;
307 let mut result = SnapshotResult::KEYB_ISSUE_NSUP;
308 let model = snapshot.model();
309 let is_128 = match model {
310 Spectrum48 => false,
311 Spectrum128 => true,
312 SpectrumPlus2|SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|SpectrumSE => {
313 result.insert(SnapshotResult::MODEL_NSUP);
314 true
315 }
316 Spectrum16|SpectrumNTSC|TimexTC2048|TimexTC2068|TimexTS2068 => {
317 result.insert(SnapshotResult::MODEL_NSUP);
318 false
319 }
320 };
321
322 let extensions = snapshot.extensions();
323 if extensions.intersects(Extensions::IF1) && snapshot.is_interface1_rom_paged_in()
324 || extensions.intersects(Extensions::PLUS_D) && snapshot.is_plus_d_rom_paged_in()
325 {
326 return Err(Error::new(ErrorKind::InvalidInput,
327 "SNA: can't create a snapshot with the external ROM paged in"))
328 }
329 if extensions != Extensions::NONE && extensions != Extensions::TR_DOS {
330 result.insert(SnapshotResult::EXTENSTION_NSUP);
331 }
332
333 let cpu = match snapshot.cpu() {
334 CpuModel::NMOS(cpu) => cpu,
335 CpuModel::CMOS(cpu) => {
336 result.insert(SnapshotResult::CPU_MODEL_NSUP);
337 cpu.into_flavour()
338 },
339 CpuModel::BM1(cpu) => {
340 result.insert(SnapshotResult::CPU_MODEL_NSUP);
341 cpu.into_flavour()
342 }
343 };
344
345 if !is_cpu_safe_for_snapshot(&cpu) {
346 return Err(Error::new(ErrorKind::InvalidInput, "SNA: can't safely snapshot the CPU state"))
347 }
348
349 let mut sna = make_header(&cpu);
350 sna.border = snapshot.border_color().into();
351
352 if snapshot.joystick().is_some() {
353 result.insert(SnapshotResult::JOYSTICK_NSUP);
354 }
355
356 if is_128 || snapshot.ay_state(Ay3_891xDevice::Melodik).is_some()
357 || snapshot.ay_state(Ay3_891xDevice::FullerBox).is_some()
358 || snapshot.ay_state(Ay3_891xDevice::Timex).is_some() {
359 result.insert(SnapshotResult::SOUND_CHIP_NSUP);
360 }
361
362 if !is_128 {
363 return save_sna48(snapshot, cpu, model == ComputerModel::Spectrum16, sna, wr, result)
364 }
365
366 let memflags = snapshot.ula128_flags();
367 let mut sna_ext = SnaHeader128 {
368 pc: cpu.get_pc().to_le_bytes(),
369 port_data: memflags.bits(),
370 ..Default::default()
371 };
372
373 if extensions.intersects(Extensions::TR_DOS) {
374 sna_ext.trdos_rom = snapshot.is_tr_dos_rom_paged_in().into();
375 }
376
377 sna.write_struct(wr.by_ref())?;
378
379 let last_page: usize = memflags.last_ram_page_bank();
380 let index48 = [5,2,last_page];
381 for page in index48.iter() {
382 wr.write_all(
383 snapshot.memory_ref(MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE))?
384 )?;
385 }
386
387 sna_ext.write_struct(wr.by_ref())?;
388
389 for page in (0..8).filter(|n| !index48.contains(n) && *n != last_page) {
390 wr.write_all(
391 snapshot.memory_ref(MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE))?
392 )?;
393 }
394
395 wr.flush()?;
396 Ok(result)
397}
398
399fn save_sna48<C: SnapshotCreator, W: Write>(
400 snapshot: &C,
401 cpu: Z80NMOS,
402 is_mem16k: bool,
403 mut sna: SnaHeader,
404 mut wr: W,
405 result: SnapshotResult
406 ) -> Result<SnapshotResult>
407{
408 const ROMSIZE: usize = 0x4000;
409 let ramtop = if is_mem16k { 0x7FFF } else { 0xFFFF };
410 let sp = cpu.get_sp().wrapping_sub(2);
411 if (sp as usize) < ROMSIZE || sp >= ramtop {
412 return Err(Error::new(ErrorKind::InvalidData, "SNA: can't store the PC address"))
413 }
414 sna.sp = sp.to_le_bytes();
415 sna.write_struct(wr.by_ref())?;
416 let pc = cpu.get_pc().to_le_bytes();
417 let pc_offset = sp as usize - ROMSIZE;
418 let mem_slice = snapshot.memory_ref(MemoryRange::Ram(0..pc_offset))?;
419 wr.write_all(mem_slice)?;
420 wr.write_all(&pc)?;
421 let mem_slice = snapshot.memory_ref(MemoryRange::Ram(pc_offset + 2..(ramtop as usize + 1) - ROMSIZE))?;
422 wr.write_all(mem_slice)?;
423 if is_mem16k {
424 io::copy(&mut io::repeat(!0).take(0x8000), &mut wr)?;
425 }
426 wr.flush()?;
427 Ok(result)
428}