1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-11 08:30:55 +00:00

Merge pull request #1250 from TomHarte/CGATarget

Add CGA emulation.
This commit is contained in:
Thomas Harte 2023-12-06 16:23:10 -05:00 committed by GitHub
commit 327dc51ece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 870 additions and 345 deletions

View File

@ -18,7 +18,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
ReflectableEnum(VideoAdaptor,
MDA,
CGA);
VideoAdaptor adaptor = VideoAdaptor::MDA;
VideoAdaptor adaptor = VideoAdaptor::CGA;
Target() : Analyser::Static::Target(Machine::PCCompatible) {
if(needs_declare()) {

View File

@ -159,7 +159,7 @@ void imul(
FI;
*/
using sIntT = typename std::make_signed<IntT>::type;
destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT));
destination_high = IntT((sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)));
destination_low = IntT(sIntT(destination_low) * sIntT(source));
const auto sign_extension = (destination_low & Numeric::top_bit<IntT>()) ? IntT(~0) : 0;
@ -216,7 +216,7 @@ void div(
}
// TEMPORARY HACK. Will not work with DWords.
const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low;
const uint32_t dividend = uint32_t((destination_high << (8 * sizeof(IntT))) + destination_low);
const auto result = dividend / source;
if(IntT(result) != result) {
interrupt(Interrupt::DivideError, context);
@ -293,7 +293,7 @@ void idiv(
}
destination_low = IntT(result);
destination_high = dividend % sIntT(source);
destination_high = IntT(dividend % sIntT(source));
}
template <typename IntT, typename ContextT>

View File

@ -54,15 +54,16 @@ void rcl(
case 0: break;
case Numeric::bit_size<IntT>(): {
const IntT temp_carry = destination & 1;
destination = (destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1));
destination = IntT((destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1)));
carry = temp_carry;
} break;
default: {
const IntT temp_carry = destination & (Numeric::top_bit<IntT>() >> (temp_count - 1));
destination =
destination = IntT(
(destination << temp_count) |
(destination >> (Numeric::bit_size<IntT>() + 1 - temp_count)) |
(carry << (temp_count - 1));
(carry << (temp_count - 1))
);
carry = temp_carry ? 1 : 0;
} break;
}
@ -103,15 +104,16 @@ void rcr(
case 0: break;
case Numeric::bit_size<IntT>(): {
const IntT temp_carry = destination & Numeric::top_bit<IntT>();
destination = (destination << 1) | carry;
destination = IntT((destination << 1) | carry);
carry = temp_carry;
} break;
default: {
const IntT temp_carry = destination & (1 << (temp_count - 1));
destination =
destination = IntT(
(destination >> temp_count) |
(destination << (Numeric::bit_size<IntT>() + 1 - temp_count)) |
(carry << (Numeric::bit_size<IntT>() - temp_count));
(carry << (Numeric::bit_size<IntT>() - temp_count))
);
carry = temp_carry;
} break;
}
@ -159,9 +161,10 @@ void rol(
return;
}
if(temp_count) {
destination =
destination = IntT(
(destination << temp_count) |
(destination >> (Numeric::bit_size<IntT>() - temp_count));
(destination >> (Numeric::bit_size<IntT>() - temp_count))
);
}
context.flags.template set_from<Flag::Carry>(destination & 1);
@ -210,9 +213,10 @@ void ror(
return;
}
if(temp_count) {
destination =
destination = IntT(
(destination >> temp_count) |
(destination << (Numeric::bit_size<IntT>() - temp_count));
(destination << (Numeric::bit_size<IntT>() - temp_count))
);
}
context.flags.template set_from<Flag::Carry>(destination & Numeric::top_bit<IntT>());
@ -298,9 +302,9 @@ void sal(
context.flags.template set_from<Flag::Carry>(
destination & mask
);
context.flags.template set_from<Flag::Overflow>(
context.flags.template set_from<Flag::Overflow>(IntT(
(destination ^ (destination << 1)) & mask
);
));
destination <<= count;
}
break;
@ -323,7 +327,7 @@ void sar(
destination = sign ? IntT(~0) : IntT(0);
context.flags.template set_from<Flag::Carry>(sign);
} else {
const IntT mask = 1 << (count - 1);
const auto mask = IntT(1 << (count - 1));
context.flags.template set_from<Flag::Carry>(destination & mask);
destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0);
}
@ -349,7 +353,7 @@ void shr(
context.flags.template set_from<Flag::Carry>(0);
destination = 0;
} else {
const IntT mask = 1 << (count - 1);
const auto mask = IntT(1 << (count - 1));
context.flags.template set_from<Flag::Carry>(destination & mask);
destination >>= count;
}

View File

