mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-26 09:29:45 +00:00
Merge pull request #315 from TomHarte/MSX
Introduces very provisional MSX 1 emulation
This commit is contained in:
commit
da57df55e8
@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
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.
|
||||
|
@ -76,8 +76,8 @@ template <class T> 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]);
|
||||
}
|
||||
|
||||
|
618
Components/9918/9918.cpp
Normal file
618
Components/9918/9918.cpp
Normal file
@ -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 <cassert>
|
||||
#include <cstring>
|
||||
|
||||
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<uint8_t *>(&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<Outputs::CRT::CRT> TMS9918::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void TMS9918::test_sprite(int sprite_number) {
|
||||
if(!(status_ & StatusFifthSprite)) {
|
||||
status_ = static_cast<uint8_t>((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<size_t>(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<size_t>(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<size_t>(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<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(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<unsigned int>(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<uint32_t *>(crt_->allocate_write_area(1));
|
||||
if(pixel_target_) *pixel_target_ = palette[background_colour_];
|
||||
crt_->output_level(static_cast<unsigned int>(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<uint16_t>((low_write_ & 0xf) << 10);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
colour_table_address_ = static_cast<uint16_t>(low_write_ << 6);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
sprite_generator_table_address_ = static_cast<uint16_t>((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<uint16_t>(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_;
|
||||
}
|
135
Components/9918/9918.hpp
Normal file
135
Components/9918/9918.hpp
Normal file
@ -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 <cstdint>
|
||||
|
||||
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<Outputs::CRT::CRT> 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<Outputs::CRT::CRT> 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 */
|
61
Machines/MSX/Keyboard.cpp
Normal file
61
Machines/MSX/Keyboard.cpp
Normal file
@ -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;
|
||||
}
|
42
Machines/MSX/Keyboard.hpp
Normal file
42
Machines/MSX/Keyboard.hpp
Normal file
@ -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 */
|
322
Machines/MSX/MSX.cpp
Normal file
322
Machines/MSX/MSX.cpp
Normal file
@ -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<Outputs::CRT::CRT> get_crt() override {
|
||||
return vdp_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> 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::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
*cycle.value = ay_->get_data_output();
|
||||
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(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::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0)));
|
||||
ay_->set_data_input(*cycle.value);
|
||||
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(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<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &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<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS9918> vdp_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> 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<uint8_t> rom_;
|
||||
std::vector<uint8_t> 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() {}
|
22
Machines/MSX/MSX.hpp
Normal file
22
Machines/MSX/MSX.hpp
Normal file
@ -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 */
|
@ -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::Machine::AmstradCPC());
|
||||
case StaticAnalyser::Target::Atari2600: return new TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());
|
||||
case StaticAnalyser::Target::Electron: return new TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());
|
||||
case StaticAnalyser::Target::MSX: return new TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());
|
||||
case StaticAnalyser::Target::Oric: return new TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric());
|
||||
case StaticAnalyser::Target::Vic20: return new TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());
|
||||
case StaticAnalyser::Target::ZX8081: return new TypedDynamicMachine<ZX8081::Machine>(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";
|
||||
|
@ -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 = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; };
|
||||
4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = "<group>"; };
|
||||
4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = "<group>"; };
|
||||
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
|
||||
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
|
||||
@ -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 = "<group>"; };
|
||||
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = "<group>"; };
|
||||
4B1BA0881FD4967700CB4ADA /* CSMSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMSX.h; sourceTree = "<group>"; };
|
||||
4B1BA0891FD4967800CB4ADA /* CSMSX.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMSX.mm; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
||||
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
||||
@ -820,6 +843,8 @@
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
|
||||
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = "<group>"; };
|
||||
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = MSX/MSX.cpp; sourceTree = "<group>"; };
|
||||
4B79A5001FC913C900EEDAD5 /* MSX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = MSX/MSX.hpp; sourceTree = "<group>"; };
|
||||
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
|
||||
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
|
||||
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
|
||||
@ -1343,6 +1368,24 @@
|
||||
path = ../../Outputs/CRT;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B0E04F01FC9E89100F43484 /* MSX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */,
|
||||
4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */,
|
||||
);
|
||||
name = MSX;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B0E04F71FC9F2C800F43484 /* 9918 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0E04F91FC9FA3100F43484 /* 9918.cpp */,
|
||||
4B0E04F81FC9FA3000F43484 /* 9918.hpp */,
|
||||
);
|
||||
name = 9918;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
4B79A4FC1FC8FF9800EEDAD5 /* MSX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */,
|
||||
4B79A5001FC913C900EEDAD5 /* MSX.hpp */,
|
||||
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */,
|
||||
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */,
|
||||
);
|
||||
name = MSX;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@ -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 = "<group>";
|
||||
};
|
||||
4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B1BA08C1FD498B000CB4ADA /* Base */,
|
||||
);
|
||||
name = MSXOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
48
OSBindings/Mac/Clock Signal/Base.lproj/MSXOptions.xib
Normal file
48
OSBindings/Mac/Clock Signal/Base.lproj/MSXOptions.xib
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
|
||||
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="175" y="30"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
@ -261,6 +261,20 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
@ -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];
|
||||
|
15
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.h
Normal file
15
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.h
Normal file
@ -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
|
25
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.mm
Normal file
25
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSMSX.mm
Normal file
@ -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::Machine> _msx;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
_msx = Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());
|
||||
return [super initWithMachine:&_msx];
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"MSX"; }
|
||||
|
||||
@end
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
7
ROMImages/MSX/readme.txt
Normal file
7
ROMImages/MSX/readme.txt
Normal file
@ -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.
|
@ -17,8 +17,8 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
|
||||
const std::list<Storage::Cartridge::Cartridge::Segment> &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;
|
||||
|
@ -189,7 +189,7 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &de
|
||||
|
||||
// try to figure out the paging scheme
|
||||
if(!media.cartridges.empty()) {
|
||||
const std::list<Storage::Cartridge::Cartridge::Segment> &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();
|
||||
|
@ -21,8 +21,8 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
|
||||
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
|
||||
const std::list<Storage::Cartridge::Cartridge::Segment> &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;
|
||||
|
73
StaticAnalyser/MSX/StaticAnalyser.cpp
Normal file
73
StaticAnalyser/MSX/StaticAnalyser.cpp
Normal file
@ -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<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
MSXCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> 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<uint16_t>(segment.data[2 + c] | segment.data[3 + c] << 8);
|
||||
if(code_pointer) {
|
||||
start_address = std::min(static_cast<uint16_t>(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<Target> &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);
|
||||
}
|
||||
}
|
22
StaticAnalyser/MSX/StaticAnalyser.hpp
Normal file
22
StaticAnalyser/MSX/StaticAnalyser.hpp
Normal file
@ -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<Target> &destination);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
|
@ -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<Storage::Disk::AcornADF>, 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<Storage::Disk::D64>, 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<Storage::Disk::SSD>, 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<Target> 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);
|
||||
|
||||
|
@ -59,6 +59,10 @@ struct Media {
|
||||
std::list<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
std::list<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||
|
||||
bool empty() const {
|
||||
return disks.empty() && tapes.empty() && cartridges.empty();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -70,6 +74,7 @@ struct Target {
|
||||
AmstradCPC,
|
||||
Atari2600,
|
||||
Electron,
|
||||
MSX,
|
||||
Oric,
|
||||
Vic20,
|
||||
ZX8081
|
||||
|
@ -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<size_t>(-1);
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
#ifndef Storage_Cartridge_hpp
|
||||
#define Storage_Cartridge_hpp
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
@ -29,19 +28,22 @@ namespace Cartridge {
|
||||
class Cartridge {
|
||||
public:
|
||||
struct Segment {
|
||||
Segment(int start_address, int end_address, std::vector<uint8_t> data) :
|
||||
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> data) :
|
||||
start_address(start_address), end_address(end_address), data(std::move(data)) {}
|
||||
|
||||
Segment(size_t start_address, std::vector<uint8_t> 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<uint8_t> data;
|
||||
};
|
||||
|
||||
const std::list<Segment> &get_segments() { return segments_; }
|
||||
const std::vector<Segment> &get_segments() const {
|
||||
return segments_;
|
||||
}
|
||||
virtual ~Cartridge() {}
|
||||
|
||||
Cartridge() {}
|
||||
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
|
||||
|
||||
protected:
|
||||
std::list<Segment> segments_;
|
||||
std::vector<Segment> segments_;
|
||||
};
|
||||
|
||||
}
|
||||
|
28
Storage/Tape/Formats/CAS.cpp
Normal file
28
Storage/Tape/Formats/CAS.cpp
Normal file
@ -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;
|
||||
}
|
47
Storage/Tape/Formats/CAS.hpp
Normal file
47
Storage/Tape/Formats/CAS.hpp
Normal file
@ -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 */
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user