use std::num::Wrapping;
use std::io::{self, Read};
use spectrusty::z80emu::{Cpu, CpuFlags, Prefix, StkReg16, Reg8};
use spectrusty::memory::ZxMemory;
pub fn try_instant_rom_tape_load_or_verify<C: Cpu, M: ZxMemory, R: Read, F: FnOnce() -> io::Result<R>>(
cpu: &mut C,
memory: &mut M,
acquire_reader: F
) -> io::Result<Option<u32>>
{
let (sp, de, head_match, flags) = match is_rom_loading(cpu, memory) {
Some(res) => res,
None => return Ok(None)
};
let mut chunk_bytes = acquire_reader()?.bytes();
let head = match chunk_bytes.next() {
Some(res) => res?,
None => return Ok(Some(0))
};
cpu.set_sp(sp);
let c = (cpu.get_reg(Reg8::C, None) & 0x7F) ^ 3;
cpu.set_reg(Reg8::C, None, c);
if head_match != head {
head_mismatch_exit(cpu, head);
return Ok(Some(1))
}
cpu.ex_af_af();
cpu.set_acc(c);
cpu.set_flags(flags|CpuFlags::Z);
cpu.ex_af_af();
let is_load = flags.cf();
let mut checksum = head;
let mut limit = de;
let mut tgt_addr = Wrapping(cpu.get_index16(Prefix::Xdd));
for res in chunk_bytes {
let octet = match res {
Ok(o) => o,
Err(e) => {
tape_timeout_exit(cpu, checksum, tgt_addr.0, limit);
return Err(e)
}
};
checksum ^= octet;
if limit == 0 || (!is_load && memory.read(tgt_addr.0) != octet) {
finished_exit(cpu, checksum, octet, tgt_addr.0, limit);
return Ok(Some((de - limit) as u32 + 2));
}
if is_load {
memory.write(tgt_addr.0, octet);
}
tgt_addr += Wrapping(1);
limit -= 1;
}
tape_timeout_exit(cpu, checksum, tgt_addr.0, limit);
Ok(Some((de - limit) as u32 + 2))
}
#[inline(never)]
fn tape_timeout_exit<C: Cpu>(cpu: &mut C, checksum: u8, tgt_addr: u16, bytes_left: u16) {
cpu.set_pc(0x05CD);
cpu.set_index16(Prefix::Xdd, tgt_addr);
cpu.set_reg16(StkReg16::DE, bytes_left);
cpu.set_reg2(StkReg16::HL, checksum, 1);
cpu.set_reg(Reg8::B, None, 0);
cpu.set_acc(0);
cpu.set_flags(CpuFlags::Z|CpuFlags::H);
}
fn finished_exit<C: Cpu>(cpu: &mut C, checksum: u8, octet: u8, tgt_addr: u16, bytes_left: u16) {
cpu.set_pc(0x05DB);
cpu.set_index16(Prefix::Xdd, tgt_addr);
cpu.set_reg16(StkReg16::DE, bytes_left);
cpu.set_reg2(StkReg16::HL, checksum, octet);
cpu.set_reg(Reg8::B, None, 0xB0);
}
fn head_mismatch_exit<C: Cpu>(cpu: &mut C, head: u8) {
cpu.set_pc(0x05DB);
cpu.set_reg2(StkReg16::HL, head, head);
cpu.set_reg(Reg8::B, None, 0xB0);
}
fn is_rom_loading<C: Cpu, M: ZxMemory>(cpu: &C, mem: &M) -> Option<(u16, u16, u8, CpuFlags)> {
if cpu.get_iffs() != (false, false) {
return None;
}
let stack: &[u16] = match cpu.get_pc() {
0x056B..=0x0570 => &[0x053F],
0x05E7..=0x05F9 => &[0x056F, 0x053F],
_ => &[]
};
let mut stack = stack.iter();
let mut cmp16 = match stack.next() {
Some(&m) => m,
None => return None,
};
let mut sp = cpu.get_sp();
loop {
if mem.read16(sp) != cmp16 {
return None;
}
cmp16 = match stack.next() {
Some(&m) => m,
None => break,
};
sp = sp.wrapping_add(2);
}
let de = cpu.get_reg16(StkReg16::DE);
if !(1..=0xFEFF).contains(&de) {
return None;
}
let (head_match, flags) = cpu.get_alt_reg2(StkReg16::AF);
let flags = CpuFlags::from_bits_truncate(flags);
if flags.zf() {
return None;
}
Some((sp, de, head_match, flags))
}