2021-04-25 20:54:34 +00:00
|
|
|
|
//
|
|
|
|
|
// Z80.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 25/04/2021.
|
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "Z80.hpp"
|
|
|
|
|
|
2021-04-25 21:34:43 +00:00
|
|
|
|
#include "../FileHolder.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
|
|
|
|
|
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
|
|
|
|
|
|
2021-04-25 20:54:34 +00:00
|
|
|
|
using namespace Storage::State;
|
|
|
|
|
|
2021-04-25 21:34:43 +00:00
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> read_memory(Storage::FileHolder &file, size_t size, bool is_compressed) {
|
|
|
|
|
if(!is_compressed) {
|
|
|
|
|
return file.read(size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> result(size);
|
|
|
|
|
size_t cursor = 0;
|
|
|
|
|
|
2021-04-25 22:07:36 +00:00
|
|
|
|
while(cursor != size) {
|
|
|
|
|
const uint8_t next = file.get8();
|
|
|
|
|
|
2021-04-26 00:46:49 +00:00
|
|
|
|
// If the next byte definitely doesn't, or can't,
|
|
|
|
|
// start an ED ED sequence then just take it.
|
|
|
|
|
if(next != 0xed || cursor == size - 1) {
|
2021-04-25 22:07:36 +00:00
|
|
|
|
result[cursor] = next;
|
|
|
|
|
++cursor;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 00:46:49 +00:00
|
|
|
|
// Grab the next byte. If it's not ED then write
|
|
|
|
|
// both and continue.
|
2021-04-25 22:07:36 +00:00
|
|
|
|
const uint8_t after = file.get8();
|
|
|
|
|
if(after != 0xed) {
|
|
|
|
|
result[cursor] = next;
|
2021-04-26 00:46:49 +00:00
|
|
|
|
result[cursor+1] = after;
|
|
|
|
|
cursor += 2;
|
2021-04-25 22:07:36 +00:00
|
|
|
|
continue;
|
2021-04-25 21:34:43 +00:00
|
|
|
|
}
|
2021-04-25 22:07:36 +00:00
|
|
|
|
|
2021-04-26 00:46:49 +00:00
|
|
|
|
// An ED ED has begun, so grab the RLE sequence.
|
2021-04-25 22:07:36 +00:00
|
|
|
|
const uint8_t count = file.get8();
|
|
|
|
|
const uint8_t value = file.get8();
|
|
|
|
|
|
|
|
|
|
memset(&result[cursor], value, count);
|
|
|
|
|
cursor += count;
|
2021-04-25 21:34:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-25 20:54:34 +00:00
|
|
|
|
std::unique_ptr<Analyser::Static::Target> Z80::load(const std::string &file_name) {
|
2021-04-25 21:34:43 +00:00
|
|
|
|
FileHolder file(file_name);
|
|
|
|
|
|
|
|
|
|
// Construct a target with a Spectrum state.
|
|
|
|
|
using Target = Analyser::Static::ZXSpectrum::Target;
|
|
|
|
|
auto result = std::make_unique<Target>();
|
|
|
|
|
auto *const state = new Sinclair::ZXSpectrum::State();
|
|
|
|
|
result->state = std::unique_ptr<Reflection::Struct>(state);
|
|
|
|
|
|
|
|
|
|
// Read version 1 header.
|
|
|
|
|
state->z80.registers.a = file.get8();
|
|
|
|
|
state->z80.registers.flags = file.get8();
|
|
|
|
|
state->z80.registers.bc = file.get16le();
|
|
|
|
|
state->z80.registers.hl = file.get16le();
|
|
|
|
|
state->z80.registers.program_counter = file.get16le();
|
|
|
|
|
state->z80.registers.stack_pointer = file.get16le();
|
|
|
|
|
state->z80.registers.ir = file.get16be(); // Stored I then R.
|
|
|
|
|
|
|
|
|
|
// Bit 7 of R is stored separately; likely this relates to an
|
|
|
|
|
// optimisation in the Z80 emulator that for some reason was
|
|
|
|
|
// exported into its file format.
|
|
|
|
|
const uint8_t raw_misc = file.get8();
|
|
|
|
|
const uint8_t misc = (raw_misc == 0xff) ? 1 : raw_misc;
|
|
|
|
|
state->z80.registers.ir = uint16_t((state->z80.registers.ir & ~0x80) | ((misc&1) << 7));
|
|
|
|
|
|
|
|
|
|
state->z80.registers.de = file.get16le();
|
|
|
|
|
state->z80.registers.bc_dash = file.get16le();
|
|
|
|
|
state->z80.registers.de_dash = file.get16le();
|
|
|
|
|
state->z80.registers.hl_dash = file.get16le();
|
|
|
|
|
state->z80.registers.af_dash = file.get16be(); // Stored A' then F'.
|
|
|
|
|
state->z80.registers.iy = file.get16le();
|
|
|
|
|
state->z80.registers.ix = file.get16le();
|
|
|
|
|
state->z80.registers.iff1 = bool(file.get8());
|
|
|
|
|
state->z80.registers.iff2 = bool(file.get8());
|
|
|
|
|
|
|
|
|
|
// Ignored from the next byte:
|
|
|
|
|
//
|
2023-05-12 18:14:45 +00:00
|
|
|
|
// bit 2 = 1 => issue 2 emulation
|
2021-04-25 21:34:43 +00:00
|
|
|
|
// bit 3 = 1 => double interrupt frequency (?)
|
|
|
|
|
// bit 4–5 => video synchronisation (to do with emulation hackery?)
|
|
|
|
|
// bit 6–7 => joystick type
|
|
|
|
|
state->z80.registers.interrupt_mode = file.get8() & 3;
|
|
|
|
|
|
|
|
|
|
// If the program counter is non-0 then this is a version 1 snapshot,
|
|
|
|
|
// which means it's definitely a 48k image.
|
|
|
|
|
if(state->z80.registers.program_counter) {
|
|
|
|
|
result->model = Target::Model::FortyEightK;
|
|
|
|
|
state->ram = read_memory(file, 48*1024, misc & 0x20);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This was a version 1 or 2 snapshot, so keep going...
|
|
|
|
|
const uint16_t bonus_header_size = file.get16le();
|
|
|
|
|
if(bonus_header_size != 23 && bonus_header_size != 54 && bonus_header_size != 55) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state->z80.registers.program_counter = file.get16le();
|
2021-04-26 00:46:49 +00:00
|
|
|
|
const uint8_t model = file.get8();
|
|
|
|
|
switch(model) {
|
2021-04-25 21:34:43 +00:00
|
|
|
|
default: return nullptr;
|
|
|
|
|
case 0: result->model = Target::Model::FortyEightK; break;
|
|
|
|
|
case 3: result->model = Target::Model::OneTwoEightK; break;
|
|
|
|
|
case 7:
|
|
|
|
|
case 8: result->model = Target::Model::Plus3; break;
|
|
|
|
|
case 12: result->model = Target::Model::Plus2; break;
|
|
|
|
|
case 13: result->model = Target::Model::Plus2a; break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 00:46:49 +00:00
|
|
|
|
state->last_7ffd = file.get8();
|
2021-04-25 21:34:43 +00:00
|
|
|
|
|
|
|
|
|
file.seek(1, SEEK_CUR);
|
|
|
|
|
if(file.get8() & 0x80) {
|
|
|
|
|
// The 'hardware modify' bit, which inexplicably does this:
|
|
|
|
|
switch(result->model) {
|
|
|
|
|
default: break;
|
|
|
|
|
case Target::Model::FortyEightK: result->model = Target::Model::SixteenK; break;
|
|
|
|
|
case Target::Model::OneTwoEightK: result->model = Target::Model::Plus2; break;
|
|
|
|
|
case Target::Model::Plus3: result->model = Target::Model::Plus2a; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 00:47:28 +00:00
|
|
|
|
state->ay.selected_register = file.get8();
|
2021-04-26 21:39:11 +00:00
|
|
|
|
file.read(state->ay.registers, 16);
|
2021-04-25 21:34:43 +00:00
|
|
|
|
|
|
|
|
|
if(bonus_header_size != 23) {
|
|
|
|
|
// More Z80, the emulator, lack of encapsulation to deal with here.
|
|
|
|
|
const uint16_t low_t_state = file.get16le();
|
|
|
|
|
const uint16_t high_t_state = file.get8();
|
|
|
|
|
switch(result->model) {
|
|
|
|
|
case Target::Model::SixteenK:
|
|
|
|
|
case Target::Model::FortyEightK:
|
2021-04-27 01:20:32 +00:00
|
|
|
|
state->video.half_cycles_since_interrupt = ((17471 - low_t_state) + (high_t_state * 17472)) * 2;
|
2021-04-25 21:34:43 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2021-04-27 01:20:32 +00:00
|
|
|
|
state->video.half_cycles_since_interrupt = ((17726 - low_t_state) + (high_t_state * 17727)) * 2;
|
2021-04-25 21:34:43 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip: Spectator flag, MGT, Multiface and other ROM flags.
|
|
|
|
|
file.seek(5, SEEK_CUR);
|
|
|
|
|
|
|
|
|
|
// Skip: highly Z80-the-emulator-specific stuff about user-defined joystick.
|
|
|
|
|
file.seek(20, SEEK_CUR);
|
|
|
|
|
|
|
|
|
|
// Skip: Disciple/Plus D stuff.
|
|
|
|
|
file.seek(3, SEEK_CUR);
|
|
|
|
|
|
|
|
|
|
if(bonus_header_size == 55) {
|
2021-04-26 00:46:49 +00:00
|
|
|
|
state->last_1ffd = file.get8();
|
2021-04-25 21:34:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Grab RAM.
|
2021-04-25 21:55:52 +00:00
|
|
|
|
switch(result->model) {
|
|
|
|
|
case Target::Model::SixteenK: state->ram.resize(16 * 1024); break;
|
|
|
|
|
case Target::Model::FortyEightK: state->ram.resize(48 * 1024); break;
|
|
|
|
|
default: state->ram.resize(128 * 1024); break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-25 21:34:43 +00:00
|
|
|
|
while(true) {
|
|
|
|
|
const uint16_t block_size = file.get16le();
|
|
|
|
|
const uint8_t page = file.get8();
|
|
|
|
|
const auto location = file.tell();
|
|
|
|
|
if(file.eof()) break;
|
|
|
|
|
|
2021-04-25 21:55:52 +00:00
|
|
|
|
const auto data = read_memory(file, 16384, block_size != 0xffff);
|
|
|
|
|
|
|
|
|
|
if(result->model == Target::Model::SixteenK || result->model == Target::Model::FortyEightK) {
|
|
|
|
|
switch(page) {
|
|
|
|
|
default: break;
|
|
|
|
|
case 4: memcpy(&state->ram[0x4000], data.data(), 16384); break;
|
|
|
|
|
case 5: memcpy(&state->ram[0x8000], data.data(), 16384); break;
|
|
|
|
|
case 8: memcpy(&state->ram[0x0000], data.data(), 16384); break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if(page >= 3 && page <= 10) {
|
|
|
|
|
memcpy(&state->ram[(page - 3) * 0x4000], data.data(), 16384);
|
|
|
|
|
}
|
2021-04-25 21:34:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-25 22:02:31 +00:00
|
|
|
|
assert(location + block_size == file.tell());
|
2021-04-25 21:34:43 +00:00
|
|
|
|
file.seek(location + block_size, SEEK_SET);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
2021-04-25 20:54:34 +00:00
|
|
|
|
}
|