diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index a6f41b5f9..062ba9fa0 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt { return Cycles(length_ >> 1); } - ///Flushes the whole cycles in @c this, subtracting that many from the total stored here. + /// Flushes the whole cycles in @c this, subtracting that many from the total stored here. inline Cycles flush_cycles() { Cycles result(length_ >> 1); length_ &= 1; return result; } + /// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. + inline HalfCycles flush() { + HalfCycles result(length_); + length_ = 0; + return result; + } + /*! Severs from @c this the effect of dividing by @c divisor — @c this will end up with the value of @c this modulo @c divisor and @c divided by @c divisor is returned. diff --git a/Components/8255/i8255.hpp b/Components/8255/i8255.hpp index d5ec91de9..1a25e8196 100644 --- a/Components/8255/i8255.hpp +++ b/Components/8255/i8255.hpp @@ -76,8 +76,8 @@ template class i8255 { private: void update_outputs() { - port_handler_.set_value(0, outputs_[0]); - port_handler_.set_value(1, outputs_[1]); + if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]); + if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]); port_handler_.set_value(2, outputs_[2]); } diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp new file mode 100644 index 000000000..9bd7a0d2f --- /dev/null +++ b/Components/9918/9918.cpp @@ -0,0 +1,618 @@ +// +// 9918.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "9918.hpp" + +#include +#include + +using namespace TI; + +namespace { + +const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { + uint32_t result = 0; + uint8_t *const result_ptr = reinterpret_cast(&result); + result_ptr[0] = r; + result_ptr[1] = g; + result_ptr[2] = b; + result_ptr[3] = 0; + return result; +} + +const uint32_t palette[16] = { + palette_pack(0, 0, 0), + palette_pack(0, 0, 0), + palette_pack(33, 200, 66), + palette_pack(94, 220, 120), + + palette_pack(84, 85, 237), + palette_pack(125, 118, 252), + palette_pack(212, 82, 77), + palette_pack(66, 235, 245), + + palette_pack(252, 85, 84), + palette_pack(255, 121, 120), + palette_pack(212, 193, 84), + palette_pack(230, 206, 128), + + palette_pack(33, 176, 59), + palette_pack(201, 91, 186), + palette_pack(204, 204, 204), + palette_pack(255, 255, 255) +}; + +const uint8_t StatusInterrupt = 0x80; +const uint8_t StatusFifthSprite = 0x40; + +const int StatusSpriteCollisionShift = 5; +const uint8_t StatusSpriteCollision = 0x20; + +} + +TMS9918::TMS9918(Personality p) : + // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole + // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. + crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) { + crt_->set_rgb_sampling_function( + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "return texture(sampler, coordinate).rgb / vec3(255.0);" + "}"); + crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); + crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); +} + +std::shared_ptr TMS9918::get_crt() { + return crt_; +} + +void TMS9918::test_sprite(int sprite_number) { + if(!(status_ & StatusFifthSprite)) { + status_ = static_cast((status_ & ~31) | sprite_number); + } + if(sprites_stopped_) + return; + + const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)]; + // A sprite Y of 208 means "don't scan the list any further". + if(sprite_position == 208) { + sprites_stopped_ = true; + return; + } + + const int sprite_row = (row_ - sprite_position)&255; + if(sprite_row < 0 || sprite_row >= sprite_height_) return; + + const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot; + if(active_sprite_slot == 4) { + status_ |= StatusFifthSprite; + return; + } + + SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot]; + sprite.index = sprite_number; + sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); + sprite_sets_[active_sprite_set_].active_sprite_slot++; +} + +void TMS9918::get_sprite_contents(int field, int cycles_left, int screen_row) { + int sprite_id = field / 6; + field %= 6; + + while(true) { + const int cycles_in_sprite = std::min(cycles_left, 6 - field); + cycles_left -= cycles_in_sprite; + const int final_field = cycles_in_sprite + field; + + assert(sprite_id < 4); + SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id]; + + if(field < 4) { + std::memcpy( + &sprite.info[field], + &ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field], + static_cast(std::min(4, final_field) - field)); + } + + field = std::min(4, final_field); + const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0); + const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; + while(field < final_field) { + sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)]; + field++; + } + + if(!cycles_left) return; + field = 0; + sprite_id++; + } +} + +void TMS9918::run_for(const HalfCycles cycles) { + // As specific as I've been able to get: + // Scanline time is always 228 cycles. + // PAL output is 313 lines total. NTSC output is 262 lines total. + // Interrupt is signalled upon entering the lower border. + + // Keep a count of cycles separate from internal counts to avoid + // potential errors mapping back and forth. + half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2); + + // Convert to 342 cycles per line; the internal clock is 1.5 times the + // nominal 3.579545 Mhz that I've advertised for this part. + int int_cycles = (cycles.as_int() * 3) + cycles_error_; + cycles_error_ = int_cycles & 7; + int_cycles >>= 3; + if(!int_cycles) return; + + while(int_cycles) { + // Determine how much time has passed in the remainder of this line, and proceed. + int cycles_left = std::min(342 - column_, int_cycles); + column_ += cycles_left; // column_ is now the column that has been reached in this line. + int_cycles -= cycles_left; // Count down duration to run for. + + + + // ------------------------------ + // TODO: memory access slot here. + // ------------------------------ + + + + // ------------------------------ + // Perform video memory accesses. + // ------------------------------ + if(row_ < 192 && !blank_screen_) { + const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line. + switch(line_mode_) { + case LineMode::Text: + access_pointer_ = std::min(30, access_slot); + if(access_pointer_ >= 30 && access_pointer_ < 150) { + const int row_base = pattern_name_address_ + (row_ >> 3) * 40; + const int end = std::min(150, access_slot); + + // Pattern names are collected every third window starting from window 30. + const int pattern_names_start = (access_pointer_ - 30 + 2) / 3; + const int pattern_names_end = (end - 30 + 2) / 3; + std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast(pattern_names_end - pattern_names_start)); + + // Patterns are collected every third window starting from window 32. + const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3; + const int pattern_buffer_end = (end - 32 + 2) / 3; + for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { + pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)]; + } + } + break; + + case LineMode::Character: + // Four access windows: no collection. + if(access_pointer_ < 5) + access_pointer_ = std::min(5, access_slot); + + // Then ten access windows are filled with collection of sprite 3 and 4 details. + if(access_pointer_ >= 5 && access_pointer_ < 15) { + int end = std::min(15, access_slot); + get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, row_ - 1); + access_pointer_ = std::min(15, access_slot); + } + + // Four more access windows: no collection. + if(access_pointer_ >= 15 && access_pointer_ < 19) + access_pointer_ = std::min(19, access_slot); + + // Start new sprite set if this is location 19. + if(access_pointer_ == 19) { + active_sprite_set_ ^= 1; + sprite_sets_[active_sprite_set_].active_sprite_slot = 0; + sprites_stopped_ = false; + } + + // Then eight access windows fetch the y position for the first eight sprites. + while(access_pointer_ < 27 && access_pointer_ < access_slot) { + test_sprite(access_pointer_ - 19); + access_pointer_++; + } + + // The next 128 access slots are video and sprite collection interleaved. + if(access_pointer_ >= 27 && access_pointer_ < 155) { + int end = std::min(155, access_slot); + + int row_base = pattern_name_address_; + int pattern_base = pattern_generator_table_address_; + int colour_base = colour_table_address_; + if(screen_mode_ == 1) { + pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); + colour_base &= 0x2000 | ((row_ & 0xc0) << 5); + } + row_base += (row_ << 2)&~31; + + // Pattern names are collected every fourth window starting from window 27. + const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2; + const int pattern_names_end = (end - 27 + 3) >> 2; + std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast(pattern_names_end - pattern_names_start)); + + // Colours are collected ever fourth window starting from window 29. + const int colours_start = (access_pointer_ - 29 + 3) >> 2; + const int colours_end = (end - 29 + 3) >> 2; + if(screen_mode_ != 1) { + for(int column = colours_start; column < colours_end; ++column) { + colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)]; + } + } else { + for(int column = colours_start; column < colours_end; ++column) { + colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)]; + } + } + + // Patterns are collected ever fourth window starting from window 30. + const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; + const int pattern_buffer_end = (end - 30 + 3) >> 2; + for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { + pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)]; + } + + // Sprite slots occur in three quarters of ever fourth window starting from window 28. + const int sprite_start = (access_pointer_ - 28 + 3) >> 2; + const int sprite_end = (end - 28 + 3) >> 2; + for(int column = sprite_start; column < sprite_end; ++column) { + if(column&3) { + test_sprite(7 + column - (column >> 2)); + } + } + + access_pointer_ = std::min(155, access_slot); + } + + // Two access windows: no collection. + if(access_pointer_ < 157) + access_pointer_ = std::min(157, access_slot); + + // Fourteen access windows: collect initial sprite information. + if(access_pointer_ >= 157 && access_pointer_ < 171) { + int end = std::min(171, access_slot); + get_sprite_contents(access_pointer_ - 157, end - access_pointer_, row_); + access_pointer_ = std::min(171, access_slot); + } + break; + } + } + // -------------------------- + // End video memory accesses. + // -------------------------- + + + + // -------------------- + // Output video stream. + // -------------------- + if(row_ < 192 && !blank_screen_) { + // ---------------------- + // Output horizontal sync + // ---------------------- + if(!output_column_ && column_ >= 26) { + crt_->output_sync(13 * 4); + crt_->output_default_colour_burst(13 * 4); + output_column_ = 26; + } + + // ------------------- + // Output left border. + // ------------------- + if(output_column_ >= 26) { + int pixels_end = std::min(first_pixel_column_, column_); + if(output_column_ < pixels_end) { + output_border(pixels_end - output_column_); + output_column_ = pixels_end; + + // Grab a pointer for drawing pixels to, if the moment has arrived. + if(pixels_end == first_pixel_column_) { + pixel_base_ = pixel_target_ = reinterpret_cast(crt_->allocate_write_area(static_cast(first_right_border_column_ - first_pixel_column_))); + } + } + } + + // -------------- + // Output pixels. + // -------------- + if(output_column_ >= first_pixel_column_) { + int pixels_end = std::min(first_right_border_column_, column_); + + if(output_column_ < pixels_end) { + switch(line_mode_) { + case LineMode::Text: { + const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; + + const int shift = (output_column_ - first_pixel_column_) % 6; + int byte_column = (output_column_ - first_pixel_column_) / 6; + int pattern = pattern_buffer_[byte_column] << shift; + int pixels_left = pixels_end - output_column_; + int length = std::min(pixels_left, 6 - shift); + while(true) { + pixels_left -= length; + while(length--) { + *pixel_target_ = colours[(pattern >> 7)&0x01]; + pixel_target_++; + pattern <<= 1; + } + + if(!pixels_left) break; + length = std::min(6, pixels_left); + byte_column++; + pattern = pattern_buffer_[byte_column]; + } + output_column_ = pixels_end; + } break; + + case LineMode::Character: { + // If this is the start of the visible area, seed sprite shifter positions. + SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; + if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) { + int c = sprite_set.active_sprite_slot; + while(c--) { + SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; + sprite.shift_position = -sprite.info[1]; + if(sprite.info[3] & 0x80) { + sprite.shift_position += 32; + if(sprite.shift_position > 0 && !sprites_magnified_) + sprite.shift_position *= 2; + } + } + } + + // Paint the background tiles. + const int shift = (output_column_ - first_pixel_column_) & 7; + int byte_column = (output_column_ - first_pixel_column_) >> 3; + + const int pixels_left = pixels_end - output_column_; + int length = std::min(pixels_left, 8 - shift); + + int pattern = pattern_buffer_[byte_column] << shift; + uint8_t colour = colour_buffer_[byte_column]; + uint32_t colours[2] = { + palette[(colour & 15) ? (colour & 15) : background_colour_], + palette[(colour >> 4) ? (colour >> 4) : background_colour_] + }; + + int background_pixels_left = pixels_left; + while(true) { + background_pixels_left -= length; + while(length--) { + *pixel_target_ = colours[(pattern >> 7)&0x01]; + pixel_target_++; + pattern <<= 1; + } + + if(!background_pixels_left) break; + length = std::min(8, background_pixels_left); + byte_column++; + + pattern = pattern_buffer_[byte_column]; + colour = colour_buffer_[byte_column]; + colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; + colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; + } + + // Paint sprites and check for collisions. + if(sprite_set.active_sprite_slot) { + int sprite_pixels_left = pixels_left; + const int shift_advance = sprites_magnified_ ? 1 : 2; +// const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; + while(sprite_pixels_left--) { + uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; + int sprite_mask = 0; + int c = sprite_set.active_sprite_slot; + while(c--) { + SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; + + if(sprite.shift_position < 0) { + sprite.shift_position++; + continue; + } else if(sprite.shift_position < 32) { + int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); + mask = (mask >> 7) & 1; + status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; + sprite_mask |= mask; + sprite.shift_position += shift_advance; + + // TODO: can a non-conditional version be found like that commented out below, but + // which accounts for colour 0 being invisible? +// sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); + if((sprite.info[3]&15) && mask) { + sprite_colour = palette[sprite.info[3]&15]; + } + } + } + + pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; + output_column_++; + } + } + + output_column_ = pixels_end; + } break; + } + + if(output_column_ == first_right_border_column_) { + crt_->output_data(static_cast(first_right_border_column_ - first_pixel_column_) * 4, 4); + pixel_target_ = nullptr; + } + } + } + + // -------------------- + // Output right border. + // -------------------- + if(output_column_ >= first_right_border_column_) { + output_border(column_ - output_column_); + output_column_ = column_; + } + } else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) { + // Vertical sync. + if(column_ == 342) { + crt_->output_sync(342 * 4); + } + } else { + // Blank. + if(!output_column_ && column_ >= 26) { + crt_->output_sync(13 * 4); + crt_->output_default_colour_burst(13 * 4); + output_column_ = 26; + } + if(output_column_ >= 26) { + output_border(column_ - output_column_); + output_column_ = column_; + } + } + // ----------------- + // End video stream. + // ----------------- + + + + // ----------------------------------- + // Prepare for next line, potentially. + // ----------------------------------- + if(column_ == 342) { + access_pointer_ = column_ = output_column_ = 0; + row_ = (row_ + 1) % frame_lines_; + if(row_ == 192) status_ |= StatusInterrupt; + + screen_mode_ = next_screen_mode_; + blank_screen_ = next_blank_screen_; + switch(screen_mode_) { + case 2: + line_mode_ = LineMode::Text; + first_pixel_column_ = 69; + first_right_border_column_ = 309; + break; + default: + line_mode_ = LineMode::Character; + first_pixel_column_ = 63; + first_right_border_column_ = 319; + break; + } + } + } +} + +void TMS9918::output_border(int cycles) { + pixel_target_ = reinterpret_cast(crt_->allocate_write_area(1)); + if(pixel_target_) *pixel_target_ = palette[background_colour_]; + crt_->output_level(static_cast(cycles) * 4); +} + +// TODO: as a temporary development measure, memory access below is magically instantaneous. Correct that. + +void TMS9918::set_register(int address, uint8_t value) { + // Writes to address 0 are writes to the video RAM. Store + // the value and return. + if(!(address & 1)) { + write_phase_ = false; + read_ahead_buffer_ = value; + ram_[ram_pointer_ & 16383] = value; + ram_pointer_++; + return; + } + + // Writes to address 1 are performed in pairs; if this is the + // low byte of a value, store it and wait for the high byte. + if(!write_phase_) { + low_write_ = value; + write_phase_ = true; + return; + } + + write_phase_ = false; + if(value & 0x80) { + // This is a write to a register. + switch(value & 7) { + case 0: + next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); +// printf("NSM: %02x\n", next_screen_mode_); + break; + + case 1: + next_blank_screen_ = !(low_write_ & 0x40); + generate_interrupts_ = !!(low_write_ & 0x20); + next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3); + sprites_16x16_ = !!(low_write_ & 0x02); + sprites_magnified_ = !!(low_write_ & 0x01); + + sprite_height_ = 8; + if(sprites_16x16_) sprite_height_ <<= 1; + if(sprites_magnified_) sprite_height_ <<= 1; +// printf("NSM: %02x\n", next_screen_mode_); + break; + + case 2: + pattern_name_address_ = static_cast((low_write_ & 0xf) << 10); + break; + + case 3: + colour_table_address_ = static_cast(low_write_ << 6); + break; + + case 4: + pattern_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); + break; + + case 5: + sprite_attribute_table_address_ = static_cast((low_write_ & 0x7f) << 7); + break; + + case 6: + sprite_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); + break; + + case 7: + text_colour_ = low_write_ >> 4; + background_colour_ = low_write_ & 0xf; + break; + } + } else { + // This is a write to the RAM pointer. + ram_pointer_ = static_cast(low_write_ | (value << 8)); + if(!(value & 0x40)) { + // Officially a 'read' set, so perform lookahead. + read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; + ram_pointer_++; + } + } +} + +uint8_t TMS9918::get_register(int address) { + write_phase_ = false; + + // Reads from address 0 read video RAM, via the read-ahead buffer. + if(!(address & 1)) { + uint8_t result = read_ahead_buffer_; + read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; + ram_pointer_++; + return result; + } + + // Reads from address 1 get the status register. + uint8_t result = status_; + status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision); + return result; +} + + HalfCycles TMS9918::get_time_until_interrupt() { + if(!generate_interrupts_) return HalfCycles(-1); + if(get_interrupt_line()) return HalfCycles(0); + + const int half_cycles_per_frame = frame_lines_ * 228 * 2; + int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; + return HalfCycles(half_cycles_remaining); +} + +bool TMS9918::get_interrupt_line() { + return (status_ & StatusInterrupt) && generate_interrupts_; +} diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp new file mode 100644 index 000000000..d9e1e43b6 --- /dev/null +++ b/Components/9918/9918.hpp @@ -0,0 +1,135 @@ +// +// 9918.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef _918_hpp +#define _918_hpp + +#include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" + +#include + +namespace TI { + +class TMS9918 { + public: + enum Personality { + TMS9918A, // includes the 9928A; set TV standard as desired. + }; + + /*! + Constructs an instance of the drive controller that behaves according to personality @c p. + @param p The type of controller to emulate. + */ + TMS9918(Personality p); + + enum TVStandard { + PAL, NTSC + }; + void set_tv_standard(TVStandard standard); + + std::shared_ptr get_crt(); + + /*! + Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code + that the input clock rate is 3579545 Hz — the NTSC colour clock rate. + */ + void run_for(const HalfCycles cycles); + + void set_register(int address, uint8_t value); + uint8_t get_register(int address); + + /*! + Returns the amount of time until get_interrupt_line would next return true if + there are no interceding calls to set_register or get_register. + + If get_interrupt_line is true now, returns zero. If get_interrupt_line would + never return true, returns -1. + */ + HalfCycles get_time_until_interrupt(); + + /*! + @returns @c true if the interrupt line is currently active; @c false otherwise. + */ + bool get_interrupt_line(); + + private: + std::shared_ptr crt_; + + uint8_t ram_[16384]; + + uint16_t ram_pointer_ = 0; + uint8_t read_ahead_buffer_ = 0; + + uint8_t status_ = 0; + + bool write_phase_ = false; + uint8_t low_write_ = 0; + + // The various register flags. + int next_screen_mode_ = 0, screen_mode_ = 0; + bool next_blank_screen_ = true, blank_screen_ = true; + bool sprites_16x16_ = false; + bool sprites_magnified_ = false; + bool generate_interrupts_ = false; + int sprite_height_ = 8; + uint16_t pattern_name_address_ = 0; + uint16_t colour_table_address_ = 0; + uint16_t pattern_generator_table_address_ = 0; + uint16_t sprite_attribute_table_address_ = 0; + uint16_t sprite_generator_table_address_ = 0; + + uint8_t text_colour_ = 0; + uint8_t background_colour_ = 0; + + HalfCycles half_cycles_into_frame_; + int column_ = 0, row_ = 0, output_column_ = 0; + int cycles_error_ = 0; + uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr; + + void output_border(int cycles); + + // Vertical timing details. + int frame_lines_ = 262; + int first_vsync_line_ = 227; + + // Horizontal selections. + enum class LineMode { + Text, + Character + } line_mode_ = LineMode::Text; + int first_pixel_column_, first_right_border_column_; + + uint8_t pattern_names_[40]; + uint8_t pattern_buffer_[40]; + uint8_t colour_buffer_[40]; + + struct SpriteSet { + struct ActiveSprite { + int index = 0; + int row = 0; + + uint8_t info[4]; + uint8_t image[2]; + + int shift_position = 0; + } active_sprites[4]; + int active_sprite_slot = 0; + } sprite_sets_[2]; + int active_sprite_set_ = 0; + bool sprites_stopped_ = false; + + int access_pointer_ = 0; + + inline void test_sprite(int sprite_number); + inline void get_sprite_contents(int start, int cycles, int screen_row); +}; + +}; + +#endif /* _918_hpp */ diff --git a/Machines/MSX/Keyboard.cpp b/Machines/MSX/Keyboard.cpp new file mode 100644 index 000000000..438c7f9fe --- /dev/null +++ b/Machines/MSX/Keyboard.cpp @@ -0,0 +1,61 @@ +// +// Keyboard.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "Keyboard.hpp" + +uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { +#define BIND(source, dest) case Inputs::Keyboard::Key::source: return MSX::Key::dest + switch(key) { + BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4); + BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9); + BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT); + BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP); + BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG); + BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL); + BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV); + BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM); + + BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5); + + BIND(F12, KeyStop); + BIND(F10, KeyDelete); BIND(F9, KeyInsert); BIND(F8, KeyHome); + BIND(Delete, KeyDelete); BIND(Insert, KeyInsert); BIND(Home, KeyHome); + + BIND(Escape, KeyEscape); + BIND(Tab, KeyTab); BIND(CapsLock, KeyCaps); + + BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl); + BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift); + BIND(LeftMeta, KeyCode); BIND(RightMeta, KeyGraph); + BIND(LeftOption, KeyCode); BIND(RightOption, KeySelect); + + BIND(Semicolon, KeySemicolon); + BIND(Quote, KeyQuote); + BIND(OpenSquareBracket, KeyLeftSquareBracket); + BIND(CloseSquareBracket, KeyRightSquareBracket); + BIND(Hyphen, KeyMinus); + BIND(Equals, KeyEquals); + BIND(Left, KeyLeft); + BIND(Right, KeyRight); + BIND(Up, KeyUp); + BIND(Down, KeyDown); + BIND(FullStop, KeyFullStop); + BIND(Comma, KeyComma); + BIND(ForwardSlash, KeyForwardSlash); + BIND(BackSlash, KeyBackSlash); + BIND(BackTick, KeyGrave); + + BIND(Enter, KeyEnter); + BIND(Space, KeySpace); + BIND(BackSpace, KeyBackspace); + + default: break; + } +#undef BIND + return KeyboardMachine::Machine::KeyNotMapped; +} diff --git a/Machines/MSX/Keyboard.hpp b/Machines/MSX/Keyboard.hpp new file mode 100644 index 000000000..4cdfd1c26 --- /dev/null +++ b/Machines/MSX/Keyboard.hpp @@ -0,0 +1,42 @@ +// +// Keyboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Machines_MSX_Keyboard_hpp +#define Machines_MSX_Keyboard_hpp + +#include "../KeyboardMachine.hpp" + +namespace MSX { + +enum Key: uint16_t { +#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \ + k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\ + k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00, + + Line(0, Key7, Key6, Key5, Key4, Key3, Key2, Key1, Key0) + Line(1, KeySemicolon, KeyRightSquareBracket, KeyLeftSquareBracket, KeyBackSlash, KeyEquals, KeyMinus, Key9, Key8) + Line(2, KeyB, KeyA, KeyNA, KeyForwardSlash, KeyFullStop, KeyComma, KeyQuote, KeyGrave) + Line(3, KeyJ, KeyI, KeyH, KeyG, KeyF, KeyE, KeyD, KeyC) + Line(4, KeyR, KeyQ, KeyP, KeyO, KeyN, KeyM, KeyL, KeyK) + Line(5, KeyZ, KeyY, KeyX, KeyW, KeyV, KeyU, KeyT, KeyS) + Line(6, KeyF3, KeyF2, KeyF1, KeyCode, KeyCaps, KeyGraph, KeyControl, KeyShift) + Line(7, KeyEnter, KeySelect, KeyBackspace, KeyStop, KeyTab, KeyEscape, KeyF5, KeyF4) + Line(8, KeyRight, KeyDown, KeyUp, KeyLeft, KeyDelete, KeyInsert, KeyHome, KeySpace) + Line(9, KeyNumpad4, KeyNumpad3, KeyNumpad2, KeyNumpad1, KeyNumpad0, KeyNumpadDivide, KeyNumpadAdd, KeyNumpadMultiply) + Line(10, KeyNumpadDecimal, KeyNumpadComma, KeyNumpadSubtract, KeyNumpad9, KeyNumpad8, KeyNumpad7, KeyNumpad6, KeyNumpad5) + +#undef Line +}; + +struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper { + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key); +}; + +}; + +#endif /* Machines_MSX_Keyboard_hpp */ diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp new file mode 100644 index 000000000..42ef4440c --- /dev/null +++ b/Machines/MSX/MSX.cpp @@ -0,0 +1,322 @@ +// +// MSX.cpp +// Clock Signal +// +// Created by Thomas Harte on 24/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "MSX.hpp" + +#include "Keyboard.hpp" + +#include "../../Processors/Z80/Z80.hpp" + +#include "../../Components/1770/1770.hpp" +#include "../../Components/9918/9918.hpp" +#include "../../Components/8255/i8255.hpp" +#include "../../Components/AY38910/AY38910.hpp" + +#include "../CRTMachine.hpp" +#include "../ConfigurationTarget.hpp" +#include "../KeyboardMachine.hpp" + +namespace MSX { + +struct AYPortHandler: public GI::AY38910::PortHandler { + void set_port_output(bool port_b, uint8_t value) { +// printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value); + } + + uint8_t get_port_input(bool port_b) { +// printf("AY port %c input\n", port_b ? 'b' : 'a'); + return 0xff; + } +}; + +class ConcreteMachine: + public Machine, + public CPU::Z80::BusHandler, + public CRTMachine::Machine, + public ConfigurationTarget::Machine, + public KeyboardMachine::Machine { + public: + ConcreteMachine(): + z80_(*this), + i8255_(i8255_port_handler_), + i8255_port_handler_(*this) { + set_clock_rate(3579545); + std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); + clear_all_keys(); + } + + void setup_output(float aspect_ratio) override { + vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); + ay_.reset(new GI::AY38910::AY38910()); + ay_->set_port_handler(&ay_port_handler_); + ay_->set_input_rate(3579545.0f / 2.0f); + } + + void close_output() override { + vdp_.reset(); + ay_.reset(); + } + + std::shared_ptr get_crt() override { + return vdp_->get_crt(); + } + + std::shared_ptr get_speaker() override { + return ay_; + } + + void run_for(const Cycles cycles) override { + z80_.run_for(cycles); + } + + void configure_as_target(const StaticAnalyser::Target &target) override { + insert_media(target.media); + } + + bool insert_media(const StaticAnalyser::Media &media) override { + if(!media.cartridges.empty()) { + const auto &segment = media.cartridges.front()->get_segments().front(); + cartridge_ = segment.data; + + // TODO: should clear other page 1 pointers, should allow for paging cartridges, etc. + size_t base = segment.start_address >> 14; + for(size_t c = 0; c < cartridge_.size(); c += 16384) { + memory_slots_[1].read_pointers[(c >> 14) + base] = cartridge_.data() + c; + } + } + return true; + } + + void page_memory(uint8_t value) { +// printf("Page: %02x\n", value); + for(size_t c = 0; c < 4; ++c) { + read_pointers_[c] = memory_slots_[value & 3].read_pointers[c]; + write_pointers_[c] = memory_slots_[value & 3].write_pointers[c]; + value >>= 2; + } + } + + HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + if(time_until_interrupt_ > 0) { + time_until_interrupt_ -= cycle.length; + if(time_until_interrupt_ <= HalfCycles(0)) { + z80_.set_interrupt_line(true, time_until_interrupt_); + } + } + + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + case CPU::Z80::PartialMachineCycle::Read: + *cycle.value = read_pointers_[address >> 14][address & 16383]; + break; + + case CPU::Z80::PartialMachineCycle::Write: + write_pointers_[address >> 14][address & 16383] = *cycle.value; + break; + + case CPU::Z80::PartialMachineCycle::Input: + switch(address & 0xff) { + case 0x98: case 0x99: + vdp_->run_for(time_since_vdp_update_.flush()); + *cycle.value = vdp_->get_register(address); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + + case 0xa2: + ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); + ay_->set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value = ay_->get_data_output(); + ay_->set_control_lines(static_cast(0)); + break; + + case 0xa8: case 0xa9: + case 0xaa: case 0xab: + *cycle.value = i8255_.get_register(address); + break; + + default: + *cycle.value = 0xff; + break; + } + break; + + case CPU::Z80::PartialMachineCycle::Output: { + const int port = address & 0xff; + switch(port) { + case 0x98: case 0x99: + vdp_->run_for(time_since_vdp_update_.flush()); + vdp_->set_register(address, *cycle.value); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + + case 0xa0: case 0xa1: + ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); + ay_->set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); + ay_->set_data_input(*cycle.value); + ay_->set_control_lines(static_cast(0)); + break; + + case 0xa8: case 0xa9: + case 0xaa: case 0xab: + i8255_.set_register(address, *cycle.value); + break; + + case 0xfc: case 0xfd: case 0xfe: case 0xff: +// printf("RAM banking %02x: %02x\n", port, *cycle.value); + break; + } + } break; + + default: break; + } + + // Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, + // but otherwise runs without pause. + HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); + time_since_vdp_update_ += cycle.length + addition; + time_since_ay_update_ += cycle.length + addition; + return addition; + } + + void flush() { + vdp_->run_for(time_since_vdp_update_.flush()); + ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); + ay_->flush(); + } + + // Obtains the system ROMs. + bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &names)> &roms_with_names) override { + auto roms = roms_with_names( + "MSX", + { + "msx.rom" + }); + + if(!roms[0]) return false; + + rom_ = std::move(*roms[0]); + rom_.resize(32768); + + for(size_t c = 0; c < 4; ++c) { + for(size_t slot = 0; slot < 3; ++slot) { + memory_slots_[slot].read_pointers[c] = unpopulated_; + memory_slots_[slot].write_pointers[c] = scratch_; + } + + memory_slots_[3].read_pointers[c] = + memory_slots_[3].write_pointers[c] = &ram_[c * 16384]; + } + + memory_slots_[0].read_pointers[0] = rom_.data(); + memory_slots_[0].read_pointers[1] = &rom_[16384]; + + for(size_t c = 0; c < 4; ++c) { + read_pointers_[c] = memory_slots_[0].read_pointers[c]; + write_pointers_[c] = memory_slots_[0].write_pointers[c]; + } + + return true; + } + + void set_keyboard_line(int line) { + selected_key_line_ = line; + } + + uint8_t read_keyboard() { + return key_states_[selected_key_line_]; + } + + void clear_all_keys() override { + std::memset(key_states_, 0xff, sizeof(key_states_)); + } + + void set_key_state(uint16_t key, bool is_pressed) override { + int mask = 1 << (key & 7); + int line = key >> 4; + if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask; + } + + KeyboardMapper &get_keyboard_mapper() override { + return keyboard_mapper_; + } + + private: + class i8255PortHandler: public Intel::i8255::PortHandler { + public: + i8255PortHandler(ConcreteMachine &machine) : machine_(machine) {} + + void set_value(int port, uint8_t value) { + switch(port) { + case 0: machine_.page_memory(value); break; + case 2: + // TODO: + // b7 keyboard click + // b6 caps lock LED + // b5 audio output + // b4 cassette motor relay + machine_.set_keyboard_line(value & 0xf); + break; + default: printf("What what what what?\n"); break; + } + } + + uint8_t get_value(int port) { + if(port == 1) { + return machine_.read_keyboard(); + } else printf("What what?\n"); + return 0xff; + } + + private: + ConcreteMachine &machine_; + }; + + CPU::Z80::Processor z80_; + std::unique_ptr vdp_; + Intel::i8255::i8255 i8255_; + std::shared_ptr ay_; + + i8255PortHandler i8255_port_handler_; + AYPortHandler ay_port_handler_; + + uint8_t *read_pointers_[4]; + uint8_t *write_pointers_[4]; + + struct MemorySlots { + uint8_t *read_pointers[4]; + uint8_t *write_pointers[4]; + } memory_slots_[4]; + + uint8_t ram_[65536]; + uint8_t scratch_[16384]; + uint8_t unpopulated_[16384]; + std::vector rom_; + std::vector cartridge_; + + HalfCycles time_since_vdp_update_; + HalfCycles time_since_ay_update_; + HalfCycles time_until_interrupt_; + + uint8_t key_states_[16]; + int selected_key_line_ = 0; + + MSX::KeyboardMapper keyboard_mapper_; +}; + +} + +using namespace MSX; + +Machine *Machine::MSX() { + return new ConcreteMachine; +} + +Machine::~Machine() {} diff --git a/Machines/MSX/MSX.hpp b/Machines/MSX/MSX.hpp new file mode 100644 index 000000000..103d635f8 --- /dev/null +++ b/Machines/MSX/MSX.hpp @@ -0,0 +1,22 @@ +// +// MSX.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef MSX_hpp +#define MSX_hpp + +namespace MSX { + +class Machine { + public: + virtual ~Machine(); + static Machine *MSX(); +}; + +} + +#endif /* MSX_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 45833dbff..5783317a0 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -12,6 +12,7 @@ #include "../Atari2600/Atari2600.hpp" #include "../Commodore/Vic-20/Vic20.hpp" #include "../Electron/Electron.hpp" +#include "../MSX/MSX.hpp" #include "../Oric/Oric.hpp" #include "../ZX8081/ZX8081.hpp" @@ -22,6 +23,7 @@ case StaticAnalyser::Target::AmstradCPC: return new TypedDynamicMachine(AmstradCPC::Machine::AmstradCPC()); case StaticAnalyser::Target::Atari2600: return new TypedDynamicMachine(Atari2600::Machine::Atari2600()); case StaticAnalyser::Target::Electron: return new TypedDynamicMachine(Electron::Machine::Electron()); + case StaticAnalyser::Target::MSX: return new TypedDynamicMachine(MSX::Machine::MSX()); case StaticAnalyser::Target::Oric: return new TypedDynamicMachine(Oric::Machine::Oric()); case StaticAnalyser::Target::Vic20: return new TypedDynamicMachine(Commodore::Vic20::Machine::Vic20()); case StaticAnalyser::Target::ZX8081: return new TypedDynamicMachine(ZX8081::Machine::ZX8081(target)); @@ -35,6 +37,7 @@ std::string Machine::ShortNameForTargetMachine(const StaticAnalyser::Target::Mac case StaticAnalyser::Target::AmstradCPC: return "AmstradCPC"; case StaticAnalyser::Target::Atari2600: return "Atari2600"; case StaticAnalyser::Target::Electron: return "Electron"; + case StaticAnalyser::Target::MSX: return "MSX"; case StaticAnalyser::Target::Oric: return "Oric"; case StaticAnalyser::Target::Vic20: return "Vic20"; case StaticAnalyser::Target::ZX8081: return "ZX8081"; @@ -48,6 +51,7 @@ std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine ma case StaticAnalyser::Target::AmstradCPC: return "Amstrad CPC"; case StaticAnalyser::Target::Atari2600: return "Atari 2600"; case StaticAnalyser::Target::Electron: return "Acorn Electron"; + case StaticAnalyser::Target::MSX: return "MSX"; case StaticAnalyser::Target::Oric: return "Oric"; case StaticAnalyser::Target::Vic20: return "Vic 20"; case StaticAnalyser::Target::ZX8081: return "ZX80/81"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b3fb517bc..24d61fa11 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -128,8 +128,17 @@ 4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; }; 4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; }; 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; + 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; + 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; + 4B0E04EE1FC9E88300F43484 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */; }; + 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; + 4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */; }; + 4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; + 4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; 4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; + 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; + 4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; 4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */; }; @@ -138,6 +147,8 @@ 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; }; 4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */; }; 4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */; }; + 4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1BA0891FD4967800CB4ADA /* CSMSX.mm */; }; + 4B1BA08D1FD498B000CB4ADA /* MSXOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */; }; 4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; }; 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; 4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; }; @@ -221,6 +232,7 @@ 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; }; 4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; }; 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; + 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; 4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; 4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; }; 4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; }; @@ -629,8 +641,16 @@ 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; + 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = ""; }; + 4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = ""; }; + 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.cpp; sourceTree = ""; }; + 4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.hpp; sourceTree = ""; }; + 4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = ""; }; + 4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = ""; }; 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = ""; }; 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = ""; }; + 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = ""; }; + 4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; @@ -645,6 +665,9 @@ 4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; }; 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = ""; }; 4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = ""; }; + 4B1BA0881FD4967700CB4ADA /* CSMSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMSX.h; sourceTree = ""; }; + 4B1BA0891FD4967800CB4ADA /* CSMSX.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMSX.mm; sourceTree = ""; }; + 4B1BA08C1FD498B000CB4ADA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MSXOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = ""; }; 4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = ""; }; 4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = ""; }; @@ -820,6 +843,8 @@ 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = ""; }; 4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = ""; }; 4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = ""; }; + 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = MSX/MSX.cpp; sourceTree = ""; }; + 4B79A5001FC913C900EEDAD5 /* MSX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = MSX/MSX.hpp; sourceTree = ""; }; 4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = ""; }; 4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = ""; }; 4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = ""; }; @@ -1343,6 +1368,24 @@ path = ../../Outputs/CRT; sourceTree = ""; }; + 4B0E04F01FC9E89100F43484 /* MSX */ = { + isa = PBXGroup; + children = ( + 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */, + 4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */, + ); + name = MSX; + sourceTree = ""; + }; + 4B0E04F71FC9F2C800F43484 /* 9918 */ = { + isa = PBXGroup; + children = ( + 4B0E04F91FC9FA3100F43484 /* 9918.cpp */, + 4B0E04F81FC9FA3000F43484 /* 9918.hpp */, + ); + name = 9918; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -1454,6 +1497,7 @@ 4B38F34A1F2EC3CA00D9235D /* CSAmstradCPC.h */, 4B2A53991D117D36003C6002 /* CSAtari2600.h */, 4B2A539B1D117D36003C6002 /* CSElectron.h */, + 4B1BA0881FD4967700CB4ADA /* CSMSX.h */, 4BCF1FA61DADC5250039D2E7 /* CSOric.h */, 4B2A539D1D117D36003C6002 /* CSVic20.h */, 4B14978D1EE4B4D200CE2596 /* CSZX8081.h */, @@ -1461,6 +1505,7 @@ 4B38F34B1F2EC3CA00D9235D /* CSAmstradCPC.mm */, 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, 4B2A539C1D117D36003C6002 /* CSElectron.mm */, + 4B1BA0891FD4967800CB4ADA /* CSMSX.mm */, 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */, 4B2A539E1D117D36003C6002 /* CSVic20.mm */, 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */, @@ -1752,6 +1797,7 @@ 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */, 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, + 4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */, 4B2A332B1DB86821002876E3 /* OricOptions.xib */, 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */, 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */, @@ -1828,6 +1874,7 @@ 4B69FB411C4D941400B5F0AA /* Formats */ = { isa = PBXGroup; children = ( + 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */, 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */, 4B3BF5AE1F146264005B6C36 /* CSW.cpp */, 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */, @@ -1835,6 +1882,7 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */, 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */, 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */, + 4B0E04E91FC9E5DA00F43484 /* CAS.hpp */, 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, 4B3BF5AF1F146264005B6C36 /* CSW.hpp */, 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */, @@ -1895,6 +1943,17 @@ name = Z80; sourceTree = ""; }; + 4B79A4FC1FC8FF9800EEDAD5 /* MSX */ = { + isa = PBXGroup; + children = ( + 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */, + 4B79A5001FC913C900EEDAD5 /* MSX.hpp */, + 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */, + 4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */, + ); + name = MSX; + sourceTree = ""; + }; 4B8334881F5DB8470097E338 /* Implementation */ = { isa = PBXGroup; children = ( @@ -2376,15 +2435,16 @@ isa = PBXGroup; children = ( 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, - 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */, 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, 4B7041271F92C26900735E45 /* JoystickMachine.hpp */, 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */, + 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */, 4B38F3491F2EC12000D9235D /* AmstradCPC */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4B79A4FC1FC8FF9800EEDAD5 /* MSX */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, 4B2B3A461F9B8FA70062DABF /* Utility */, 4B1497931EE4B5AC00CE2596 /* ZX8081 */, @@ -2482,6 +2542,7 @@ 4BE845221F2FF7F400A5EA22 /* 6845 */, 4BD9137C1F3115AC009BCF85 /* 8255 */, 4BBC951F1F368D87008F4C34 /* 8272 */, + 4B0E04F71FC9F2C800F43484 /* 9918 */, ); name = Components; path = ../../Components; @@ -2660,6 +2721,7 @@ 4BCF1FAC1DADD41F0039D2E7 /* Oric */, 4B14978C1EE4AC6200CE2596 /* ZX80/81 */, 4B38F3451F2EB41800D9235D /* AmstradCPC */, + 4B0E04F01FC9E89100F43484 /* MSX */, ); name = StaticAnalyser; sourceTree = ""; @@ -2821,6 +2883,7 @@ 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, 4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, + 4B1BA08D1FD498B000CB4ADA /* MSXOptions.xib in Resources */, 4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, @@ -3130,12 +3193,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */, 4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */, 4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */, 4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */, 4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */, 4B055AE61FAE9B6F0060FFFF /* OutputShader.cpp in Sources */, 4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */, + 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */, 4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */, 4B055A811FAE853A0060FFFF /* Disk.cpp in Sources */, 4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */, @@ -3157,6 +3222,7 @@ 4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */, 4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */, 4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */, + 4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */, 4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */, 4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */, 4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */, @@ -3222,6 +3288,7 @@ 4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */, 4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */, 4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, + 4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */, 4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */, 4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */, 4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */, @@ -3234,6 +3301,7 @@ 4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */, 4B055AE71FAE9B6F0060FFFF /* Shader.cpp in Sources */, 4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */, + 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */, 4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, @@ -3254,6 +3322,7 @@ 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */, + 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, @@ -3296,6 +3365,7 @@ 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */, 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */, + 4B0E04EE1FC9E88300F43484 /* StaticAnalyser.cpp in Sources */, 4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */, 4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, @@ -3303,6 +3373,7 @@ 4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, + 4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */, 4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, @@ -3316,6 +3387,7 @@ 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */, + 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, @@ -3331,6 +3403,7 @@ 4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */, + 4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, 4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */, @@ -3369,6 +3442,7 @@ 4BFE7B871FC39BF100160B38 /* StandardOptions.cpp in Sources */, 4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */, 4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */, + 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */, 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, @@ -3457,6 +3531,14 @@ name = ZX8081Options.xib; sourceTree = ""; }; + 4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B1BA08C1FD498B000CB4ADA /* Base */, + ); + name = MSXOptions.xib; + sourceTree = ""; + }; 4B2A332B1DB86821002876E3 /* OricOptions.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/MSXOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/MSXOptions.xib new file mode 100644 index 000000000..39bd7024b --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/MSXOptions.xib @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index ca23f90e0..ee2011d8a 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -261,6 +261,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument + + CFBundleTypeExtensions + + cas + + CFBundleTypeIconFile + cassette + CFBundleTypeName + MSX Tape Image + CFBundleTypeRole + Viewer + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index ef3c89782..4395921b7 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -17,6 +17,7 @@ #import "CSAmstradCPC.h" #import "CSAtari2600.h" #import "CSElectron.h" +#import "CSMSX.h" #import "CSOric.h" #import "CSVic20.h" #import "CSZX8081+Instantiation.h" @@ -45,6 +46,7 @@ case StaticAnalyser::Target::AmstradCPC: return nil; case StaticAnalyser::Target::Atari2600: return @"Atari2600Options"; case StaticAnalyser::Target::Electron: return @"ElectronOptions"; + case StaticAnalyser::Target::MSX: return @"MSXOptions"; case StaticAnalyser::Target::Oric: return @"OricOptions"; case StaticAnalyser::Target::Vic20: return @"Vic20Options"; case StaticAnalyser::Target::ZX8081: return @"ZX8081Options"; @@ -57,6 +59,7 @@ case StaticAnalyser::Target::AmstradCPC: return [[CSAmstradCPC alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; + case StaticAnalyser::Target::MSX: return [[CSMSX alloc] init]; case StaticAnalyser::Target::Oric: return [[CSOric alloc] init]; case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] initWithIntendedTarget:_target]; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.h new file mode 100644 index 000000000..b082e92dd --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.h @@ -0,0 +1,15 @@ +// +// CSMSX.h +// Clock Signal +// +// Created by Thomas Harte on 03/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" + +@interface CSMSX : CSMachine + +- (instancetype)init; + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.mm new file mode 100644 index 000000000..aac38a6fb --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.mm @@ -0,0 +1,25 @@ +// +// CSMSX.m +// Clock Signal +// +// Created by Thomas Harte on 03/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#import "CSMSX.h" + +#include "MSX.hpp" +#include "TypedDynamicMachine.hpp" + +@implementation CSMSX { + Machine::TypedDynamicMachine _msx; +} + +- (instancetype)init { + _msx = Machine::TypedDynamicMachine(MSX::Machine::MSX()); + return [super initWithMachine:&_msx]; +} + +- (NSString *)userDefaultsPrefix { return @"MSX"; } + +@end diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 35786d80a..d85bc85e1 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -14,6 +14,7 @@ SOURCES += glob.glob('../../Components/1770/*.cpp') SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp') SOURCES += glob.glob('../../Components/6560/*.cpp') SOURCES += glob.glob('../../Components/8272/*.cpp') +SOURCES += glob.glob('../../Components/9918/*.cpp') SOURCES += glob.glob('../../Components/AY38910/*.cpp') SOURCES += glob.glob('../../Concurrency/*.cpp') @@ -29,6 +30,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp') SOURCES += glob.glob('../../Machines/Electron/*.cpp') +SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') SOURCES += glob.glob('../../Machines/ZX8081/*.cpp') @@ -48,6 +50,7 @@ SOURCES += glob.glob('../../StaticAnalyser/AmstradCPC/*.cpp') SOURCES += glob.glob('../../StaticAnalyser/Atari/*.cpp') SOURCES += glob.glob('../../StaticAnalyser/Commodore/*.cpp') SOURCES += glob.glob('../../StaticAnalyser/Disassembler/*.cpp') +SOURCES += glob.glob('../../StaticAnalyser/MSX/*.cpp') SOURCES += glob.glob('../../StaticAnalyser/Oric/*.cpp') SOURCES += glob.glob('../../StaticAnalyser/ZX8081/*.cpp') diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index b45cead2e..9dd45179a 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -120,7 +120,7 @@ class ProcessorStorage { RegisterPair afDash_, bcDash_, deDash_, hlDash_; RegisterPair ix_, iy_, pc_, sp_; RegisterPair ir_, refresh_addr_; - bool iff1_, iff2_; + bool iff1_ = false, iff2_ = false; int interrupt_mode_ = 0; uint16_t pc_increment_ = 1; uint8_t sign_result_; // the sign flag is set if the value in sign_result_ is negative diff --git a/ROMImages/MSX/readme.txt b/ROMImages/MSX/readme.txt new file mode 100644 index 000000000..d5b4a4325 --- /dev/null +++ b/ROMImages/MSX/readme.txt @@ -0,0 +1,7 @@ +ROMs for the MSX go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +msx.rom + +These names match those offered for download at http://fms.komkon.org/fMSX/ (albeit in lowercase), and the emulator has been tested against those images. \ No newline at end of file diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index cbebb3fe0..8e0cda62d 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -17,8 +17,8 @@ static std::list> AcornCartridgesFrom(const std::list> &cartridges) { std::list> acorn_cartridges; - for(std::shared_ptr cartridge : cartridges) { - const std::list &segments = cartridge->get_segments(); + for(const auto &cartridge : cartridges) { + const auto &segments = cartridge->get_segments(); // only one mapped item is allowed if(segments.size() != 1) continue; diff --git a/StaticAnalyser/Atari/StaticAnalyser.cpp b/StaticAnalyser/Atari/StaticAnalyser.cpp index 17fa62bb5..a0b8d8170 100644 --- a/StaticAnalyser/Atari/StaticAnalyser.cpp +++ b/StaticAnalyser/Atari/StaticAnalyser.cpp @@ -189,7 +189,7 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list &de // try to figure out the paging scheme if(!media.cartridges.empty()) { - const std::list &segments = media.cartridges.front()->get_segments(); + const auto &segments = media.cartridges.front()->get_segments(); if(segments.size() == 1) { const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); diff --git a/StaticAnalyser/Commodore/StaticAnalyser.cpp b/StaticAnalyser/Commodore/StaticAnalyser.cpp index 2be425235..32fdd2099 100644 --- a/StaticAnalyser/Commodore/StaticAnalyser.cpp +++ b/StaticAnalyser/Commodore/StaticAnalyser.cpp @@ -21,8 +21,8 @@ static std::list> Vic20CartridgesFrom(const std::list> &cartridges) { std::list> vic20_cartridges; - for(std::shared_ptr cartridge : cartridges) { - const std::list &segments = cartridge->get_segments(); + for(const auto &cartridge : cartridges) { + const auto &segments = cartridge->get_segments(); // only one mapped item is allowed if(segments.size() != 1) continue; diff --git a/StaticAnalyser/MSX/StaticAnalyser.cpp b/StaticAnalyser/MSX/StaticAnalyser.cpp new file mode 100644 index 000000000..c7bed821e --- /dev/null +++ b/StaticAnalyser/MSX/StaticAnalyser.cpp @@ -0,0 +1,73 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +/* +DEFB "AB" ; expansion ROM header +DEFW initcode ; start of the init code, 0 if no initcode +DEFW callstat; pointer to CALL statement handler, 0 if no such handler +DEFW device; pointer to expansion device handler, 0 if no such handler +DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram +DEFS 6,0 ; room reserved for future extensions +*/ +static std::list> + MSXCartridgesFrom(const std::list> &cartridges) { + std::list> msx_cartridges; + + for(const auto &cartridge : cartridges) { + const auto &segments = cartridge->get_segments(); + + // Only one mapped item is allowed. + if(segments.size() != 1) continue; + + // Which must be a multiple of 16 kb in size. + Storage::Cartridge::Cartridge::Segment segment = segments.front(); + const size_t data_size = segment.data.size(); + if(data_size < 0x4000 || data_size & 0x3fff) continue; + + // Check for a ROM header at address 0; TODO: if it's not found then try 0x4000 + // and consider swapping the image. + + // Check for the expansion ROM header and the reserved bytes. + if(segment.data[0] != 0x41 || segment.data[1] != 0x42) continue; + bool all_zeroes = true; + for(size_t c = 0; c < 6; ++c) { + if(segment.data[10 + c] != 0) all_zeroes = false; + } + if(!all_zeroes) continue; + + // Pick a paging address based on the four pointers. + uint16_t start_address = 0xc000; + for(size_t c = 0; c < 8; c += 2) { + uint16_t code_pointer = static_cast(segment.data[2 + c] | segment.data[3 + c] << 8); + if(code_pointer) { + start_address = std::min(static_cast(code_pointer &~ 0x3fff), start_address); + } + } + + // That'll do then, but apply the detected start address. + msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge({ + Storage::Cartridge::Cartridge::Segment(start_address, segment.data) + })); + } + + return msx_cartridges; +} + +void StaticAnalyser::MSX::AddTargets(const Media &media, std::list &destination) { + Target target; + + target.media.cartridges = MSXCartridgesFrom(media.cartridges); + + if(!target.media.empty()) { + target.machine = Target::MSX; + target.probability = 1.0; + destination.push_back(target); + } +} diff --git a/StaticAnalyser/MSX/StaticAnalyser.hpp b/StaticAnalyser/MSX/StaticAnalyser.hpp new file mode 100644 index 000000000..354fd2d7c --- /dev/null +++ b/StaticAnalyser/MSX/StaticAnalyser.hpp @@ -0,0 +1,22 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp +#define StaticAnalyser_MSX_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" + +namespace StaticAnalyser { +namespace MSX { + +void AddTargets(const Media &media, std::list &destination); + +} +} + +#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */ diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 36e2c082c..b828e7c66 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -16,6 +16,7 @@ #include "AmstradCPC/StaticAnalyser.hpp" #include "Atari/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" +#include "MSX/StaticAnalyser.hpp" #include "Oric/StaticAnalyser.hpp" #include "ZX8081/StaticAnalyser.hpp" @@ -33,6 +34,7 @@ #include "../Storage/Disk/DiskImage/Formats/SSD.hpp" // Tapes +#include "../Storage/Tape/Formats/CAS.hpp" #include "../Storage/Tape/Formats/CommodoreTAP.hpp" #include "../Storage/Tape/Formats/CSW.hpp" #include "../Storage/Tape/Formats/OricTAP.hpp" @@ -83,6 +85,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 Format("adf", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADF Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN + Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("d64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // D64 @@ -107,7 +110,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType } } - Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM + Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX) // ROM Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) @@ -145,6 +148,7 @@ std::list StaticAnalyser::GetTargets(const char *file_name) { if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets); + if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets); if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms); diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 943c67089..84f92c0b8 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -59,6 +59,10 @@ struct Media { std::list> disks; std::list> tapes; std::list> cartridges; + + bool empty() const { + return disks.empty() && tapes.empty() && cartridges.empty(); + } }; /*! @@ -70,6 +74,7 @@ struct Target { AmstradCPC, Atari2600, Electron, + MSX, Oric, Vic20, ZX8081 diff --git a/Storage/Cartridge/Cartridge.cpp b/Storage/Cartridge/Cartridge.cpp index d197548e1..10582f6cd 100644 --- a/Storage/Cartridge/Cartridge.cpp +++ b/Storage/Cartridge/Cartridge.cpp @@ -8,4 +8,5 @@ #include "Cartridge.hpp" -const int Storage::Cartridge::Cartridge::Segment::UnknownAddress = -1; +const size_t Storage::Cartridge::Cartridge::Segment::UnknownAddress = static_cast(-1); + diff --git a/Storage/Cartridge/Cartridge.hpp b/Storage/Cartridge/Cartridge.hpp index 37ce806d2..e5dc14c31 100644 --- a/Storage/Cartridge/Cartridge.hpp +++ b/Storage/Cartridge/Cartridge.hpp @@ -9,7 +9,6 @@ #ifndef Storage_Cartridge_hpp #define Storage_Cartridge_hpp -#include #include #include @@ -29,19 +28,22 @@ namespace Cartridge { class Cartridge { public: struct Segment { - Segment(int start_address, int end_address, std::vector data) : + Segment(size_t start_address, size_t end_address, std::vector data) : start_address(start_address), end_address(end_address), data(std::move(data)) {} + Segment(size_t start_address, std::vector data) : + Segment(start_address, start_address + data.size(), data) {} + /// Indicates that an address is unknown. - static const int UnknownAddress; + static const size_t UnknownAddress; /// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress. - int start_address; + size_t start_address; /*! The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal to start_address + data_length due to potential paging. */ - int end_address; + size_t end_address; /*! The data contents for this segment. If @c start_address and @c end_address are suppled then @@ -51,11 +53,16 @@ class Cartridge { std::vector data; }; - const std::list &get_segments() { return segments_; } + const std::vector &get_segments() const { + return segments_; + } virtual ~Cartridge() {} + Cartridge() {} + Cartridge(const std::vector &segments) : segments_(segments) {} + protected: - std::list segments_; + std::vector segments_; }; } diff --git a/Storage/Tape/Formats/CAS.cpp b/Storage/Tape/Formats/CAS.cpp new file mode 100644 index 000000000..edce11dd2 --- /dev/null +++ b/Storage/Tape/Formats/CAS.cpp @@ -0,0 +1,28 @@ +// +// CAS.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "CAS.hpp" + +using namespace Storage::Tape; + +CAS::CAS(const char *file_name) : + file_(file_name) { +} + +bool CAS::is_at_end() { + return true; +} + +void CAS::virtual_reset() { + +} + +Tape::Pulse CAS::virtual_get_next_pulse() { + Pulse empty_pulse; + return empty_pulse; +} diff --git a/Storage/Tape/Formats/CAS.hpp b/Storage/Tape/Formats/CAS.hpp new file mode 100644 index 000000000..4b998e2b9 --- /dev/null +++ b/Storage/Tape/Formats/CAS.hpp @@ -0,0 +1,47 @@ +// +// CAS.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef CAS_hpp +#define CAS_hpp + +#include "../Tape.hpp" +#include "../../FileHolder.hpp" + +namespace Storage { +namespace Tape { + +/*! + Provides a @c Tape containing a CAS tape image, which is an MSX byte stream. +*/ +class CAS: public Tape { + public: + /*! + Constructs a @c CAS containing content from the file with name @c file_name. + + @throws ErrorNotCAS if this file could not be opened and recognised as a valid CAS file. + */ + CAS(const char *file_name); + + enum { + ErrorNotCAS + }; + + // implemented to satisfy @c Tape + bool is_at_end(); + + private: + Storage::FileHolder file_; + + void virtual_reset(); + Pulse virtual_get_next_pulse(); +}; + +} +} + +#endif /* CAS_hpp */ diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index e05759d2c..560abf0be 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -21,14 +21,15 @@ enum Type: IntType { BBCModelA = 1 << 6, BBCModelB = 1 << 7, Commodore = 1 << 8, - Oric = 1 << 9, - ZX80 = 1 << 10, - ZX81 = 1 << 11, + MSX = 1 << 9, + Oric = 1 << 10, + ZX80 = 1 << 11, + ZX81 = 1 << 12, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB, ZX8081 = ZX80 | ZX81, - AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81, - AllDisk = Acorn | AmstradCPC | Commodore | Oric, + AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX, + AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX, }; class TypeDistinguisher {