@ -0,0 +1,377 @@
//
// CGA.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/12/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef CGA_h
#define CGA_h
#include "../../Components/6845/CRTC6845.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Machines/Utility/ROMCatalogue.hpp"
namespace PCCompatible {
class CGA {
public:
CGA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
static constexpr uint32_t BaseAddress = 0xb'8000;
static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont;
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
outputter_.ram = ram;
outputter_.font = font;
}
void run_for(Cycles cycles) {
// Input rate is the PIT rate of 1,193,182 Hz.
// CGA is clocked at the real oscillator rate of 14 times that.
// But there's also an internal divide by 8 to align to the fetch clock.
full_clock_ += 7 * cycles.as<int>();
const int modulo = 4 * outputter_.clock_divider;
crtc_.run_for(Cycles(full_clock_ / modulo));
full_clock_ %= modulo;
}
template <int address>
void write(uint8_t value) {
switch(address) {
case 0: case 2: case 4: case 6:
crtc_.select_register(value);
break;
case 1: case 3: case 5: case 7:
crtc_.set_register(value);
break;
case 0x8: outputter_.set_mode(value); break;
case 0x9: outputter_.set_colours(value); break;
}
}
template <int address>
uint8_t read() {
switch(address) {
case 1: case 3: case 5: case 7:
return crtc_.get_register();
case 0xa:
return
// b3: 1 => in vsync; 0 => not;
// b2: 1 => light pen switch is off;
// b1: 1 => positive edge from light pen has set trigger;
// b0: 1 => safe to write to VRAM now without causing snow.
(crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) |
(crtc_.get_bus_state().hsync ? 0b0001 : 0b0000) |
0b0100;
default: return 0xff;
}
}
// MARK: - Display type configuration.
void set_display_type(Outputs::Display::DisplayType display_type) {
outputter_.crt.set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const {
return outputter_.crt.get_display_type();
}
// MARK: - Call-ins for ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
outputter_.crt.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return outputter_.crt.get_scaled_scan_status() * 4.0f / (7.0f * 8.0f);
}
private:
struct CRTCOutputter {
enum class OutputState {
Sync, Pixels, Border, ColourBurst
};
CRTCOutputter() :
crt(910, 8, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red2Green2Blue2)
{
crt.set_visible_area(Outputs::Display::Rect(0.097f, 0.095f, 0.82f, 0.82f));
// crt.set_display_type(Outputs::Display::DisplayType::CompositeColour); // TODO: needs to be a user option.
crt.set_display_type(Outputs::Display::DisplayType::RGB); // TODO: needs to be a user option.
}
void set_mode(uint8_t control) {
// b5: enable blink
// b4: 1 => 640x200 graphics
// b3: video enable
// b2: 1 => monochrome
// b1: 1 => 320x200 graphics; 0 => text
// b0: 1 => 80-column text; 0 => 40
control_ = control; // To capture blink, monochrome and video enable bits.
if(control & 0x2) {
mode_ = (control & 0x10) ? Mode::Pixels640 : Mode::Pixels320;
pixels_per_tick = (mode_ == Mode::Pixels640) ? 16 : 8;
} else {
mode_ = Mode::Text;
pixels_per_tick = 8;
}
clock_divider = 1 + !(control & 0x01);
}
void set_colours(uint8_t value) {
// b5: 320x200 palette.
if(value & 0x20) {
palette320[1] = 0b00'10'10;
palette320[2] = 0b10'00'10;
palette320[3] = 0b10'10'10;
} else {
palette320[1] = 0b00'10'00;
palette320[2] = 0b10'00'00;
palette320[3] = 0b10'10'00; // TODO: brown, here and elsewhere.
}
// b4: set 320x200 palette into high intensity.
if(value & 0x10) {
palette320[1] |= palette320[1] >> 1;
palette320[2] |= palette320[2] >> 1;
palette320[3] |= palette320[3] >> 1;
}
// b3b0: set background, border, monochrome colour.
palette320[0] = uint8_t(
((value & 0x01) << 1) |
((value & 0x02) << 2) |
((value & 0x04) << 3)
);
if(value & 0x08) {
palette320[0] |= palette320[0] >> 1;
}
palette640[1] = palette320[0];
}
uint8_t control() {
return control_;
}
void update_hsync(bool new_hsync) {
if(new_hsync == previous_hsync) {
cycles_since_hsync += clock_divider;
} else {
cycles_since_hsync = 0;
previous_hsync = new_hsync;
}
}
OutputState implied_state(const Motorola::CRTC::BusState &state) const {
OutputState new_state;
if(state.hsync || state.vsync) {
new_state = OutputState::Sync;
} else if(!state.display_enable || !(control_&0x08)) {
new_state = OutputState::Border;
// TODO: I'm pretty sure this isn't correct for colour burst positioning, though
// it happens to fool the particular CRT I've implemented.
if(!(control_&4) && cycles_since_hsync <= 4) {
new_state = OutputState::ColourBurst;
}
} else {
new_state = OutputState::Pixels;
}
return new_state;
}
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
// Determine new output state.
update_hsync(state.hsync);
const OutputState new_state = implied_state(state);
// Upon either a state change or just having accumulated too much local time...
if(new_state != output_state || active_pixels_per_tick != pixels_per_tick || active_clock_divider != clock_divider || count > 912) {
// (1) flush preexisting state.
if(count) {
switch(output_state) {
case OutputState::Sync: crt.output_sync(count * active_clock_divider); break;
case OutputState::Border: crt.output_blank(count * active_clock_divider); break;
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, 0); break;
case OutputState::Pixels: flush_pixels(); break;
}
}
// (2) adopt new state.
output_state = new_state;
active_pixels_per_tick = pixels_per_tick;
active_clock_divider = clock_divider;
count = 0;
}
// Collect pixels if applicable.
if(output_state == OutputState::Pixels) {
if(!pixels) {
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
// Flush any period where pixels weren't recorded due to back pressure.
if(pixels && count) {
crt.output_blank(count * active_clock_divider);
count = 0;
}
}
if(pixels) {
if(state.cursor) {
std::fill(pixel_pointer, pixel_pointer + pixels_per_tick, 0x3f); // i.e. white.
} else {
if(mode_ == Mode::Text) {
serialise_text(state);
} else {
serialise_pixels(state);
}
}
pixel_pointer += active_pixels_per_tick;
}
}
// Advance.
count += 8;
// Output pixel row prematurely if storage is exhausted.
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
flush_pixels();
count = 0;
}
}
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
void flush_pixels() {
crt.output_data(count * active_clock_divider, size_t((count * active_pixels_per_tick) / 8));
pixels = pixel_pointer = nullptr;
}
void serialise_pixels(const Motorola::CRTC::BusState &state) {
// This is what I think is happenings:
//
// Refresh address is still shifted left one and two bytes are fetched, just as if it were
// character code + attributes except that these are two bytes worth of graphics.
//
// Meanwhile, row address is used to invent a 15th address line.
const auto base_address = ((state.refresh_address << 1) + (state.row_address << 13)) & 0x3fff;
const uint8_t bitmap[] = {
ram[base_address],
ram[base_address + 1],
};
if(mode_ == Mode::Pixels320) {
pixel_pointer[0] = palette320[(bitmap[0] & 0xc0) >> 6];
pixel_pointer[1] = palette320[(bitmap[0] & 0x30) >> 4];
pixel_pointer[2] = palette320[(bitmap[0] & 0x0c) >> 2];
pixel_pointer[3] = palette320[(bitmap[0] & 0x03) >> 0];
pixel_pointer[4] = palette320[(bitmap[1] & 0xc0) >> 6];
pixel_pointer[5] = palette320[(bitmap[1] & 0x30) >> 4];
pixel_pointer[6] = palette320[(bitmap[1] & 0x0c) >> 2];
pixel_pointer[7] = palette320[(bitmap[1] & 0x03) >> 0];
} else {
pixel_pointer[0x0] = palette640[(bitmap[0] & 0x80) >> 7];
pixel_pointer[0x1] = palette640[(bitmap[0] & 0x40) >> 6];
pixel_pointer[0x2] = palette640[(bitmap[0] & 0x20) >> 5];
pixel_pointer[0x3] = palette640[(bitmap[0] & 0x10) >> 4];
pixel_pointer[0x4] = palette640[(bitmap[0] & 0x08) >> 3];
pixel_pointer[0x5] = palette640[(bitmap[0] & 0x04) >> 2];
pixel_pointer[0x6] = palette640[(bitmap[0] & 0x02) >> 1];
pixel_pointer[0x7] = palette640[(bitmap[0] & 0x01) >> 0];
pixel_pointer[0x8] = palette640[(bitmap[1] & 0x80) >> 7];
pixel_pointer[0x9] = palette640[(bitmap[1] & 0x40) >> 6];
pixel_pointer[0xa] = palette640[(bitmap[1] & 0x20) >> 5];
pixel_pointer[0xb] = palette640[(bitmap[1] & 0x10) >> 4];
pixel_pointer[0xc] = palette640[(bitmap[1] & 0x08) >> 3];
pixel_pointer[0xd] = palette640[(bitmap[1] & 0x04) >> 2];
pixel_pointer[0xe] = palette640[(bitmap[1] & 0x02) >> 1];
pixel_pointer[0xf] = palette640[(bitmap[1] & 0x01) >> 0];
}
}
void serialise_text(const Motorola::CRTC::BusState &state) {
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff];
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff];
const uint8_t row = font[(glyph * 8) + state.row_address];
uint8_t colours[2] = {
uint8_t(((attributes & 0x40) >> 1) | ((attributes & 0x20) >> 2) | ((attributes & 0x10) >> 3)),
uint8_t(((attributes & 0x04) << 3) | ((attributes & 0x02) << 2) | ((attributes & 0x01) << 1)),
};
// Apply foreground intensity.
if(attributes & 0x08) {
colours[1] |= colours[1] >> 1;
}
// Apply blink or background intensity.
if(control_ & 0x20) {
if((attributes & 0x80) && (state.field_count & 16)) {
std::swap(colours[0], colours[1]);
}
} else {
if(attributes & 0x80) {
colours[0] |= colours[0] >> 1;
}
}
// Draw according to ROM contents.
pixel_pointer[0] = (row & 0x80) ? colours[1] : colours[0];
pixel_pointer[1] = (row & 0x40) ? colours[1] : colours[0];
pixel_pointer[2] = (row & 0x20) ? colours[1] : colours[0];
pixel_pointer[3] = (row & 0x10) ? colours[1] : colours[0];
pixel_pointer[4] = (row & 0x08) ? colours[1] : colours[0];
pixel_pointer[5] = (row & 0x04) ? colours[1] : colours[0];
pixel_pointer[6] = (row & 0x02) ? colours[1] : colours[0];
pixel_pointer[7] = (row & 0x01) ? colours[1] : colours[0];
}
Outputs::CRT::CRT crt;
static constexpr size_t DefaultAllocationSize = 320;
// Current output stream.
uint8_t *pixels = nullptr;
uint8_t *pixel_pointer = nullptr;
int active_pixels_per_tick = 8;
int active_clock_divider = 1;
// Source data.
const uint8_t *ram = nullptr;
std::vector<uint8_t> font;
// CRTC state tracking, for CRT serialisation.
OutputState output_state = OutputState::Sync;
int count = 0;
bool previous_hsync = false;
int cycles_since_hsync = 0;
// Current Programmer-set parameters.
int clock_divider = 1;
int pixels_per_tick = 8;
uint8_t control_ = 0;
enum class Mode {
Pixels640, Pixels320, Text,
} mode_ = Mode::Text;
uint8_t palette320[4]{};
uint8_t palette640[2]{};
} outputter_;
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
int full_clock_ = 0;
};
}
#endif /* CGA_h */

