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§

The error type returned by the SnapshotLoader::select_model method.

Required Methods§

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.

Should read a memory chunk from the given reader source according to the specified range.

This method should not fail.

Should attach an instance of the cpu.

This method should not fail.

Should set the frame T-states clock to the value given in tstates.

This method should not fail.

Should emulate sending the data to the given port of the main chipset.

This method should not fail.

Provided Methods§

Should select the given joystick model if one is available.

This method should not fail. Default implementation does nothing.

Examples found in repository?
src/z80/loader.rs (line 193)
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(())
}

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?
src/z80/loader.rs (line 232)
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(())
}

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?
src/z80/loader.rs (line 270)
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(())
}

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.

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?
src/sna.rs (line 223)
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(())
}

Implementors§