Trait spectrusty_formats::snapshot::SnapshotLoader
source · pub trait SnapshotLoader {
type Error: Into<Box<dyn Error + Send + Sync + 'static>>;
fn select_model(
&mut self,
model: ComputerModel,
extensions: Extensions,
border: BorderColor,
issue: ReadEarMode
) -> Result<(), Self::Error>;
fn read_into_memory<R: Read>(
&mut self,
range: MemoryRange,
reader: R
) -> Result<(), ZxMemoryError>;
fn assign_cpu(&mut self, cpu: CpuModel);
fn set_clock(&mut self, tstates: FTs);
fn write_port(&mut self, port: u16, data: u8);
fn select_joystick(&mut self, _joystick: JoystickModel) { ... }
fn setup_ay(
&mut self,
_choice: Ay3_891xDevice,
_reg_selected: AyRegister,
_reg_values: &[u8; 16]
) { ... }
fn interface1_rom_paged_in(&mut self) { ... }
fn plus_d_rom_paged_in(&mut self) { ... }
fn tr_dos_rom_paged_in(&mut self) { ... }
}
Expand description
Implement this trait to be able to load snapshot from files supported by this crate.
The method SnapshotLoader::select_model is always being called first. Then the other methods are being called in unspecified order. Not every method is always being called though. Some methods are not called when it makes no sense in the context of the loaded snapshot.
Required Associated Types§
Required Methods§
sourcefn select_model(
&mut self,
model: ComputerModel,
extensions: Extensions,
border: BorderColor,
issue: ReadEarMode
) -> Result<(), Self::Error>
fn select_model(
&mut self,
model: ComputerModel,
extensions: Extensions,
border: BorderColor,
issue: ReadEarMode
) -> Result<(), Self::Error>
Should create an instance of an emulated model from the given model
and other arguments.
If the model can not be emulated this method should return an Err(Self::Error)
.
This method is always being called first, before any other methods in this trait.
sourcefn read_into_memory<R: Read>(
&mut self,
range: MemoryRange,
reader: R
) -> Result<(), ZxMemoryError>
fn read_into_memory<R: Read>(
&mut self,
range: MemoryRange,
reader: R
) -> Result<(), ZxMemoryError>
Should read a memory chunk from the given reader
source according to the specified range
.
This method should not fail.
sourcefn assign_cpu(&mut self, cpu: CpuModel)
fn assign_cpu(&mut self, cpu: CpuModel)
Should attach an instance of the cpu
.
This method should not fail.
sourcefn set_clock(&mut self, tstates: FTs)
fn set_clock(&mut self, tstates: FTs)
Should set the frame T-states clock to the value given in tstates
.
This method should not fail.
sourcefn write_port(&mut self, port: u16, data: u8)
fn write_port(&mut self, port: u16, data: u8)
Should emulate sending the data
to the given port
of the main chipset.
This method should not fail.
Provided Methods§
sourcefn select_joystick(&mut self, _joystick: JoystickModel)
fn select_joystick(&mut self, _joystick: JoystickModel)
Should select the given joystick model if one is available.
This method should not fail. Default implementation does nothing.
Examples found in repository?
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
pub fn load_z80<R: Read, S: SnapshotLoader>(
mut rd: R,
loader: &mut S
) -> Result<()>
{
use ComputerModel::*;
let header = Header::read_new_struct(rd.by_ref())?;
let mut version = Z80Version::V1;
let mut model = ComputerModel::Spectrum48;
let mut extensions = Extensions::default();
let mut cpu = create_cpu(&header)?;
let header_ex = if cpu.get_pc() == 0 {
let (ver, head_ex) = load_header_ex(rd.by_ref())?;
version = ver;
cpu.set_pc(u16::from_le_bytes(head_ex.pc));
let (mdl, ext) = select_hw_model(version, &head_ex).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported model")
)?;
model = mdl;
extensions = ext;
Some(head_ex)
}
else {
None
};
let flags1 = Flags1::from(header.flags1);
let border = flags1.border_color();
let flags2 = Flags2::from(header.flags2);
let joystick = flags2.joystick_model();
let issue = model.applicable_issue(
if flags2.is_issue2_emulation() {
ReadEarMode::Issue2
}
else {
ReadEarMode::Issue3
}
);
loader.select_model(model, extensions, border, issue)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
loader.select_joystick(joystick);
loader.assign_cpu(CpuModel::NMOS(cpu));
// clippy false positive: https://github.com/rust-lang/rust-clippy/issues/9274
#[allow(clippy::read_zero_byte_vec)] {
let mut buf = Vec::new();
if version == Z80Version::V1 {
if flags1.is_mem_compressed() {
rd.read_to_end(&mut buf)?;
let buf = match buf.get(buf.len() - 4..) {
Some(MEMORY_V1_TERM) => &buf[..buf.len() - 4],
_ => &buf[..]
};
let decompress = MemDecompress::new(buf);
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), decompress)?;
}
else {
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), rd)?;
}
}
else {
while let Some((len, page, is_compressed)) = load_mem_header(rd.by_ref())? {
let range = mem_page_to_range(page, model, extensions).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported memory page")
)?;
if is_compressed {
buf.resize(len, 0);
rd.read_exact(&mut buf)?;
let decompress = MemDecompress::new(&buf);
loader.read_into_memory(range, decompress)?;
}
else {
loader.read_into_memory(range, rd.by_ref().take(len as u64))?;
}
}
}}
if let Some(head_ex) = header_ex {
if let Some(choice) = select_ay_model(model, Flags3::from(head_ex.flags3)) {
loader.setup_ay(choice, head_ex.ay_sel_reg.into(), &head_ex.ay_regs);
}
if version == Z80Version::V3 {
let ts = z80_to_cycles(u16::from_le_bytes(head_ex.ts_lo), head_ex.ts_hi, model);
loader.set_clock(ts);
let data = head_ex.port2;
if data != 0 {
match model {
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x1ffd, data);
}
_ => {}
}
}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xf4, head_ex.port1);
}
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x7ffd, head_ex.port1);
}
_ => {}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xff, head_ex.ifrom);
}
Spectrum16|Spectrum48|
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE
if head_ex.ifrom == 0xff && extensions.intersects(Extensions::IF1) => {
loader.interface1_rom_paged_in();
}
_ => {}
}
}
Ok(())
}
sourcefn setup_ay(
&mut self,
_choice: Ay3_891xDevice,
_reg_selected: AyRegister,
_reg_values: &[u8; 16]
)
fn setup_ay(
&mut self,
_choice: Ay3_891xDevice,
_reg_selected: AyRegister,
_reg_values: &[u8; 16]
)
Should attach the amulated instance of AY-3-891x
sound processor and initialize it from
the given arguments if one is available.
This method is called n-times if more than one AY chipset is attached and there are different register values for each of them.
This method should not fail. Default implementation does nothing.
Examples found in repository?
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
pub fn load_z80<R: Read, S: SnapshotLoader>(
mut rd: R,
loader: &mut S
) -> Result<()>
{
use ComputerModel::*;
let header = Header::read_new_struct(rd.by_ref())?;
let mut version = Z80Version::V1;
let mut model = ComputerModel::Spectrum48;
let mut extensions = Extensions::default();
let mut cpu = create_cpu(&header)?;
let header_ex = if cpu.get_pc() == 0 {
let (ver, head_ex) = load_header_ex(rd.by_ref())?;
version = ver;
cpu.set_pc(u16::from_le_bytes(head_ex.pc));
let (mdl, ext) = select_hw_model(version, &head_ex).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported model")
)?;
model = mdl;
extensions = ext;
Some(head_ex)
}
else {
None
};
let flags1 = Flags1::from(header.flags1);
let border = flags1.border_color();
let flags2 = Flags2::from(header.flags2);
let joystick = flags2.joystick_model();
let issue = model.applicable_issue(
if flags2.is_issue2_emulation() {
ReadEarMode::Issue2
}
else {
ReadEarMode::Issue3
}
);
loader.select_model(model, extensions, border, issue)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
loader.select_joystick(joystick);
loader.assign_cpu(CpuModel::NMOS(cpu));
// clippy false positive: https://github.com/rust-lang/rust-clippy/issues/9274
#[allow(clippy::read_zero_byte_vec)] {
let mut buf = Vec::new();
if version == Z80Version::V1 {
if flags1.is_mem_compressed() {
rd.read_to_end(&mut buf)?;
let buf = match buf.get(buf.len() - 4..) {
Some(MEMORY_V1_TERM) => &buf[..buf.len() - 4],
_ => &buf[..]
};
let decompress = MemDecompress::new(buf);
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), decompress)?;
}
else {
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), rd)?;
}
}
else {
while let Some((len, page, is_compressed)) = load_mem_header(rd.by_ref())? {
let range = mem_page_to_range(page, model, extensions).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported memory page")
)?;
if is_compressed {
buf.resize(len, 0);
rd.read_exact(&mut buf)?;
let decompress = MemDecompress::new(&buf);
loader.read_into_memory(range, decompress)?;
}
else {
loader.read_into_memory(range, rd.by_ref().take(len as u64))?;
}
}
}}
if let Some(head_ex) = header_ex {
if let Some(choice) = select_ay_model(model, Flags3::from(head_ex.flags3)) {
loader.setup_ay(choice, head_ex.ay_sel_reg.into(), &head_ex.ay_regs);
}
if version == Z80Version::V3 {
let ts = z80_to_cycles(u16::from_le_bytes(head_ex.ts_lo), head_ex.ts_hi, model);
loader.set_clock(ts);
let data = head_ex.port2;
if data != 0 {
match model {
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x1ffd, data);
}
_ => {}
}
}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xf4, head_ex.port1);
}
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x7ffd, head_ex.port1);
}
_ => {}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xff, head_ex.ifrom);
}
Spectrum16|Spectrum48|
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE
if head_ex.ifrom == 0xff && extensions.intersects(Extensions::IF1) => {
loader.interface1_rom_paged_in();
}
_ => {}
}
}
Ok(())
}
sourcefn interface1_rom_paged_in(&mut self)
fn interface1_rom_paged_in(&mut self)
Should page in the Interface 1 ROM if one is available.
This method should not fail. If an Interface 1 is not supported SnapshotLoader::select_model may return with an error if Extensions would indicate such support is being requested.
Panics
The default implementation always panics.
Examples found in repository?
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
pub fn load_z80<R: Read, S: SnapshotLoader>(
mut rd: R,
loader: &mut S
) -> Result<()>
{
use ComputerModel::*;
let header = Header::read_new_struct(rd.by_ref())?;
let mut version = Z80Version::V1;
let mut model = ComputerModel::Spectrum48;
let mut extensions = Extensions::default();
let mut cpu = create_cpu(&header)?;
let header_ex = if cpu.get_pc() == 0 {
let (ver, head_ex) = load_header_ex(rd.by_ref())?;
version = ver;
cpu.set_pc(u16::from_le_bytes(head_ex.pc));
let (mdl, ext) = select_hw_model(version, &head_ex).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported model")
)?;
model = mdl;
extensions = ext;
Some(head_ex)
}
else {
None
};
let flags1 = Flags1::from(header.flags1);
let border = flags1.border_color();
let flags2 = Flags2::from(header.flags2);
let joystick = flags2.joystick_model();
let issue = model.applicable_issue(
if flags2.is_issue2_emulation() {
ReadEarMode::Issue2
}
else {
ReadEarMode::Issue3
}
);
loader.select_model(model, extensions, border, issue)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
loader.select_joystick(joystick);
loader.assign_cpu(CpuModel::NMOS(cpu));
// clippy false positive: https://github.com/rust-lang/rust-clippy/issues/9274
#[allow(clippy::read_zero_byte_vec)] {
let mut buf = Vec::new();
if version == Z80Version::V1 {
if flags1.is_mem_compressed() {
rd.read_to_end(&mut buf)?;
let buf = match buf.get(buf.len() - 4..) {
Some(MEMORY_V1_TERM) => &buf[..buf.len() - 4],
_ => &buf[..]
};
let decompress = MemDecompress::new(buf);
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), decompress)?;
}
else {
loader.read_into_memory(MemoryRange::Ram(0..3*PAGE_SIZE), rd)?;
}
}
else {
while let Some((len, page, is_compressed)) = load_mem_header(rd.by_ref())? {
let range = mem_page_to_range(page, model, extensions).ok_or_else(||
io::Error::new(io::ErrorKind::InvalidData, "unsupported memory page")
)?;
if is_compressed {
buf.resize(len, 0);
rd.read_exact(&mut buf)?;
let decompress = MemDecompress::new(&buf);
loader.read_into_memory(range, decompress)?;
}
else {
loader.read_into_memory(range, rd.by_ref().take(len as u64))?;
}
}
}}
if let Some(head_ex) = header_ex {
if let Some(choice) = select_ay_model(model, Flags3::from(head_ex.flags3)) {
loader.setup_ay(choice, head_ex.ay_sel_reg.into(), &head_ex.ay_regs);
}
if version == Z80Version::V3 {
let ts = z80_to_cycles(u16::from_le_bytes(head_ex.ts_lo), head_ex.ts_hi, model);
loader.set_clock(ts);
let data = head_ex.port2;
if data != 0 {
match model {
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x1ffd, data);
}
_ => {}
}
}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xf4, head_ex.port1);
}
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE => {
loader.write_port(0x7ffd, head_ex.port1);
}
_ => {}
}
match model {
TimexTC2048|TimexTS2068|TimexTC2068 => {
loader.write_port(0xff, head_ex.ifrom);
}
Spectrum16|Spectrum48|
Spectrum128|SpectrumPlus2|
SpectrumPlus2A|SpectrumPlus3|SpectrumPlus3e|
SpectrumSE
if head_ex.ifrom == 0xff && extensions.intersects(Extensions::IF1) => {
loader.interface1_rom_paged_in();
}
_ => {}
}
}
Ok(())
}
sourcefn plus_d_rom_paged_in(&mut self)
fn plus_d_rom_paged_in(&mut self)
Should page in the MGT +D ROM if one is available.
This method should not fail. If an MGT +D is not supported SnapshotLoader::select_model may return with an error if Extensions would indicate such support is being requested.
Panics
The default implementation always panics.
sourcefn tr_dos_rom_paged_in(&mut self)
fn tr_dos_rom_paged_in(&mut self)
Should page in the TR-DOS ROM if one is available.
This method should not fail. If an TR-DOS is not supported SnapshotLoader::select_model may return with an error if Extensions would indicate such support is being requested.
Panics
The default implementation always panics.
Examples found in repository?
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
pub fn load_sna<R: Read + Seek, S: SnapshotLoader>(
mut rd: R,
loader: &mut S
) -> Result<()>
{
let cur_pos = rd.seek(SeekFrom::Current(0))?;
let end_pos = rd.seek(SeekFrom::Current(SNA_LENGTH as i64))?;
if end_pos - cur_pos != SNA_LENGTH {
return Err(Error::new(ErrorKind::InvalidData, "SNA: wrong size of the supplied stream"));
}
let mut sna_ext = SnaHeader128::default();
let ext_read = sna_ext.read_struct_or_nothing(rd.by_ref())?;
rd.seek(SeekFrom::Start(cur_pos))?;
if !ext_read {
return load_sna48(rd, loader)
}
let mut cpu = Z80NMOS::default();
let border = read_header(rd.by_ref(), &mut cpu)?;
cpu.set_pc(u16::from_le_bytes(sna_ext.pc));
let extensions = if sna_ext.trdos_rom != 0 {
Extensions::TR_DOS
}
else {
Extensions::NONE
};
let model = ComputerModel::Spectrum128;
loader.select_model(model, extensions, border, ReadEarMode::Issue3)
.map_err(|e| Error::new(ErrorKind::Other, e))?;
let index48 = [5, 2];
let last_page = Ula128MemFlags::from_bits_truncate(sna_ext.port_data)
.last_ram_page_bank();
for page in index48.iter().chain(
Some(&last_page).filter(|n| !index48.contains(n))
) {
loader.read_into_memory(
MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE), rd.by_ref()
)?;
}
rd.seek(SeekFrom::Current(size_of::<SnaHeader128>() as i64))?;
for page in (0..8).filter(|n| !index48.contains(n) && *n != last_page) {
loader.read_into_memory(
MemoryRange::Ram(page * PAGE_SIZE..(page + 1) * PAGE_SIZE), rd.by_ref()
)?;
}
loader.assign_cpu(CpuModel::NMOS(cpu));
loader.write_port(0x7ffd, sna_ext.port_data);
if sna_ext.trdos_rom == 1 {
loader.tr_dos_rom_paged_in();
}
Ok(())
}