View File

@ -0,0 +1,236 @@
//
// MDA.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/12/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef MDA_h
#define MDA_h
#include "../../Components/6845/CRTC6845.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Machines/Utility/ROMCatalogue.hpp"
namespace PCCompatible {
class MDA {
public:
MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
static constexpr uint32_t BaseAddress = 0xb'0000;
static constexpr auto FontROM = ROM::Name::PCCompatibleMDAFont;
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
outputter_.ram = ram;
outputter_.font = font;
}
void run_for(Cycles cycles) {
// Input rate is the PIT rate of 1,193,182 Hz.
// MDA is actually clocked at 16.257 MHz; which is treated internally as 1,806,333.333333333333333... Hz
//
// The GCD of those two numbers is... 2. Oh well.
full_clock_ += 8'128'500 * cycles.as<int>();
crtc_.run_for(Cycles(full_clock_ / (596'591 * 9)));
full_clock_ %= (596'591 * 9);
}
template <int address>
void write(uint8_t value) {
switch(address) {
case 0: case 2: case 4: case 6:
crtc_.select_register(value);
break;
case 1: case 3: case 5: case 7:
crtc_.set_register(value);
break;
case 0x8:
outputter_.set_control(value);
break;
default: break;
}
}
template <int address>
uint8_t read() {
switch(address) {
case 1: case 3: case 5: case 7:
return crtc_.get_register();
case 0x8: return outputter_.control();
default: return 0xff;
}
}
// MARK: - Call-ins for ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
outputter_.crt.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return outputter_.crt.get_scaled_scan_status() * 596591.0f / 8128500.0f;
}
// MARK: - Display type configuration.
void set_display_type(Outputs::Display::DisplayType) {}
Outputs::Display::DisplayType get_display_type() const {
return outputter_.crt.get_display_type();
}
private:
struct CRTCOutputter {
CRTCOutputter() :
crt(882, 9, 370, 3, Outputs::Display::InputDataType::Red2Green2Blue2)
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
// consider whether that's worth building into the scan target.
{
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt.set_display_type(Outputs::Display::DisplayType::RGB);
}
void set_control(uint8_t control) {
// b0: 'high resolution' (probably breaks if not 1)
// b3: video enable
// b5: enable blink
control_ = control;
}
uint8_t control() {
return control_;
}
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
// Determine new output state.
const OutputState new_state =
(state.hsync | state.vsync) ? OutputState::Sync :
((state.display_enable && control_&0x08) ? OutputState::Pixels : OutputState::Border);
// Upon either a state change or just having accumulated too much local time...
if(new_state != output_state || count > 882) {
// (1) flush preexisting state.
if(count) {
switch(output_state) {
case OutputState::Sync: crt.output_sync(count); break;
case OutputState::Border: crt.output_blank(count); break;
case OutputState::Pixels:
crt.output_data(count);
pixels = pixel_pointer = nullptr;
break;
}
}
// (2) adopt new state.
output_state = new_state;
count = 0;
}
// Collect pixels if applicable.
if(output_state == OutputState::Pixels) {
if(!pixels) {
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
// Flush any period where pixels weren't recorded due to back pressure.
if(pixels && count) {
crt.output_blank(count);
count = 0;
}
}
if(pixels) {
static constexpr uint8_t high_intensity = 0x0d;
static constexpr uint8_t low_intensity = 0x09;
static constexpr uint8_t off = 0x00;
if(state.cursor) {
pixel_pointer[0] = pixel_pointer[1] = pixel_pointer[2] = pixel_pointer[3] =
pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] =
pixel_pointer[8] = low_intensity;
} else {
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff];
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff];
uint8_t row = font[(glyph * 14) + state.row_address];
const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity;
uint8_t blank = off;
// Handle irregular attributes.
// Cf. http://www.seasip.info/VintagePC/mda.html#memmap
switch(attributes) {
case 0x00: case 0x08: case 0x80: case 0x88:
row = 0;
break;
case 0x70: case 0x78: case 0xf0: case 0xf8:
row ^= 0xff;
blank = intensity;
break;
}
// Apply blink if enabled.
if((control_ & 0x20) && (attributes & 0x80) && (state.field_count & 16)) {
row ^= 0xff;
blank = (blank == off) ? intensity : off;
}
if(((attributes & 7) == 1) && state.row_address == 13) {
// Draw as underline.
std::fill(pixel_pointer, pixel_pointer + 9, intensity);
} else {
// Draw according to ROM contents, possibly duplicating final column.
pixel_pointer[0] = (row & 0x80) ? intensity : off;
pixel_pointer[1] = (row & 0x40) ? intensity : off;
pixel_pointer[2] = (row & 0x20) ? intensity : off;
pixel_pointer[3] = (row & 0x10) ? intensity : off;
pixel_pointer[4] = (row & 0x08) ? intensity : off;
pixel_pointer[5] = (row & 0x04) ? intensity : off;
pixel_pointer[6] = (row & 0x02) ? intensity : off;
pixel_pointer[7] = (row & 0x01) ? intensity : off;
pixel_pointer[8] = (glyph >= 0xc0 && glyph < 0xe0) ? pixel_pointer[7] : blank;
}
}
pixel_pointer += 9;
}
}
// Advance.
count += 9;
// Output pixel row prematurely if storage is exhausted.
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
crt.output_data(count);
count = 0;
pixels = pixel_pointer = nullptr;
}
}
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
Outputs::CRT::CRT crt;
enum class OutputState {
Sync, Pixels, Border
} output_state = OutputState::Sync;
int count = 0;
uint8_t *pixels = nullptr;
uint8_t *pixel_pointer = nullptr;
static constexpr size_t DefaultAllocationSize = 720;
const uint8_t *ram = nullptr;
std::vector<uint8_t> font;
uint8_t control_ = 0;
} outputter_;
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
int full_clock_ = 0;
};
}
#endif /* MDA_h */

