1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-11 04:28:58 +00:00

Merge pull request #929 from TomHarte/SpectrumSnapshots

Adds loading of state snapshots for the ZX Spectrum
This commit is contained in:
Thomas Harte 2021-04-26 17:44:02 -04:00 committed by GitHub
commit bd5dd9b9a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 740 additions and 113 deletions

View File

@ -57,6 +57,10 @@
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/Z80.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@ -73,15 +77,23 @@
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
namespace {
std::string get_extension(const std::string &name) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::string::size_type final_dot = name.find_last_of(".");
if(final_dot == std::string::npos) return name;
std::string extension = name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
@ -199,14 +211,34 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
Format("sna", SNA);
Format("z80", Z80);
#undef TryInsert
// Otherwise:
//
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\

View File

@ -15,6 +15,7 @@
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Reflection/Struct.hpp"
#include <memory>
#include <string>
@ -23,8 +24,10 @@
namespace Analyser {
namespace Static {
struct State;
/*!
A list of disks, tapes and cartridges.
A list of disks, tapes and cartridges, and possibly a state snapshot.
*/
struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
@ -48,13 +51,16 @@ struct Media {
};
/*!
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() {}
// This field is entirely optional.
std::unique_ptr<Reflection::Struct> state;
Machine machine;
Media media;
float confidence = 0.0f;

View File

@ -12,6 +12,8 @@
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Reflection/Struct.hpp"
namespace GI {
namespace AY38910 {
@ -162,6 +164,8 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
friend struct State;
};
/*!
@ -192,6 +196,26 @@ struct Utility {
};
struct State: public Reflection::StructImpl<State> {
uint8_t registers[16]{};
// TODO: all audio-production thread state.
State() {
if(needs_declare()) {
DeclareField(registers);
}
}
template <typename AY> void apply(AY &target) {
// Establish emulator-thread state
for(uint8_t c = 0; c < 16; c++) {
target.select_register(c);
target.set_register_value(registers[c]);
}
}
};
}
}

View File

@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@ -20,6 +20,7 @@
#include "MediaTarget.hpp"
#include "MouseMachine.hpp"
#include "ScanProducer.hpp"
#include "StateProducer.hpp"
#include "TimedMachine.hpp"
#endif /* MachineTypes_h */

View File

@ -127,5 +127,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@ -275,13 +275,13 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
switch(machine_) {
case Machine::ZX80:
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
return table_lookup_sequence_for_character(zx80_key_sequences, character);
case Machine::ZX81:
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(zx81_key_sequences, character);
case Machine::ZXSpectrum:
return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(spectrum_key_sequences, character);
}
}

View File

@ -0,0 +1,55 @@
//
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_hpp
#define State_hpp
#include "../../../Reflection/Struct.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "Video.hpp"
#include "../../../Components/AY38910/AY38910.hpp"
namespace Sinclair {
namespace ZXSpectrum {
struct State: public Reflection::StructImpl<State> {
CPU::Z80::State z80;
Video::State video;
// In 16kb or 48kb mode, RAM will be 16kb or 48kb and represent
// memory in standard linear order. In 128kb mode, RAM will be
// 128kb with the first 16kb representing bank 0, the next bank 1, etc.
std::vector<uint8_t> ram;
// Meaningful for 128kb machines only.
uint8_t last_7ffd = 0;
uint8_t last_fffd = 0;
GI::AY38910::State ay;
// Meaningful for the +2a and +3 only.
uint8_t last_1ffd = 0;
State() {
if(needs_declare()) {
DeclareField(z80);
DeclareField(video);
DeclareField(ram);
DeclareField(last_7ffd);
DeclareField(last_fffd);
DeclareField(last_1ffd);
DeclareField(ay);
}
}
};
}
}
#endif /* State_h */

View File

@ -12,12 +12,15 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Reflection/Struct.hpp"
#include <algorithm>
namespace Sinclair {
namespace ZXSpectrum {
namespace Video {
enum class VideoTiming {
enum class Timing {
FortyEightK,
OneTwoEightK,
Plus3,
@ -47,7 +50,7 @@ enum class VideoTiming {
*/
template <VideoTiming timing> class Video {
template <Timing timing> class Video {
private:
struct Timings {
// Number of cycles per line. Will be 224 or 228.
@ -78,17 +81,17 @@ template <VideoTiming timing> class Video {
};
static constexpr Timings get_timings() {
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
return Timings(228, 311, 6, 129, 14361, delays);
}
if constexpr (timing == VideoTiming::OneTwoEightK) {
if constexpr (timing == Timing::OneTwoEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(228, 311, 4, 128, 14361, delays);
}
if constexpr (timing == VideoTiming::FortyEightK) {
if constexpr (timing == Timing::FortyEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(224, 312, 4, 128, 14335, delays);
}
@ -103,7 +106,7 @@ template <VideoTiming timing> class Video {
constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1;
constexpr int sync_position = (timing == VideoTiming::FortyEightK) ? 164 * 2 : 166 * 2;
constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
constexpr int sync_length = 17 * 2;
constexpr int burst_position = sync_position + 40;
constexpr int burst_length = 17;
@ -220,7 +223,7 @@ template <VideoTiming timing> class Video {
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
if constexpr (timing >= VideoTiming::OneTwoEightK) {
if constexpr (timing >= Timing::OneTwoEightK) {
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
// The colour burst phase above is an empirical guess. I need to research further.
} else {
@ -248,7 +251,7 @@ template <VideoTiming timing> class Video {
}
static constexpr int half_cycles_per_line() {
if constexpr (timing == VideoTiming::FortyEightK) {
if constexpr (timing == Timing::FortyEightK) {
// TODO: determine real figure here, if one exists.
// The source I'm looking at now suggests that the theoretical
// ideal of 224*2 ignores the real-life effects of separate
@ -267,6 +270,11 @@ template <VideoTiming timing> class Video {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// Get the CRT roughly into phase.
//
// TODO: this is coupled to an assumption about the initial CRT. Fix.
const auto timings = get_timings();
crt_.output_blank(timings.lines_per_frame*timings.cycles_per_line - timings.interrupt_time);
}
void set_video_source(const uint8_t *source) {
@ -328,7 +336,7 @@ template <VideoTiming timing> class Video {
*/
uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff;
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
const int line = time_into_frame_ / timings.cycles_per_line;
if(line >= 192) {
@ -342,7 +350,7 @@ template <VideoTiming timing> class Video {
// The +2a and +3 always return the low bit as set.
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
return value | 1;
}
return value;
@ -354,7 +362,7 @@ template <VideoTiming timing> class Video {
bus is accessed when the gate array isn't currently reading.
*/
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
last_contended_access_ = value | 1;
}
}
@ -363,6 +371,7 @@ template <VideoTiming timing> class Video {
Sets the current border colour.
*/
void set_border_colour(uint8_t colour) {
border_byte_ = colour;
border_colour_ = palette[colour];
}
@ -381,11 +390,17 @@ template <VideoTiming timing> class Video {
crt_.set_display_type(type);
}
/*! Gets the display type. */
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
private:
int time_into_frame_ = 0;
Outputs::CRT::CRT crt_;
const uint8_t *memory_ = nullptr;
uint8_t border_colour_ = 0;
uint8_t border_byte_ = 0;
uint8_t *pixel_target_ = nullptr;
int attribute_address_ = 0;
@ -398,6 +413,8 @@ template <VideoTiming timing> class Video {
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
uint8_t last_contended_access_ = 0xff;
friend struct State;
#define RGB(r, g, b) (r << 4) | (g << 2) | b
static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
@ -408,6 +425,41 @@ template <VideoTiming timing> class Video {
#undef RGB
};
struct State: public Reflection::StructImpl<State> {
uint8_t border_colour = 0;
int time_into_frame = 0;
bool flash = 0;
int flash_counter = 0;
bool is_alternate_line = false;
State() {
if(needs_declare()) {
DeclareField(border_colour);
DeclareField(time_into_frame);
DeclareField(flash);
DeclareField(flash_counter);
DeclareField(is_alternate_line);
}
}
template <typename Video> State(const Video &source) : State() {
border_colour = source.border_byte_;
time_into_frame = source.time_into_frame_;
flash = source.flash_mask_;
flash_counter = source.flash_counter_;
is_alternate_line = source. is_alternate_line_;
}
template <typename Video> void apply(Video &target) {
target.set_border_colour(border_colour);
target.time_into_frame_ = time_into_frame;
target.flash_mask_ = flash ? 0xff : 0x00;
target.flash_counter_ = flash_counter;
target.is_alternate_line_ = is_alternate_line;
}
};
}
}
}

View File

@ -8,9 +8,9 @@
#include "ZXSpectrum.hpp"
#include "State.hpp"
#include "Video.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../Keyboard/Keyboard.hpp"
#include "../../../Activity/Source.hpp"
#include "../../MachineTypes.hpp"
@ -24,7 +24,9 @@
// just grab the CPC's version of an FDC.
#include "../../AmstradCPC/FDC.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../../../Outputs/Log.hpp"
#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
@ -39,10 +41,6 @@
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "../Keyboard/Keyboard.hpp"
#include <array>
namespace Sinclair {
@ -124,6 +122,31 @@ template<Model model> class ConcreteMachine:
duration_to_press_enter_ = Cycles(5 * clock_rate());
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, true);
}
// Install state if supplied.
if(target.state) {
const auto state = static_cast<State *>(target.state.get());
state->z80.apply(z80_);
state->video.apply(*video_.last_valid());
state->ay.apply(ay_);
// If this is a 48k or 16k machine, remap source data from its original
// linear form to whatever the banks end up being; otherwise copy as is.
if(model <= Model::FortyEightK) {
const size_t num_banks = std::min(size_t(48*1024), state->ram.size()) >> 14;
for(size_t c = 0; c < num_banks; c++) {
memcpy(&write_pointers_[c + 1][(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000);
}
} else {
memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size()));
port1ffd_ = state->last_1ffd;
port7ffd_ = state->last_7ffd;
update_memory_map();
GI::AY38910::Utility::select_register(ay_, state->last_fffd);
}
}
}
~ConcreteMachine() {
@ -202,6 +225,10 @@ template<Model model> class ConcreteMachine:
video_->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const override {
return video_->get_display_type();
}
// MARK: - BusHandler.
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
@ -384,10 +411,6 @@ template<Model model> class ConcreteMachine:
// Set the proper video base pointer.
set_video_address();
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ |= *cycle.value & 0x20;
}
// Test for +2a/+3 paging (i.e. port 1ffd).
@ -625,6 +648,7 @@ template<Model model> class ConcreteMachine:
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
options->output = get_video_signal_configurable();
return options;
}
@ -713,6 +737,10 @@ template<Model model> class ConcreteMachine:
set_memory(2, 2);
set_memory(3, port7ffd_ & 7);
}
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ = port7ffd_ & 0x20;
}
void set_memory(int bank, uint8_t source) {
@ -758,10 +786,10 @@ template<Model model> class ConcreteMachine:
// MARK: - Video.
using VideoType =
std::conditional_t<
model <= Model::FortyEightK, Video<VideoTiming::FortyEightK>,
model <= Model::FortyEightK, Video::Video<Video::Timing::FortyEightK>,
std::conditional_t<
model <= Model::Plus2, Video<VideoTiming::OneTwoEightK>,
Video<VideoTiming::Plus3>
model <= Model::Plus2, Video::Video<Video::Timing::OneTwoEightK>,
Video::Video<Video::Timing::Plus3>
>
>;
JustInTimeActor<VideoType> video_;

View File

@ -0,0 +1,26 @@
//
// StateProducer.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_h
#define State_h
#include <memory>
#include "../Analyser/Static/StaticAnalyser.hpp"
namespace MachineTypes {
struct StateProducer {
// TODO.
// virtual bool get_state(Analyser::Static::State *, [[maybe_unused]] bool advance_to_simple = false) {
// return false;
// }
};
};
#endif /* State_h */

View File

@ -131,13 +131,3 @@ bool Typer::type_next_character() {
return true;
}
// MARK: - Character mapper
const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= (length / sizeof(KeySequence))) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}

View File

@ -44,11 +44,15 @@ class CharacterMapper {
typedef uint16_t KeySequence[16];
/*!
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
with @c length entries, returns the sequence for character @c character if it exists; otherwise
returns @c nullptr.
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/
const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const;
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}
};
/*!

View File

@ -479,6 +479,10 @@
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */; };
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; };
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; };
4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B8DF4D825465B7500F3433C /* IIgsMemoryMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */; };
4B8DF4F9254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; };
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; };
@ -1429,6 +1433,12 @@
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SpectrumVideoContentionTests.mm; sourceTree = "<group>"; };
4B8DD375263481BB00B3C866 /* StateProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StateProducer.hpp; sourceTree = "<group>"; };
4B8DD3842634D37E00B3C866 /* SNA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNA.cpp; sourceTree = "<group>"; };
4B8DD3852634D37E00B3C866 /* SNA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SNA.hpp; sourceTree = "<group>"; };
4B8DD3912635A72F00B3C866 /* State.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B8DD39526360DDF00B3C866 /* Z80.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Z80.cpp; sourceTree = "<group>"; };
4B8DD39626360DDF00B3C866 /* Z80.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Z80.hpp; sourceTree = "<group>"; };
4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = "<group>"; };
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = "<group>"; };
4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = "<group>"; };
@ -2210,6 +2220,7 @@
4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */,
4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */,
4B0F1C092603BA5F00B85C66 /* Video.hpp */,
4B8DD3912635A72F00B3C866 /* State.hpp */,
);
path = ZXSpectrum;
sourceTree = "<group>";
@ -2842,6 +2853,7 @@
4B8805F81DCFF6CD003085B1 /* Data */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
4B8DD3832634D37E00B3C866 /* State */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
);
name = Storage;
@ -3248,6 +3260,17 @@
path = AmstradCPC;
sourceTree = "<group>";
};
4B8DD3832634D37E00B3C866 /* State */ = {
isa = PBXGroup;
children = (
4B8DD3842634D37E00B3C866 /* SNA.cpp */,
4B8DD3852634D37E00B3C866 /* SNA.hpp */,
4B8DD39526360DDF00B3C866 /* Z80.cpp */,
4B8DD39626360DDF00B3C866 /* Z80.hpp */,
);
path = State;
sourceTree = "<group>";
};
4B8DF4EC254B840B00F3433C /* AppleClock */ = {
isa = PBXGroup;
children = (
@ -4018,6 +4041,7 @@
4B92294222B04A3D00A1458F /* MouseMachine.hpp */,
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B046DC31CFE651500E9E45E /* ScanProducer.hpp */,
4B8DD375263481BB00B3C866 /* StateProducer.hpp */,
4BC57CD32434282000FBC404 /* TimedMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4BCE0048227CE8CA000CA200 /* Apple */,
@ -5216,6 +5240,7 @@
4BEDA43225B3C700000C2DBD /* Executor.cpp in Sources */,
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */,
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
@ -5249,6 +5274,7 @@
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */,
4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */,
4B894525201967B4007DE474 /* Tape.cpp in Sources */,
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
@ -5345,6 +5371,7 @@
4BE211FF253FC80900435408 /* StaticAnalyser.cpp in Sources */,
4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */,
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,
4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */,
4BEDA40C25B2844B000C2DBD /* Decoder.cpp in Sources */,
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
@ -5486,6 +5513,7 @@
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */,
4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */,
4B894524201967B4007DE474 /* Tape.cpp in Sources */,
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,

View File

@ -592,6 +592,46 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sna</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SNA snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>z80</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum Z80 snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -129,6 +129,7 @@ SOURCES += \
$$SRC/Storage/MassStorage/Encodings/*.cpp \
$$SRC/Storage/MassStorage/Formats/*.cpp \
$$SRC/Storage/MassStorage/SCSI/*.cpp \
$$SRC/Storage/State/*.cpp \
$$SRC/Storage/Tape/*.cpp \
$$SRC/Storage/Tape/Formats/*.cpp \
$$SRC/Storage/Tape/Parsers/*.cpp \
@ -269,6 +270,7 @@ HEADERS += \
$$SRC/Storage/MassStorage/Encodings/*.hpp \
$$SRC/Storage/MassStorage/Formats/*.hpp \
$$SRC/Storage/MassStorage/SCSI/*.hpp \
$$SRC/Storage/State/*.hpp \
$$SRC/Storage/Tape/*.hpp \
$$SRC/Storage/Tape/Formats/*.hpp \
$$SRC/Storage/Tape/Parsers/*.hpp \

View File

@ -125,6 +125,7 @@ SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/SCSI/*.cpp')
SOURCES += glob.glob('../../Storage/State/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp')

View File

@ -33,18 +33,18 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const {
case Register::L: return hl_.halves.low;
case Register::HL: return hl_.full;
case Register::ADash: return afDash_.halves.high;
case Register::FlagsDash: return afDash_.halves.low;
case Register::AFDash: return afDash_.full;
case Register::BDash: return bcDash_.halves.high;
case Register::CDash: return bcDash_.halves.low;
case Register::BCDash: return bcDash_.full;
case Register::DDash: return deDash_.halves.high;
case Register::EDash: return deDash_.halves.low;
case Register::DEDash: return deDash_.full;
case Register::HDash: return hlDash_.halves.high;
case Register::LDash: return hlDash_.halves.low;
case Register::HLDash: return hlDash_.full;
case Register::ADash: return af_dash_.halves.high;
case Register::FlagsDash: return af_dash_.halves.low;
case Register::AFDash: return af_dash_.full;
case Register::BDash: return bc_dash_.halves.high;
case Register::CDash: return bc_dash_.halves.low;
case Register::BCDash: return bc_dash_.full;
case Register::DDash: return de_dash_.halves.high;
case Register::EDash: return de_dash_.halves.low;
case Register::DEDash: return de_dash_.full;
case Register::HDash: return hl_dash_.halves.high;
case Register::LDash: return hl_dash_.halves.low;
case Register::HLDash: return hl_dash_.full;
case Register::IXh: return ix_.halves.high;
case Register::IXl: return ix_.halves.low;
@ -86,18 +86,18 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
case Register::L: hl_.halves.low = uint8_t(value); break;
case Register::HL: hl_.full = value; break;
case Register::ADash: afDash_.halves.high = uint8_t(value); break;
case Register::FlagsDash: afDash_.halves.low = uint8_t(value); break;
case Register::AFDash: afDash_.full = value; break;
case Register::BDash: bcDash_.halves.high = uint8_t(value); break;
case Register::CDash: bcDash_.halves.low = uint8_t(value); break;
case Register::BCDash: bcDash_.full = value; break;
case Register::DDash: deDash_.halves.high = uint8_t(value); break;
case Register::EDash: deDash_.halves.low = uint8_t(value); break;
case Register::DEDash: deDash_.full = value; break;
case Register::HDash: hlDash_.halves.high = uint8_t(value); break;
case Register::LDash: hlDash_.halves.low = uint8_t(value); break;
case Register::HLDash: hlDash_.full = value; break;
case Register::ADash: af_dash_.halves.high = uint8_t(value); break;
case Register::FlagsDash: af_dash_.halves.low = uint8_t(value); break;
case Register::AFDash: af_dash_.full = value; break;
case Register::BDash: bc_dash_.halves.high = uint8_t(value); break;
case Register::CDash: bc_dash_.halves.low = uint8_t(value); break;
case Register::BCDash: bc_dash_.full = value; break;
case Register::DDash: de_dash_.halves.high = uint8_t(value); break;
case Register::EDash: de_dash_.halves.low = uint8_t(value); break;
case Register::DEDash: de_dash_.full = value; break;
case Register::HDash: hl_dash_.halves.high = uint8_t(value); break;
case Register::LDash: hl_dash_.halves.low = uint8_t(value); break;
case Register::HLDash: hl_dash_.full = value; break;
case Register::IXh: ix_.halves.high = uint8_t(value); break;
case Register::IXl: ix_.halves.low = uint8_t(value); break;

View File

@ -461,17 +461,17 @@ template < class T,
case MicroOp::ExAFAFDash: {
const uint8_t a = a_;
const uint8_t f = get_flags();
set_flags(afDash_.halves.low);
a_ = afDash_.halves.high;
afDash_.halves.high = a;
afDash_.halves.low = f;
set_flags(af_dash_.halves.low);
a_ = af_dash_.halves.high;
af_dash_.halves.high = a;
af_dash_.halves.low = f;
} break;
case MicroOp::EXX: {
uint16_t temp;
swap(de_, deDash_);
swap(bc_, bcDash_);
swap(hl_, hlDash_);
swap(de_, de_dash_);
swap(bc_, bc_dash_);
swap(hl_, hl_dash_);
} break;
#undef swap

View File

@ -129,7 +129,7 @@ class ProcessorStorage {
uint8_t a_;
RegisterPair16 bc_, de_, hl_;
RegisterPair16 afDash_, bcDash_, deDash_, hlDash_;
RegisterPair16 af_dash_, bc_dash_, de_dash_, hl_dash_;
RegisterPair16 ix_, iy_, pc_, sp_;
RegisterPair16 ir_, refresh_addr_;
bool iff1_ = false, iff2_ = false;

View File

@ -19,10 +19,10 @@ State::State(const ProcessorBase &src): State() {
registers.bc = src.bc_.full;
registers.de = src.de_.full;
registers.hl = src.hl_.full;
registers.afDash = src.afDash_.full;
registers.bcDash = src.bcDash_.full;
registers.deDash = src.deDash_.full;
registers.hlDash = src.hlDash_.full;
registers.af_dash = src.af_dash_.full;
registers.bc_dash = src.bc_dash_.full;
registers.de_dash = src.de_dash_.full;
registers.hl_dash = src.hl_dash_.full;
registers.ix = src.ix_.full;
registers.iy = src.iy_.full;
registers.ir = src.ir_.full;
@ -108,10 +108,10 @@ void State::apply(ProcessorBase &target) {
target.bc_.full = registers.bc;
target.de_.full = registers.de;
target.hl_.full = registers.hl;
target.afDash_.full = registers.afDash;
target.bcDash_.full = registers.bcDash;
target.deDash_.full = registers.deDash;
target.hlDash_.full = registers.hlDash;
target.af_dash_.full = registers.af_dash;
target.bc_dash_.full = registers.bc_dash;
target.de_dash_.full = registers.de_dash;
target.hl_dash_.full = registers.hl_dash;
target.ix_.full = registers.ix;
target.iy_.full = registers.iy;
target.ir_.full = registers.ir;
@ -179,10 +179,10 @@ State::Registers::Registers() {
DeclareField(bc);
DeclareField(de);
DeclareField(hl);
DeclareField(afDash);
DeclareField(bcDash);
DeclareField(deDash);
DeclareField(hlDash);
DeclareField(af_dash); // TODO: is there any disadvantage to declaring these for reflective
DeclareField(bc_dash); // purposes as AF', BC', etc?
DeclareField(de_dash);
DeclareField(hl_dash);
DeclareField(ix);
DeclareField(iy);
DeclareField(ir);

View File

@ -31,7 +31,7 @@ struct State: public Reflection::StructImpl<State> {
uint8_t a;
uint8_t flags;
uint16_t bc, de, hl;
uint16_t afDash, bcDash, deDash, hlDash;
uint16_t af_dash, bc_dash, de_dash, hl_dash;
uint16_t ix, iy, ir;
uint16_t program_counter, stack_pointer;
uint16_t memptr;
@ -60,25 +60,25 @@ struct State: public Reflection::StructImpl<State> {
obviously doesn't.
*/
struct ExecutionState: public Reflection::StructImpl<ExecutionState> {
bool is_halted;
bool is_halted = false;
uint8_t requests;
uint8_t last_requests;
uint8_t temp8;
uint8_t operation;
uint16_t temp16;
unsigned int flag_adjustment_history;
uint16_t pc_increment;
uint16_t refresh_address;
uint8_t requests = 0;
uint8_t last_requests = 0;
uint8_t temp8 = 0;
uint8_t operation = 0;
uint16_t temp16 = 0;
unsigned int flag_adjustment_history = 0;
uint16_t pc_increment = 1;
uint16_t refresh_address = 0;
ReflectableEnum(Phase,
UntakenConditionalCall, Reset, IRQMode0, IRQMode1, IRQMode2,
NMI, FetchDecode, Operation
);
Phase phase;
int half_cycles_into_step;
int steps_into_phase;
Phase phase = Phase::FetchDecode;
int half_cycles_into_step = 0;
int steps_into_phase = 0;
uint16_t instruction_page = 0;
ExecutionState();

View File

@ -43,7 +43,7 @@ class FileHolder final {
Rewrite opens the file for rewriting; none of the original content is preserved; whatever
the caller outputs will replace the existing file.
@raises ErrorCantOpen if the file cannot be opened.
@throws ErrorCantOpen if the file cannot be opened.
*/
FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);

79
Storage/State/SNA.cpp Normal file
View File

@ -0,0 +1,79 @@
//
// SNA.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "SNA.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
using namespace Storage::State;
std::unique_ptr<Analyser::Static::Target> SNA::load(const std::string &file_name) {
// Make sure the file is accessible and appropriately sized.
FileHolder file(file_name);
if(file.stats().st_size != 48*1024 + 0x1b) {
return nullptr;
}
// SNAs are always for 48kb machines.
using Target = Analyser::Static::ZXSpectrum::Target;
auto result = std::make_unique<Target>();
result->model = Target::Model::FortyEightK;
// Prepare to populate ZX Spectrum state.
auto *const state = new Sinclair::ZXSpectrum::State();
result->state = std::unique_ptr<Reflection::Struct>(state);
// Comments below: [offset] [contents]
// 00 I
const uint8_t i = file.get8();
// 01 HL'; 03 DE'; 05 BC'; 07 AF'
state->z80.registers.hl_dash = file.get16le();
state->z80.registers.de_dash = file.get16le();
state->z80.registers.bc_dash = file.get16le();
state->z80.registers.af_dash = file.get16le();
// 09 HL; 0B DE; 0D BC; 0F IY; 11 IX
state->z80.registers.hl = file.get16le();
state->z80.registers.de = file.get16le();
state->z80.registers.bc = file.get16le();
state->z80.registers.iy = file.get16le();
state->z80.registers.ix = file.get16le();
// 13 IFF2 (in bit 2)
const uint8_t iff = file.get8();
state->z80.registers.iff1 = state->z80.registers.iff2 = iff & 4;
// 14 R
const uint8_t r = file.get8();
state->z80.registers.ir = uint16_t((i << 8) | r);
// 15 AF; 17 SP; 19 interrupt mode
state->z80.registers.flags = file.get8();
state->z80.registers.a = file.get8();
state->z80.registers.stack_pointer = file.get16le();
state->z80.registers.interrupt_mode = file.get8();
// 1A border colour
state->video.border_colour = file.get8();
// 1B 48kb RAM contents
state->ram = file.read(48*1024);
// To establish program counter, point it to a RET that
// I know is in the 16/48kb ROM. This avoids having to
// try to do a pop here, given that the true program counter
// might currently be in the ROM.
state->z80.registers.program_counter = 0x1d83;
return result;
}

24
Storage/State/SNA.hpp Normal file
View File

@ -0,0 +1,24 @@
//
// SNA.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_SNA_hpp
#define Storage_State_SNA_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct SNA {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_SNA_hpp */

211
Storage/State/Z80.cpp Normal file
View File

@ -0,0 +1,211 @@
//
// Z80.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Z80.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
using namespace Storage::State;
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;
while(cursor != size) {
const uint8_t next = file.get8();
// 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) {
result[cursor] = next;
++cursor;
continue;
}
// Grab the next byte. If it's not ED then write
// both and continue.
const uint8_t after = file.get8();
if(after != 0xed) {
result[cursor] = next;
result[cursor+1] = after;
cursor += 2;
continue;
}
// An ED ED has begun, so grab the RLE sequence.
const uint8_t count = file.get8();
const uint8_t value = file.get8();
memset(&result[cursor], value, count);
cursor += count;
}
return result;
}
}
std::unique_ptr<Analyser::Static::Target> Z80::load(const std::string &file_name) {
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:
//
// bit 2 = 1 => issue 2 emulation
// bit 3 = 1 => double interrupt frequency (?)
// bit 45 => video synchronisation (to do with emulation hackery?)
// bit 67 => 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();
const uint8_t model = file.get8();
switch(model) {
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;
}
state->last_7ffd = file.get8();
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;
}
}
state->last_fffd = file.get8();
file.read(state->ay.registers, 16);
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();
int time_since_interrupt;
switch(result->model) {
case Target::Model::SixteenK:
case Target::Model::FortyEightK:
time_since_interrupt = (17471 - low_t_state) + (high_t_state * 17472);
break;
default:
time_since_interrupt = (17726 - low_t_state) + (high_t_state * 17727);
break;
}
// TODO: map time_since_interrupt to time_into_frame, somehow.
// 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) {
state->last_1ffd = file.get8();
}
}
// Grab RAM.
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;
}
while(true) {
const uint16_t block_size = file.get16le();
const uint8_t page = file.get8();
const auto location = file.tell();
if(file.eof()) break;
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);
}
}
assert(location + block_size == file.tell());
file.seek(location + block_size, SEEK_SET);
}
return result;
}

24
Storage/State/Z80.hpp Normal file
View File

@ -0,0 +1,24 @@
//
// Z80.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_Z80_hpp
#define Storage_State_Z80_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct Z80 {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_Z80_hpp */