diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index e2c2e2823..97160fe37 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -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));\ diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index e806ae042..259f7cca9 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -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; diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index f13a00810..cbd7e19b7 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -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]); + } + } +}; + } } diff --git a/Machines/AmstradCPC/Keyboard.cpp b/Machines/AmstradCPC/Keyboard.cpp index 98b67e8ae..7473f942f 100644 --- a/Machines/AmstradCPC/Keyboard.cpp +++ b/Machines/AmstradCPC/Keyboard.cpp @@ -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 { diff --git a/Machines/Commodore/Vic-20/Keyboard.cpp b/Machines/Commodore/Vic-20/Keyboard.cpp index 1b79bd414..62493d0e1 100644 --- a/Machines/Commodore/Vic-20/Keyboard.cpp +++ b/Machines/Commodore/Vic-20/Keyboard.cpp @@ -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); } diff --git a/Machines/Electron/Keyboard.cpp b/Machines/Electron/Keyboard.cpp index c554c2cbe..689e5707f 100644 --- a/Machines/Electron/Keyboard.cpp +++ b/Machines/Electron/Keyboard.cpp @@ -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 { diff --git a/Machines/MachineTypes.hpp b/Machines/MachineTypes.hpp index c96a02f10..f19e18d3a 100644 --- a/Machines/MachineTypes.hpp +++ b/Machines/MachineTypes.hpp @@ -20,6 +20,7 @@ #include "MediaTarget.hpp" #include "MouseMachine.hpp" #include "ScanProducer.hpp" +#include "StateProducer.hpp" #include "TimedMachine.hpp" #endif /* MachineTypes_h */ diff --git a/Machines/Oric/Keyboard.cpp b/Machines/Oric/Keyboard.cpp index 2e373ad57..3b12bc7e2 100644 --- a/Machines/Oric/Keyboard.cpp +++ b/Machines/Oric/Keyboard.cpp @@ -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); } diff --git a/Machines/Sinclair/Keyboard/Keyboard.cpp b/Machines/Sinclair/Keyboard/Keyboard.cpp index 1e9db2bf8..f0cd23b39 100644 --- a/Machines/Sinclair/Keyboard/Keyboard.cpp +++ b/Machines/Sinclair/Keyboard/Keyboard.cpp @@ -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); } } diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp new file mode 100644 index 000000000..69d23c5f5 --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -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 */ diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index c48392a61..4bb96d883 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -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; + } +}; + +} } } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 06812cdce..e3c242d81 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -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_; diff --git a/Machines/StateProducer.hpp b/Machines/StateProducer.hpp new file mode 100644 index 000000000..3a27e3cd0 --- /dev/null +++ b/Machines/StateProducer.hpp @@ -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 */ diff --git a/Machines/Utility/Typer.cpp b/Machines/Utility/Typer.cpp index 9706d85bf..1e290bef5 100644 --- a/Machines/Utility/Typer.cpp +++ b/Machines/Utility/Typer.cpp @@ -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]; -} - diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index 0e0a15b3a..79ac1b86a 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -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]; + } }; /*! diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 24c509446..7d908b13d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 5bb5dc8d1..b37475e53 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -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> diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index a78500ab7..65055ae4d 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -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 \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 6be9e9e28..2511a5291 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -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') diff --git a/Processors/Z80/Implementation/Z80Base.cpp b/Processors/Z80/Implementation/Z80Base.cpp index c1c10943a..1516220d5 100644 --- a/Processors/Z80/Implementation/Z80Base.cpp +++ b/Processors/Z80/Implementation/Z80Base.cpp @@ -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; diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 490e5f50f..56cf63fd9 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -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 diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index 613e9334a..b88bf2a84 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -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; diff --git a/Processors/Z80/State/State.cpp b/Processors/Z80/State/State.cpp index c9b90f2de..4f6f83fae 100644 --- a/Processors/Z80/State/State.cpp +++ b/Processors/Z80/State/State.cpp @@ -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); diff --git a/Processors/Z80/State/State.hpp b/Processors/Z80/State/State.hpp index f5676aa60..b074e38bf 100644 --- a/Processors/Z80/State/State.hpp +++ b/Processors/Z80/State/State.hpp @@ -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(); diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index a78fce034..dfcfb4d29 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -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); diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp new file mode 100644 index 000000000..5daa42444 --- /dev/null +++ b/Storage/State/SNA.cpp @@ -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; +} diff --git a/Storage/State/SNA.hpp b/Storage/State/SNA.hpp new file mode 100644 index 000000000..2f2811d93 --- /dev/null +++ b/Storage/State/SNA.hpp @@ -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 */ diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp new file mode 100644 index 000000000..4f5c9d390 --- /dev/null +++ b/Storage/State/Z80.cpp @@ -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 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(); + 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; +} diff --git a/Storage/State/Z80.hpp b/Storage/State/Z80.hpp new file mode 100644 index 000000000..ef5a9af1e --- /dev/null +++ b/Storage/State/Z80.hpp @@ -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 */