View File

@ -8,18 +8,19 @@
#include "PCCompatible.hpp"
#include "CGA.hpp"
#include "DMA.hpp"
#include "KeyboardMapper.hpp"
#include "MDA.hpp"
#include "Memory.hpp"
#include "PIC.hpp"
#include "PIT.hpp"
#include "DMA.hpp"
#include "Memory.hpp"
#include "../../InstructionSets/x86/Decoder.hpp"
#include "../../InstructionSets/x86/Flags.hpp"
#include "../../InstructionSets/x86/Instruction.hpp"
#include "../../InstructionSets/x86/Perform.hpp"
#include "../../Components/6845/CRTC6845.hpp"
#include "../../Components/8255/i8255.hpp"
#include "../../Components/8272/CommandDecoder.hpp"
#include "../../Components/8272/Results.hpp"
@ -41,27 +42,33 @@
#include "../ScanProducer.hpp"
#include "../TimedMachine.hpp"
#include "../../Analyser/Static/PCCompatible/Target.hpp"
#include <array>
#include <iostream>
namespace PCCompatible {
//bool log = false;
//std::string previous;
using VideoAdaptor = Analyser::Static::PCCompatible::Target::VideoAdaptor;
template <VideoAdaptor adaptor> struct Adaptor;
template <> struct Adaptor<VideoAdaptor::MDA> {
using type = MDA;
};
template <> struct Adaptor<VideoAdaptor::CGA> {
using type = CGA;
};
class FloppyController {
public:
FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {
FloppyController(PIC &pic, DMA &dma, int drive_count) : pic_(pic), dma_(dma) {
// Default: one floppy drive only.
drives_[0].exists = true;
drives_[1].exists = false;
drives_[2].exists = false;
drives_[3].exists = false;
for(int c = 0; c < 4; c++) {
drives_[c].exists = drive_count > c;
}
}
void set_digital_output(uint8_t control) {
// printf("FDC DOR: %02x\n", control);
// b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1;
// b3: 1 => enable DMA; 0 => disable;
// b2: 1 => enable FDC; 0 => hold at reset;
@ -92,7 +99,6 @@ class FloppyController {
}
uint8_t status() const {
// printf("FDC: read status %02x\n", status_.main());
return status_.main();
}
@ -138,6 +144,7 @@ class FloppyController {
switch(access_result) {
default: break;
case AccessResult::NotAccepted:
complete = true;
wrote_in_full = false;
break;
case AccessResult::AcceptedWithEOP:
@ -182,7 +189,6 @@ class FloppyController {
} break;
case Command::Recalibrate:
printf("FDC: Recalibrate\n");
drives_[decoder_.target().drive].track = 0;
drives_[decoder_.target().drive].raised_interrupt = true;
@ -190,7 +196,6 @@ class FloppyController {
pic_.apply_edge<6>(true);
break;
case Command::Seek:
printf("FDC: Seek %d:%d to %d\n", decoder_.target().drive, decoder_.target().head, decoder_.seek_target());
drives_[decoder_.target().drive].track = decoder_.seek_target();
drives_[decoder_.target().drive].raised_interrupt = true;
@ -199,8 +204,6 @@ class FloppyController {
break;
case Command::SenseInterruptStatus: {
printf("FDC: SenseInterruptStatus\n");
int c = 0;
for(; c < 4; c++) {
if(drives_[c].raised_interrupt) {
@ -219,14 +222,12 @@ class FloppyController {
}
} break;
case Command::Specify:
printf("FDC: Specify\n");
specify_specs_ = decoder_.specify_specs();
break;
// case Command::SenseDriveStatus: {
// } break;
case Command::Invalid:
printf("FDC: Invalid\n");
results_.serialise_none();
break;
}
@ -251,11 +252,9 @@ class FloppyController {
status_.set(MainStatus::DataIsToProcessor, false);
status_.set(MainStatus::CommandInProgress, false);
}
// printf("FDC read: %02x\n", result);
return result;
}
printf("FDC read?\n");
return 0x80;
}
@ -458,203 +457,6 @@ class KeyboardController {
int reset_delay_ = 0;
};
class MDA {
public:
MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
outputter_.ram = ram;
outputter_.font = font;
}
void run_for(Cycles cycles) {
// I _think_ the MDA's CRTC is clocked at 14/9ths the PIT clock.
// Do that conversion here.
full_clock_ += 14 * cycles.as<int>();
crtc_.run_for(Cycles(full_clock_ / 9));
full_clock_ %= 9;
}
template <int address>
void write(uint8_t value) {
if constexpr (address & 0x8) {
outputter_.set_control(value);
} else {
if constexpr (address & 0x1) {
crtc_.set_register(value);
} else {
crtc_.select_register(value);
}
}
}
template <int address>
uint8_t read() {
if constexpr (address & 0x8) {
return outputter_.control();
} else {
return crtc_.get_register();
}
}
// MARK: - Call-ins for ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
outputter_.crt.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return outputter_.crt.get_scaled_scan_status() * 9.0f / 14.0f;
}
private:
struct CRTCOutputter {
CRTCOutputter() :
crt(882, 9, 382, 3, Outputs::Display::InputDataType::Red2Green2Blue2)
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
// consider whether that's worth building into the scan target.
{
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt.set_display_type(Outputs::Display::DisplayType::RGB);
}
void set_control(uint8_t control) {
// b0: 'high resolution' (probably breaks if not 1)
// b3: video enable
// b5: enable blink
control_ = control;
}
uint8_t control() {
return control_;
}
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
// Determine new output state.
const OutputState new_state =
(state.hsync | state.vsync) ? OutputState::Sync :
((state.display_enable && control_&0x08) ? OutputState::Pixels : OutputState::Border);
// Upon either a state change or just having accumulated too much local time...
if(new_state != output_state || count > 882) {
// (1) flush preexisting state.
if(count) {
switch(output_state) {
case OutputState::Sync: crt.output_sync(count); break;
case OutputState::Border: crt.output_blank(count); break;
case OutputState::Pixels:
crt.output_data(count);
pixels = pixel_pointer = nullptr;
break;
}
}
// (2) adopt new state.
output_state = new_state;
count = 0;
}
// Collect pixels if applicable.
if(output_state == OutputState::Pixels) {
if(!pixels) {
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
// Flush any period where pixels weren't recorded due to back pressure.
if(pixels && count) {
crt.output_blank(count);
count = 0;
}
}
if(pixels) {
static constexpr uint8_t high_intensity = 0x0d;
static constexpr uint8_t low_intensity = 0x09;
static constexpr uint8_t off = 0x00;
if(state.cursor) {
pixel_pointer[0] = pixel_pointer[1] = pixel_pointer[2] = pixel_pointer[3] =
pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] =
pixel_pointer[8] = low_intensity;
} else {
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff];
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff];
uint8_t row = font[(glyph * 14) + state.row_address];
const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity;
uint8_t blank = off;
// Handle irregular attributes.
// Cf. http://www.seasip.info/VintagePC/mda.html#memmap
switch(attributes) {
case 0x00: case 0x08: case 0x80: case 0x88:
row = 0;
break;
case 0x70: case 0x78: case 0xf0: case 0xf8:
row ^= 0xff;
blank = intensity;
break;
}
// Apply blink if enabled.
if((control_ & 0x20) && (attributes & 0x80) && (state.field_count & 16)) {
row ^= 0xff;
blank = (blank == off) ? intensity : off;
}
if(((attributes & 7) == 1) && state.row_address == 13) {
// Draw as underline.
std::fill(pixel_pointer, pixel_pointer + 9, intensity);
} else {
// Draw according to ROM contents, possibly duplicating final column.
pixel_pointer[0] = (row & 0x80) ? intensity : off;
pixel_pointer[1] = (row & 0x40) ? intensity : off;
pixel_pointer[2] = (row & 0x20) ? intensity : off;
pixel_pointer[3] = (row & 0x10) ? intensity : off;
pixel_pointer[4] = (row & 0x08) ? intensity : off;
pixel_pointer[5] = (row & 0x04) ? intensity : off;
pixel_pointer[6] = (row & 0x02) ? intensity : off;
pixel_pointer[7] = (row & 0x01) ? intensity : off;
pixel_pointer[8] = (glyph >= 0xc0 && glyph < 0xe0) ? pixel_pointer[7] : blank;
}
}
pixel_pointer += 9;
}
}
// Advance.
count += 9;
// Output pixel row prematurely if storage is exhausted.
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
crt.output_data(count);
count = 0;
pixels = pixel_pointer = nullptr;
}
}
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
Outputs::CRT::CRT crt;
enum class OutputState {
Sync, Pixels, Border
} output_state = OutputState::Sync;
int count = 0;
uint8_t *pixels = nullptr;
uint8_t *pixel_pointer = nullptr;
static constexpr size_t DefaultAllocationSize = 720;
const uint8_t *ram = nullptr;
std::vector<uint8_t> font;
uint8_t control_ = 0;
} outputter_;
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
int full_clock_;
};
struct PCSpeaker {
PCSpeaker() :
toggle(queue),
@ -724,10 +526,28 @@ class PITObserver {
using PIT = i8253<false, PITObserver>;
class i8255PortHandler : public Intel::i8255::PortHandler {
// Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol
public:
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard) :
speaker_(speaker), keyboard_(keyboard) {}
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, VideoAdaptor adaptor, int drive_count) :
speaker_(speaker), keyboard_(keyboard) {
// High switches:
//
// b3, b2: drive count; 00 = 1, 01 = 2, etc
// b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA)
switch(adaptor) {
default: break;
case VideoAdaptor::MDA: high_switches_ |= 0b11; break;
case VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns.
}
high_switches_ |= uint8_t(drive_count << 2);
// Low switches:
//
// b3, b2: RAM on motherboard (64 * bit pattern)
// b1: 1 => FPU present; 0 => absent;
// b0: 1 => floppy drive present; 0 => absent.
low_switches_ |= 0b1100; // Assume maximum RAM.
if(drive_count) low_switches_ |= 0xb0001;
}
void set_value(int port, uint8_t value) {
switch(port) {
@ -742,43 +562,35 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
enable_keyboard_ = !(value & 0x80);
keyboard_.set_mode(value >> 6);
high_switches_ = value & 0x08;
use_high_switches_ = value & 0x08;
speaker_.set_control(value & 0x01, value & 0x02);
break;
}
// printf("PPI: %02x to %d\n", value, port);
}
uint8_t get_value(int port) {
switch(port) {
case 0:
// printf("PPI: from keyboard\n");
return enable_keyboard_ ? keyboard_.read() : 0b0011'1101;
return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_);
// Guesses that switches is high and low combined as below.
case 2:
// Common:
//
// b7: 1 => memory parity error; 0 => none;
// b6: 1 => IO channel error; 0 => none;
// b5: timer 2 output; [TODO]
// b4: cassette data input; [TODO]
// b3...b0: whichever of the high and low switches is selected.
return
high_switches_ ?
// b3, b2: drive count; 00 = 1, 01 = 2, etc
// b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA)
0b0000'0011
:
// b3, b2: RAM on motherboard (64 * bit pattern)
// b1: 1 => FPU present; 0 => absent;
// b0: 1 => floppy drive present; 0 => absent.
0b0000'1101;
use_high_switches_ ? high_switches_ : low_switches_;
}
return 0;
};
private:
bool high_switches_ = false;
uint8_t high_switches_ = 0;
uint8_t low_switches_ = 0;
bool use_high_switches_ = false;
PCSpeaker &speaker_;
KeyboardController &keyboard_;
@ -786,12 +598,16 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
};
using PPI = Intel::i8255::i8255<i8255PortHandler>;
template <VideoAdaptor video>
class IO {
public:
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) :
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), mda_(mda), fdc_(fdc) {}
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc) :
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc) {}
template <typename IntT> void out(uint16_t port, IntT value) {
static constexpr uint16_t crtc_base =
video == VideoAdaptor::MDA ? 0x03b0 : 0x03d0;
switch(port) {
default:
if constexpr (std::is_same_v<IntT, uint8_t>) {
@ -847,34 +663,30 @@ class IO {
case 0x0086: dma_.pages.set_page<6>(uint8_t(value)); break;
case 0x0087: dma_.pages.set_page<7>(uint8_t(value)); break;
case 0x03b0: case 0x03b2: case 0x03b4: case 0x03b6:
//
// CRTC access block, with slightly laboured 16-bit to 8-bit mapping.
//
case crtc_base + 0: case crtc_base + 2:
case crtc_base + 4: case crtc_base + 6:
if constexpr (std::is_same_v<IntT, uint16_t>) {
mda_.write<0>(uint8_t(value));
mda_.write<1>(uint8_t(value >> 8));
video_.template write<0>(uint8_t(value));
video_.template write<1>(uint8_t(value >> 8));
} else {
mda_.write<0>(value);
video_.template write<0>(value);
}
break;
case crtc_base + 1: case crtc_base + 3:
case crtc_base + 5: case crtc_base + 7:
if constexpr (std::is_same_v<IntT, uint16_t>) {
video_.template write<1>(uint8_t(value));
video_.template write<0>(uint8_t(value >> 8));
} else {
video_.template write<1>(value);
}
break;
case 0x03b1: case 0x03b3: case 0x03b5: case 0x03b7:
if constexpr (std::is_same_v<IntT, uint16_t>) {
mda_.write<1>(uint8_t(value));
mda_.write<0>(uint8_t(value >> 8));
} else {
mda_.write<1>(value);
}
break;
case 0x03b8:
mda_.write<8>(uint8_t(value));
break;
case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3:
case 0x03d4: case 0x03d5: case 0x03d6: case 0x03d7:
case 0x03d8: case 0x03d9: case 0x03da: case 0x03db:
case 0x03dc: case 0x03dd: case 0x03de: case 0x03df:
// Ignore CGA accesses.
break;
case crtc_base + 0x8: video_.template write<0x8>(uint8_t(value)); break;
case crtc_base + 0x9: video_.template write<0x9>(uint8_t(value)); break;
case 0x03f2:
fdc_.set_digital_output(uint8_t(value));
@ -959,7 +771,17 @@ class IO {
case 0x03f4: return fdc_.status();
case 0x03f5: return fdc_.read();
case 0x03b8: return mda_.read<8>();
case 0x03b8:
if constexpr (video == VideoAdaptor::MDA) {
return video_.template read<0x8>();
}
break;
case 0x3da:
if constexpr (video == VideoAdaptor::CGA) {
return video_.template read<0xa>();
}
break;
case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb:
case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef:
@ -972,7 +794,7 @@ class IO {
// Ignore serial port accesses.
break;
}
return IntT(~0);
return 0xff;
}
private:
@ -980,7 +802,7 @@ class IO {
DMA &dma_;
PPI &ppi_;
PIC &pic_;
MDA &mda_;
typename Adaptor<video>::type &video_;
FloppyController &fdc_;
};
@ -1033,6 +855,7 @@ class FlowController {
bool halted_ = false;
};
template <VideoAdaptor video>
class ConcreteMachine:
public Machine,
public MachineTypes::TimedMachine,
@ -1040,20 +863,24 @@ class ConcreteMachine:
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
public Activity::Source
public Activity::Source,
public Configurable::Device
{
static constexpr int DriveCount = 1;
using Video = typename Adaptor<video>::type;
public:
ConcreteMachine(
[[maybe_unused]] const Analyser::Static::Target &target,
const Analyser::Static::PCCompatible::Target &target,
const ROMMachine::ROMFetcher &rom_fetcher
) :
keyboard_(pic_),
fdc_(pic_, dma_),
fdc_(pic_, dma_, DriveCount),
pit_observer_(pic_, speaker_),
ppi_handler_(speaker_, keyboard_),
ppi_handler_(speaker_, keyboard_, video, DriveCount),
pit_(pit_observer_),
ppi_(ppi_handler_),
context(pit_, dma_, ppi_, pic_, mda_, fdc_)
context(pit_, dma_, ppi_, pic_, video_, fdc_)
{
// Set up DMA source/target.
dma_.set_memory(&context.memory);
@ -1065,7 +892,7 @@ class ConcreteMachine:
// Fetch the BIOS. [8088 only, for now]
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
const auto font = ROM::Name::PCCompatibleMDAFont;
const auto font = Video::FontROM;
ROM::Request request = ROM::Request(bios) && ROM::Request(font);
auto roms = rom_fetcher(request);
@ -1076,9 +903,9 @@ class ConcreteMachine:
const auto &bios_contents = roms.find(bios)->second;
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
// Give the MDA something to read from.
// Give the video card something to read from.
const auto &font_contents = roms.find(font)->second;
mda_.set_source(context.memory.at(0xb'0000), font_contents);
video_.set_source(context.memory.at(Video::BaseAddress), font_contents);
// ... and insert media.
insert_media(target.media);
@ -1113,7 +940,7 @@ class ConcreteMachine:
//
// Advance CRTC at a more approximate rate.
//
mda_.run_for(Cycles(3));
video_.run_for(Cycles(3));
//
// Perform one CPU instruction every three PIT cycles.
@ -1123,7 +950,7 @@ class ConcreteMachine:
keyboard_.run_for(Cycles(1));
// Query for interrupts and apply if pending.
if(pic_.pending() && context.flags.flag<InstructionSet::x86::Flag::Interrupt>()) {
if(pic_.pending() && context.flags.template flag<InstructionSet::x86::Flag::Interrupt>()) {
// Regress the IP if a REP is in-progress so as to resume it later.
if(context.flow_controller.should_repeat()) {
context.registers.ip() = decoded_ip_;
@ -1192,10 +1019,10 @@ class ConcreteMachine:
// MARK: - ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
mda_.set_scan_target(scan_target);
video_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
return mda_.get_scaled_scan_status();
return video_.get_scaled_scan_status();
}
// MARK: - AudioProducer.
@ -1235,11 +1062,31 @@ class ConcreteMachine:
fdc_.set_activity_observer(observer);
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() override {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) override {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
video_.set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const override {
return video_.get_display_type();
}
private:
PIC pic_;
DMA dma_;
PCSpeaker speaker_;
MDA mda_;
Video video_;
KeyboardController keyboard_;
FloppyController fdc_;
@ -1252,11 +1099,11 @@ class ConcreteMachine:
PCCompatible::KeyboardMapper keyboard_mapper_;
struct Context {
Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) :
Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc) :
segments(registers),
memory(registers, segments),
flow_controller(registers, segments),
io(pit, dma, ppi, pic, mda, fdc)
io(pit, dma, ppi, pic, card, fdc)
{
reset();
}
@ -1271,7 +1118,7 @@ class ConcreteMachine:
Segments segments;
Memory memory;
FlowController flow_controller;
IO io;
IO<video> io;
static constexpr auto model = InstructionSet::x86::Model::i8086;
} context;
@ -1293,7 +1140,14 @@ using namespace PCCompatible;
// See header; constructs and returns an instance of the Amstrad CPC.
Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
return new PCCompatible::ConcreteMachine(*target, rom_fetcher);
using Target = Analyser::Static::PCCompatible::Target;
const Target *const pc_target = dynamic_cast<const Target *>(target);
switch(pc_target->adaptor) {
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA>(*pc_target, rom_fetcher);
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA>(*pc_target, rom_fetcher);
default: return nullptr;
}
}
Machine::~Machine() {}

View File

@ -9,6 +9,8 @@
#ifndef PCCompatible_hpp
#define PCCompatible_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
@ -26,6 +28,23 @@ class Machine {
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
);
/// Defines the runtime options [sometimes] available for a PC.
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>
{
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType) :
Configurable::DisplayOption<Options>(Configurable::Display::RGB)
{
if(needs_declare()) {
declare_display_option();
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
}
}
};
};
}

View File

@ -222,6 +222,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
Emplace(MasterSystem, Sega::MasterSystem::Machine);
Emplace(MSX, MSX::Machine);
Emplace(Oric, Oric::Machine);
Emplace(PCCompatible, PCCompatible::Machine);
Emplace(Vic20, Commodore::Vic20::Machine);
Emplace(ZX8081, Sinclair::ZX8081::Machine);
Emplace(ZXSpectrum, Sinclair::ZXSpectrum::Machine);

View File

@ -573,6 +573,9 @@ Description::Description(Name name) {
case Name::PCCompatiblePhoenix80286BIOS:
*this = Description(name, "PCCompatible", "Phoenix 80286 BIOS 3.05", "Phoenix 80286 ROM BIOS Version 3.05.bin", 32 * 1024, 0x8d0d318au);
break;
case Name::PCCompatibleCGAFont:
*this = Description(name, "PCCompatible", "IBM's CGA font", "CGA.F08", 8 * 256, 0xa362ffe6u);
break;
case Name::PCCompatibleMDAFont:
*this = Description(name, "PCCompatible", "IBM's MDA font", "EUMDA9.F14", 14 * 256, 0x7754882au);
break;

View File

@ -136,6 +136,7 @@ enum Name {
PCCompatibleGLaBIOS,
PCCompatiblePhoenix80286BIOS,
PCCompatibleMDAFont,
PCCompatibleCGAFont,
// Sinclair QL.
SinclairQLJS,

View File

@ -1168,6 +1168,8 @@
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
429B135E2B1F7BDA006BB4CB /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
429B135F2B1F7BDA006BB4CB /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
429B13622B1FCA96006BB4CB /* MDA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MDA.hpp; sourceTree = "<group>"; };
429B13632B20234B006BB4CB /* CGA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CGA.hpp; sourceTree = "<group>"; };
42A5E80B2ABBE04600A0DD5D /* NeskellTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeskellTests.swift; sourceTree = "<group>"; };
42A5E8332ABBE16F00A0DD5D /* illegal_rmw_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = illegal_rmw_test.bin; sourceTree = "<group>"; };
42A5E8342ABBE16F00A0DD5D /* arr_bcd_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = arr_bcd_test.bin; sourceTree = "<group>"; };
@ -2394,8 +2396,10 @@
isa = PBXGroup;
children = (
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
429B13632B20234B006BB4CB /* CGA.hpp */,
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
429B13622B1FCA96006BB4CB /* MDA.hpp */,
423820132B1A235200964EFE /* Memory.hpp */,
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,

View File

@ -127,6 +127,11 @@ typedef NS_ENUM(NSInteger, CSPCCompatibleModel) {
CSPCCompatibleModelTurboXT,
};
typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) {
CSPCCompatibleVideoAdaptorMDA,
CSPCCompatibleVideoAdaptorCGA,
};
typedef int Kilobytes;
@interface CSStaticAnalyser : NSObject
@ -147,7 +152,7 @@ typedef int Kilobytes;
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model;
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor;
@property(nonatomic, readonly, nullable) NSString *optionsNibName;
@property(nonatomic, readonly) NSString *displayName;

View File

@ -24,6 +24,7 @@
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
#include "../../../../../Analyser/Static/PCCompatible/Target.hpp"
#include "../../../../../Analyser/Static/ZX8081/Target.hpp"
#include "../../../../../Analyser/Static/ZXSpectrum/Target.hpp"
@ -272,11 +273,15 @@
return self;
}
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model {
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor {
self = [super init];
if(self) {
using Target = Analyser::Static::Target;
auto target = std::make_unique<Target>(Analyser::Machine::PCCompatible);
using Target = Analyser::Static::PCCompatible::Target;
auto target = std::make_unique<Target>();
switch(adaptor) {
case CSPCCompatibleVideoAdaptorMDA: target->adaptor = Target::VideoAdaptor::MDA; break;
case CSPCCompatibleVideoAdaptorCGA: target->adaptor = Target::VideoAdaptor::CGA; break;
}
_targets.push_back(std::move(target));
}
return self;
@ -375,6 +380,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Oric: return @"OricOptions";
case Analyser::Machine::PCCompatible: return @"CompositeOptions";
case Analyser::Machine::Vic20: return @"QuickLoadCompositeOptions";
case Analyser::Machine::ZX8081: return @"ZX8081Options";
case Analyser::Machine::ZXSpectrum: return @"QuickLoadCompositeOptions"; // TODO: @"ZXSpectrumOptions";

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="367"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<rect key="screenRect" x="0.0" y="0.0" width="1800" height="1131"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="367"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -49,7 +49,7 @@ Gw
<action selector="cancelCreateMachine:" target="-2" id="lf8-PM-c0m"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="1" verticalHuggingPriority="1" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<textField horizontalHuggingPriority="1" verticalHuggingPriority="1" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<rect key="frame" x="18" y="14" width="405" height="32"/>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
<font key="font" metaFont="system"/>
@ -81,7 +81,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6K-dt-stj">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6K-dt-stj">
<rect key="frame" x="18" y="198" width="89" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Chip Memory:" id="FIO-ZR-rsA">
<font key="font" metaFont="system"/>
@ -89,7 +89,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YD0-OJ-2bY">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YD0-OJ-2bY">
<rect key="frame" x="18" y="168" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Fast Memory:" id="Rpz-39-jyt">
<font key="font" metaFont="system"/>
@ -148,7 +148,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c3g-96-b3x">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c3g-96-b3x">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="53v-92-jmf">
<font key="font" metaFont="system"/>
@ -172,7 +172,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="222"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
<font key="font" metaFont="system"/>
@ -180,9 +180,9 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<rect key="frame" x="18" y="154" width="96" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk Controller:" id="kbf-rc-Y4M">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -238,7 +238,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="222"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
<font key="font" metaFont="system"/>
@ -246,9 +246,9 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<rect key="frame" x="18" y="154" width="85" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory size:" id="OLJ-nC-yyj">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="OLJ-nC-yyj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -303,7 +303,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="222"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dKg-qC-BBF">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dKg-qC-BBF">
<rect key="frame" x="18" y="184" width="58" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory:" id="ZBF-0r-RNK">
<font key="font" metaFont="system"/>
@ -461,7 +461,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<rect key="frame" x="18" y="124" width="43" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
<font key="font" metaFont="system"/>
@ -469,7 +469,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<rect key="frame" x="18" y="184" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
<font key="font" metaFont="system"/>
@ -477,7 +477,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<rect key="frame" x="18" y="94" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
<font key="font" metaFont="system"/>
@ -485,7 +485,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<rect key="frame" x="18" y="64" width="36" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
<font key="font" metaFont="system"/>
@ -493,7 +493,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
<rect key="frame" x="17" y="154" width="47" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="sAw-C9-Sf7">
<font key="font" metaFont="system"/>
@ -539,7 +539,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="222"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
<font key="font" metaFont="system"/>
@ -599,7 +599,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="18" y="154" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
<font key="font" metaFont="system"/>
@ -607,7 +607,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFV-RB-7dB">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFV-RB-7dB">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="r7C-zE-gyj">
<font key="font" metaFont="system"/>
@ -662,7 +662,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="222"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
<font key="font" metaFont="system"/>
@ -700,9 +700,9 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<rect key="frame" x="18" y="154" width="92" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk Interface:" id="SFK-hS-tFC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -730,22 +730,21 @@ Gw
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="stw-i3-ikG">
<rect key="frame" x="108" y="192" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="oIy-If-5bQ" id="xz8-mu-ynU">
<rect key="frame" x="116" y="192" width="68" height="25"/>
<popUpButtonCell key="cell" type="push" title="MDA" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="oIy-If-5bQ" id="xz8-mu-ynU">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="dju-ZY-2DH">
<items>
<menuItem title="Unexpanded" state="on" id="oIy-If-5bQ"/>
<menuItem title="16 kb" tag="16" id="igW-oa-8YI"/>
<menuItem title="64 kb" tag="64" hidden="YES" enabled="NO" id="Tcz-UB-hbn"/>
<menuItem title="MDA" state="on" id="oIy-If-5bQ"/>
<menuItem title="CGA" tag="1" id="igW-oa-8YI"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uhf-1k-ibT">
<rect key="frame" x="18" y="198" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="ROV-EU-T3W">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uhf-1k-ibT">
<rect key="frame" x="18" y="198" width="95" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Video Adaptor:" id="ROV-EU-T3W">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -797,7 +796,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<rect key="frame" x="18" y="184" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
<font key="font" metaFont="system"/>
@ -805,7 +804,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<rect key="frame" x="18" y="154" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
<font key="font" metaFont="system"/>
@ -858,7 +857,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="18" y="184" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
<font key="font" metaFont="system"/>
@ -906,7 +905,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="184" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
@ -947,7 +946,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="184" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
@ -1012,7 +1011,7 @@ Gw
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<rect key="frame" x="18" y="320" width="554" height="27"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
@ -1070,6 +1069,7 @@ Gw
<outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/>
<outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/>
<outlet property="oricModelTypeButton" destination="ENP-hI-BVZ" id="n9i-Ym-miE"/>
<outlet property="pcVideoAdaptorButton" destination="stw-i3-ikG" id="6VO-Q9-4kV"/>
<outlet property="spectrumModelTypeButton" destination="gFZ-d4-WFv" id="tdX-Cv-Swe"/>
<outlet property="vic20HasC1540Button" destination="Lrf-gL-6EI" id="21g-dJ-mOo"/>
<outlet property="vic20MemorySizeButton" destination="2eV-Us-eEv" id="5j4-jw-89d"/>

View File

@ -63,6 +63,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet var oricModelTypeButton: NSPopUpButton!
@IBOutlet var oricDiskInterfaceButton: NSPopUpButton!
// MARK: - PC compatible properties
@IBOutlet var pcVideoAdaptorButton: NSPopUpButton!
// MARK: - Spectrum properties
@IBOutlet var spectrumModelTypeButton: NSPopUpButton!
@ -150,6 +153,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
oricDiskInterfaceButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.oricDiskInterface"))
oricModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.oricModel"))
// PC settings
pcVideoAdaptorButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcVideoAdaptor"))
// Spectrum settings
spectrumModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.spectrumModel"))
@ -216,6 +222,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
standardUserDefaults.set(oricDiskInterfaceButton.selectedTag(), forKey: "new.oricDiskInterface")
standardUserDefaults.set(oricModelTypeButton.selectedTag(), forKey: "new.oricModel")
// PC settings
standardUserDefaults.set(pcVideoAdaptorButton.selectedTag(), forKey: "new.pcVideoAdaptor")
// Spectrum settings
standardUserDefaults.set(spectrumModelTypeButton.selectedTag(), forKey: "new.spectrumModel")
@ -402,7 +411,12 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
return CSStaticAnalyser(oricModel: model, diskInterface: diskInterface)
case "pc":
return CSStaticAnalyser(pcCompatibleModel: .turboXT)
var videoAdaptor: CSPCCompatibleVideoAdaptor = .MDA
switch pcVideoAdaptorButton.selectedTag() {
case 1: videoAdaptor = .CGA
default: break
}
return CSStaticAnalyser(pcCompatibleModel: .turboXT, videoAdaptor: videoAdaptor)
case "spectrum":
var model: CSMachineSpectrumModel = .plus2a

Binary file not shown.

View File

@ -3,8 +3,9 @@ Expected files:
GLABIOS_0.2.5_8T.ROM — the 8088 GlaBIOS ROM.
Phoenix 80286 ROM BIOS Version 3.05.bin — Phoenix's 80286 AT-clone BIOS.
EUMDA9.F14 — a dump of the MDA font.
CGA.F08 — a dump of the CGA font.
GlaBIOS is an open-source GPLv3 alternative BIOS for XT clones, available from https://glabios.org/
The MDA font is in the form offered at https://github.com/viler-int10h/vga-text-mode-fonts i.e. it's 256 lots of 14 bytes, the first 14 being the content of character 0, the next 14 being the content of character 1, etc.
The MDA and CGA fonts are in the form offered at https://github.com/viler-int10h/vga-text-mode-fonts i.e. it's 256 lots of 14 bytes, the first 14 being the content of character 0, the next 14 being the content of character 1, etc.