1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge pull request #502 from TomHarte/IIe

Extends Apple II emulation to include the IIe
This commit is contained in:
Thomas Harte 2018-08-04 21:02:45 -04:00 committed by GitHub
commit 11ae2c64ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 713 additions and 270 deletions

View File

@ -18,7 +18,8 @@ namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
II,
IIplus
IIplus,
IIe
};
enum class DiskController {
None,
@ -26,7 +27,7 @@ struct Target: public ::Analyser::Static::Target {
ThirteenSector
};
Model model = Model::IIplus;
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
};

View File

@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) {
drives_[active_drive_].set_motor_on(motor_is_enabled_);
}
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
void DiskII::run_for(const Cycles cycles) {
if(preferred_clocking() == ClockingHint::Preference::None) return;
int integer_cycles = cycles.as_int();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
inputs_ |= input_flux;
if(flux_duration_) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
state_ = state_machine_[static_cast<std::size_t>(address)];
switch(state_ & 0xf) {
default: shift_register_ = 0; break; // clear
@ -115,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) {
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
}
// Per comp.sys.apple2.programmer there is a delay between the controller
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
}
decide_clocking_preference();
}
@ -200,6 +214,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux;
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
decide_clocking_preference();
}
}
@ -232,9 +247,12 @@ int DiskII::read_address(int address) {
case 0x8:
shift_register_ = 0;
set_control(Control::Motor, false);
motor_off_time_ = clock_rate_;
break;
case 0x9:
set_control(Control::Motor, true);
motor_off_time_ = -1;
break;
case 0x9: set_control(Control::Motor, true); break;
case 0xa: select_drive(0); break;
case 0xb: select_drive(1); break;

View File

@ -109,6 +109,7 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;
@ -121,6 +122,7 @@ class DiskII:
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
};
}

View File

@ -27,27 +27,17 @@
#include "../../Analyser/Static/AppleII/Target.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
#include <algorithm>
#include <array>
#include <memory>
std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload"));
return options;
}
namespace {
class ConcreteMachine:
template <bool is_iie> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public AppleII::Machine,
@ -57,19 +47,22 @@ class ConcreteMachine:
private:
struct VideoBusHandler : public AppleII::Video::BusHandler {
public:
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
uint8_t perform_read(uint16_t address) {
return ram_[address];
}
uint16_t perform_aux_read(uint16_t address) {
return static_cast<uint16_t>(ram_[address] | (aux_ram_[address] << 8));
}
private:
uint8_t *ram_;
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
VideoBusHandler video_bus_handler_;
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie>> video_;
int cycles_into_current_line_ = 0;
Cycles cycles_since_video_update_;
@ -92,6 +85,7 @@ class ConcreteMachine:
std::vector<uint8_t> rom_;
std::vector<uint8_t> character_rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
@ -136,11 +130,44 @@ class ConcreteMachine:
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
}
// MARK: - Memory Map
struct MemoryBlock {
uint8_t *read_pointer = nullptr;
uint8_t *write_pointer = nullptr;
} memory_blocks_[4]; // The IO page isn't included.
// MARK: - Memory Map.
/*
The Apple II's paging mechanisms are byzantine to say the least. Painful is
another appropriate adjective.
On a II and II+ there are five distinct zones of memory:
0000 to c000 : the main block of RAM
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it
0400 to 0800 : the text screen, can be configured to write to auxiliary RAM
2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM
c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area
c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest
c800 to d000 : can contain ROM separately from the region below c800
If dealt with as individual blocks in the inner loop, that would therefore imply mapping
an address to one of 13 potential pageable zones. So I've gone reductive and surrendered
to paging every 6502 page of memory independently. It makes the paging events more expensive,
but hopefully more clear.
*/
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
void page(int start, int end, uint8_t *read, uint8_t *write) {
for(int position = start; position < end; ++position) {
read_pages_[position] = read;
if(read) read += 256;
write_pages_[position] = write;
if(write) write += 256;
}
}
// MARK: - The language card.
struct {
@ -151,28 +178,70 @@ class ConcreteMachine:
} language_card_;
bool has_language_card_ = true;
void set_language_card_paging() {
if(has_language_card_ && !language_card_.write) {
memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].write_pointer = &ram_[56*1024];
} else {
memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr;
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
uint8_t *const rom = is_iie ? &rom_[3840] : rom_.data();
page(0xd0, 0xe0,
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_card_.read ? &ram[0xe000] : &rom[0x1000],
language_card_.write ? nullptr : &ram[0xe000]);
}
// MARK - The IIe's ROM controls.
bool internal_CX_rom_ = false;
bool slot_C3_rom_ = false;
bool internal_c8_rom_ = false;
void set_card_paging() {
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
if(!internal_CX_rom_) {
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
}
if(has_language_card_ && language_card_.read) {
memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].read_pointer = &ram_[56*1024];
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
// MARK - The IIe's auxiliary RAM controls.
bool alternative_zero_page_ = false;
void set_zero_page_paging() {
if(alternative_zero_page_) {
read_pages_[0] = aux_ram_;
} else {
memory_blocks_[2].read_pointer = rom_.data();
memory_blocks_[3].read_pointer = rom_.data() + 0x1000;
read_pages_[0] = ram_;
}
read_pages_[1] = read_pages_[0] + 256;
write_pages_[0] = read_pages_[0];
write_pages_[1] = read_pages_[1];
}
bool read_auxiliary_memory_ = false;
bool write_auxiliary_memory_ = false;
void set_main_paging() {
page(0x02, 0xc0,
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
if(video_ && video_->get_80_store()) {
bool use_aux_ram = video_->get_page2();
page(0x04, 0x08,
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
if(video_->get_high_resolution()) {
page(0x20, 0x40,
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK - quick loading
bool should_load_quickly_ = false;
// MARK - joysticks
class Joystick: public Inputs::ConcreteJoystick {
public:
@ -223,10 +292,14 @@ class ConcreteMachine:
return static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1] < analogue_charge_ + analogue_biases_[channel];
}
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
video_bus_handler_(ram_),
video_bus_handler_(ram_, aux_ram_),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
// The system's master clock rate.
@ -248,6 +321,7 @@ class ConcreteMachine:
// Also, start with randomised memory contents.
Memory::Fuzz(ram_, sizeof(ram_));
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
@ -255,14 +329,22 @@ class ConcreteMachine:
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
std::vector<std::string> rom_names = {"apple2-character.rom"};
std::vector<std::string> rom_names;
size_t rom_size = 12*1024;
switch(target.model) {
default:
rom_names.push_back("apple2-character.rom");
rom_names.push_back("apple2o.rom");
break;
case Target::Model::IIplus:
rom_names.push_back("apple2-character.rom");
rom_names.push_back("apple2.rom");
break;
case Target::Model::IIe:
rom_size += 3840;
rom_names.push_back("apple2eu-character.rom");
rom_names.push_back("apple2eu.rom");
break;
}
const auto roms = rom_fetcher("AppleII", rom_names);
@ -270,20 +352,27 @@ class ConcreteMachine:
throw ROMMachine::Error::MissingROMs;
}
character_rom_ = std::move(*roms[0]);
rom_ = std::move(*roms[1]);
if(rom_.size() > 12*1024) {
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
}
character_rom_ = std::move(*roms[0]);
if(target.disk_controller != Target::DiskController::None) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
}
// Set up the default memory blocks.
memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_;
memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200];
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);
// Set proper values for the language card/ROM area.
set_language_card_paging();
insert_media(target.media);
@ -294,7 +383,7 @@ class ConcreteMachine:
}
void setup_output(float aspect_ratio) override {
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie>(video_bus_handler_));
video_->set_character_rom(character_rom_);
}
@ -328,108 +417,18 @@ class ConcreteMachine:
++ stretched_cycles_since_card_update_;
}
/*
There are five distinct zones of memory on an Apple II:
0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe
0200 to c000 : the main block of RAM, which can be paged on a IIe
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
*/
uint16_t accessed_address = address;
MemoryBlock *block = nullptr;
if(address < 0x200) block = &memory_blocks_[0];
else if(address < 0xc000) {
if(address < 0x6000 && !isReadOperation(operation)) update_video();
block = &memory_blocks_[1];
accessed_address -= 0x200;
}
else if(address < 0xd000) block = nullptr;
else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; }
else { block = &memory_blocks_[3]; accessed_address -= 0xe000; }
bool has_updated_cards = false;
if(block) {
if(isReadOperation(operation)) *value = block->read_pointer[accessed_address];
else if(block->write_pointer) block->write_pointer[accessed_address] = *value;
if(read_pages_[address >> 8]) {
if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff];
else if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
if(should_load_quickly_) {
// Check for a prima facie entry into RWTS.
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) {
// Grab the IO control block address for inspection.
uint16_t io_control_block_address =
static_cast<uint16_t>(
(m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) |
m6502_.get_value_of_register(CPU::MOS6502::Register::Y)
);
// Verify that this is table type one, for execution on card six,
// against drive 1 or 2, and that the command is either a seek or a sector read.
if(
ram_[io_control_block_address+0x00] == 0x01 &&
ram_[io_control_block_address+0x01] == 0x60 &&
ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 &&
ram_[io_control_block_address+0x0c] < 2
) {
const uint8_t iob_track = ram_[io_control_block_address+4];
const uint8_t iob_sector = ram_[io_control_block_address+5];
const uint8_t iob_drive = ram_[io_control_block_address+2] - 1;
// Get the track identified and store the new head position.
auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track));
// DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2
// is also kept in that Disk II card's screen hole.
ram_[0x478] = iob_track;
if(ram_[io_control_block_address+0x02] == 1) {
ram_[0x47e] = iob_track;
} else {
ram_[0x4fe] = iob_track;
}
// Check whether this is a read, not merely a seek.
if(ram_[io_control_block_address+0x0c] == 1) {
// Apple the DOS 3.3 formula to map the requested logical sector to a physical sector.
const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15);
// Parse the entire track. TODO: cache these.
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000)));
bool found_sector = false;
for(const auto &pair: sector_map) {
if(pair.second.address.sector == physical_sector) {
found_sector = true;
// Copy the sector contents to their destination.
uint16_t target = static_cast<uint16_t>(
ram_[io_control_block_address+8] |
(ram_[io_control_block_address+9] << 8)
);
for(size_t c = 0; c < 256; ++c) {
ram_[target] = pair.second.data[c];
++target;
}
// Set no error encountered.
ram_[io_control_block_address + 0xd] = 0;
break;
}
}
if(found_sector) {
// Set no error in the flags register too, and RTS.
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
*value = 0x60;
}
} else {
// No error encountered; RTS.
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
*value = 0x60;
}
}
if(is_iie && address >= 0xc300 && address < 0xd000) {
bool internal_c8_rom = internal_c8_rom_;
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
internal_c8_rom &= (address != 0xcfff);
if(internal_c8_rom != internal_c8_rom_) {
internal_c8_rom_ = internal_c8_rom;
set_card_paging();
}
}
} else {
@ -467,12 +466,18 @@ class ConcreteMachine:
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2])
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
(is_iie && open_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc062: // Switch input 1.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1])
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
(is_iie && closed_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc063: // Switch input 2.
@ -491,9 +496,80 @@ class ConcreteMachine:
*value |= 0x80;
}
} break;
// The IIe-only state reads follow...
case 0xc011: if(is_iie) *value = (*value & 0x7f) | (language_card_.bank1 ? 0x80 : 0x00); break;
case 0xc012: if(is_iie) *value = (*value & 0x7f) | (language_card_.read ? 0x80 : 0x00); break;
case 0xc013: if(is_iie) *value = (*value & 0x7f) | (read_auxiliary_memory_ ? 0x80 : 0x00); break;
case 0xc014: if(is_iie) *value = (*value & 0x7f) | (write_auxiliary_memory_ ? 0x80 : 0x00); break;
case 0xc015: if(is_iie) *value = (*value & 0x7f) | (internal_CX_rom_ ? 0x80 : 0x00); break;
case 0xc016: if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00); break;
case 0xc017: if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00); break;
case 0xc018: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00); break;
case 0xc019: if(is_iie) *value = (*value & 0x7f) | (video_->get_is_vertical_blank(cycles_since_video_update_) ? 0x00 : 0x80); break;
case 0xc01a: if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00); break;
case 0xc01b: if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00); break;
case 0xc01c: if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00); break;
case 0xc01d: if(is_iie) *value = (*value & 0x7f) | (video_->get_high_resolution() ? 0x80 : 0x00); break;
case 0xc01e: if(is_iie) *value = (*value & 0x7f) | (video_->get_alternative_character_set() ? 0x80 : 0x00); break;
case 0xc01f: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_columns() ? 0x80 : 0x00); break;
case 0xc07f: if(is_iie) *value = (*value & 0x7f) | (video_->get_double_high_resolution() ? 0x80 : 0x00); break;
}
} else {
// Write-only switches.
// Write-only switches. All IIe as currently implemented.
if(is_iie) {
switch(address) {
default: break;
case 0xc000:
case 0xc001:
video_->set_80_store(!!(address&1));
set_main_paging();
break;
case 0xc002:
case 0xc003:
read_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc004:
case 0xc005:
write_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc006:
case 0xc007:
internal_CX_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc008:
case 0xc009:
// The alternative zero page setting affects both bank 0 and any RAM
// that's paged as though it were on a language card.
alternative_zero_page_ = !!(address&1);
set_zero_page_paging();
set_language_card_paging();
break;
case 0xc00a:
case 0xc00b:
slot_C3_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc00c:
case 0xc00d: video_->set_80_columns(!!(address&1)); break;
case 0xc00e:
case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break;
case 0xc05e:
case 0xc05f: video_->set_double_high_resolution(!(address&1)); break;
}
}
}
break;
@ -510,14 +586,25 @@ class ConcreteMachine:
} break;
/* Read-write switches. */
case 0xc050: update_video(); video_->set_graphics_mode(); break;
case 0xc051: update_video(); video_->set_text_mode(); break;
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
case 0xc054: update_video(); video_->set_video_page(0); break;
case 0xc055: update_video(); video_->set_video_page(1); break;
case 0xc056: update_video(); video_->set_low_resolution(); break;
case 0xc057: update_video(); video_->set_high_resolution(); break;
case 0xc050:
case 0xc051:
update_video();
video_->set_text(!!(address&1));
break;
case 0xc052: update_video(); video_->set_mixed(false); break;
case 0xc053: update_video(); video_->set_mixed(true); break;
case 0xc054:
case 0xc055:
update_video();
video_->set_page2(!!(address&1));
set_main_paging();
break;
case 0xc056:
case 0xc057:
update_video();
video_->set_high_resolution(!!(address&1));
set_main_paging();
break;
case 0xc010:
keyboard_input_ &= 0x7f;
@ -525,6 +612,11 @@ class ConcreteMachine:
if(!string_serialiser_->advance())
string_serialiser_.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
}
break;
case 0xc030:
@ -556,6 +648,7 @@ class ConcreteMachine:
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
set_language_card_paging();
break;
}
@ -564,7 +657,7 @@ class ConcreteMachine:
Communication with cards follows.
*/
if(address >= 0xc090 && address < 0xc800) {
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
// If this is a card access, figure out which card is at play before determining
// the totality of who needs messaging.
size_t card_number = 0;
@ -589,7 +682,7 @@ class ConcreteMachine:
// If the selected card is a just-in-time card, update the just-in-time cards,
// and then message it specifically.
const bool is_read = isReadOperation(operation);
AppleII::Card *const target = cards_[card_number].get();
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
if(target && !is_every_cycle_card(target)) {
update_just_in_time_cards();
target->perform_bus_operation(select, is_read, address, value);
@ -633,24 +726,46 @@ class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys() override {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
if(key == Key::F12) {
m6502_.set_reset_line(is_pressed);
switch(key) {
default: break;
case Key::F12:
m6502_.set_reset_line(is_pressed);
return;
case Key::LeftOption:
open_apple_is_pressed_ = is_pressed;
return;
case Key::RightOption:
closed_apple_is_pressed_ = is_pressed;
return;
}
if(is_pressed) {
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 8; break;
case Key::Right: value = 21; break;
case Key::Down: value = 10; break;
default: break;
}
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
default: return;
}
}
keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80);
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie) value = static_cast<char>(toupper(value));
if(is_pressed) {
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
}
@ -678,30 +793,6 @@ class ConcreteMachine:
}
}
// MARK: Options
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return AppleII::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
should_load_quickly_ = quickload;
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
return selection_set;
}
// MARK: JoystickMachine
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
@ -715,8 +806,11 @@ using namespace AppleII;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;
const Target *const appleii_target = dynamic_cast<const Target *>(target);
return new ConcreteMachine(*appleii_target, rom_fetcher);
if(appleii_target->model == Target::Model::IIe) {
return new ConcreteMachine<true>(*appleii_target, rom_fetcher);
} else {
return new ConcreteMachine<false>(*appleii_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@ -18,9 +18,6 @@
namespace AppleII {
/// @returns The options available for an Apple II.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine {
public:
virtual ~Machine();

View File

@ -18,7 +18,7 @@ VideoBase::VideoBase() :
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"return texture(sampler, coordinate).r;"
"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);"
"}");
// Show only the centre 75% of the TV frame.
@ -31,30 +31,87 @@ Outputs::CRT::CRT *VideoBase::get_crt() {
return crt_.get();
}
void VideoBase::set_graphics_mode() {
use_graphics_mode_ = true;
/*
Rote setters and getters.
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
alternative_character_set_ = alternative_character_set;
}
void VideoBase::set_text_mode() {
use_graphics_mode_ = false;
bool VideoBase::get_alternative_character_set() {
return alternative_character_set_;
}
void VideoBase::set_mixed_mode(bool mixed_mode) {
mixed_mode_ = mixed_mode;
void VideoBase::set_80_columns(bool columns_80) {
columns_80_ = columns_80;
}
void VideoBase::set_video_page(int page) {
video_page_ = page;
bool VideoBase::get_80_columns() {
return columns_80_;
}
void VideoBase::set_low_resolution() {
graphics_mode_ = GraphicsMode::LowRes;
void VideoBase::set_80_store(bool store_80) {
store_80_ = store_80;
}
void VideoBase::set_high_resolution() {
graphics_mode_ = GraphicsMode::HighRes;
bool VideoBase::get_80_store() {
return store_80_;
}
void VideoBase::set_page2(bool page2) {
page2_ = page2;
}
bool VideoBase::get_page2() {
return page2_;
}
void VideoBase::set_text(bool text) {
text_ = text;
}
bool VideoBase::get_text() {
return text_;
}
void VideoBase::set_mixed(bool mixed) {
mixed_ = mixed;
}
bool VideoBase::get_mixed() {
return mixed_;
}
void VideoBase::set_high_resolution(bool high_resolution) {
high_resolution_ = high_resolution;
}
bool VideoBase::get_high_resolution() {
return high_resolution_;
}
void VideoBase::set_double_high_resolution(bool double_high_resolution) {
double_high_resolution_ = double_high_resolution;
}
bool VideoBase::get_double_high_resolution() {
return double_high_resolution_;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom;
// Flip all character contents based on the second line of the $ graphic.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}

View File

@ -19,9 +19,21 @@ namespace Video {
class BusHandler {
public:
/*!
Reads an 8-bit value from the ordinary II/II+ memory pool.
*/
uint8_t perform_read(uint16_t address) {
return 0xff;
}
/*!
Reads two 8-bit values, from the same address one from
main RAM, one from auxiliary. Should return as
(main) | (aux << 8).
*/
uint16_t perform_aux_read(uint16_t address) {
return 0xffff;
}
};
class VideoBase {
@ -31,13 +43,107 @@ class VideoBase {
/// @returns The CRT this video feed is feeding.
Outputs::CRT::CRT *get_crt();
// Inputs for the various soft switches.
void set_graphics_mode();
void set_text_mode();
void set_mixed_mode(bool);
void set_video_page(int);
void set_low_resolution();
void set_high_resolution();
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool);
bool get_alternative_character_set();
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool);
bool get_80_columns();
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool);
bool get_80_store();
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool);
bool get_page2();
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool);
bool get_text();
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool);
bool get_mixed();
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool);
bool get_high_resolution();
/*!
Setter for DHIRES ($C05E/$C05F; triggers on write only).
* On: turn on double-high resolution.
* Off: turn off double-high resolution.
DHIRES doesn't exist on a II/II+. On the IIe there is another
register usually grouped with the graphics setters called IOUDIS
that affects visibility of this switch. But it has no effect on
video, so it's not modelled by this class.
*/
void set_double_high_resolution(bool);
bool get_double_high_resolution();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
@ -45,25 +151,47 @@ class VideoBase {
protected:
std::unique_ptr<Outputs::CRT::CRT> crt_;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
int pixel_pointer_column_ = 0;
bool pixels_are_high_density_ = false;
int video_page_ = 0;
// State affecting logical state.
int row_ = 0, column_ = 0, flash_ = 0;
std::vector<uint8_t> character_rom_;
// Enumerates all Apple II and IIe display modes.
enum class GraphicsMode {
LowRes,
DoubleLowRes,
HighRes,
Text
} graphics_mode_ = GraphicsMode::LowRes;
bool use_graphics_mode_ = false;
bool mixed_mode_ = false;
DoubleHighRes,
Text,
DoubleText
};
bool is_text_mode(GraphicsMode m) { return m >= GraphicsMode::Text; }
// Various soft-switch values.
bool alternative_character_set_ = false;
bool columns_80_ = false;
bool store_80_ = false;
bool page2_ = false;
bool text_ = true;
bool mixed_ = false;
bool high_resolution_ = false;
bool double_high_resolution_ = false;
// Graphics carry is the final level output in a fetch window;
// it carries on into the next if it's high resolution with
// the delay bit set.
uint8_t graphics_carry_ = 0;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
};
template <class BusHandler> class Video: public VideoBase {
template <class BusHandler, bool is_iie> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
@ -108,15 +236,14 @@ template <class BusHandler> class Video: public VideoBase {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
}
} else {
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
const GraphicsMode line_mode = graphics_mode(row_);
// The first 40 columns are submitted to the CRT only upon completion;
// they'll be either graphics or blank, depending on which side we are
// of line 192.
if(column_ < 40) {
if(row_ < 192) {
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
bool requires_high_density = pixel_mode != GraphicsMode::Text;
const bool requires_high_density = line_mode != GraphicsMode::Text;
if(!column_ || requires_high_density != pixels_are_high_density_) {
if(column_) output_data_to_column(column_);
pixel_pointer_ = crt_->allocate_write_area(561);
@ -129,21 +256,25 @@ template <class BusHandler> class Video: public VideoBase {
const int character_row = row_ >> 3;
const int pixel_row = row_ & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
switch(pixel_mode) {
switch(line_mode) {
case GraphicsMode::Text: {
const uint8_t inverses[] = {
0xff,
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
alternative_character_set_ ? static_cast<uint8_t>(0xff) : static_cast<uint8_t>((flash_ / flash_length) * 0xff),
0x00,
0x00
};
const uint8_t masks[] = {
alternative_character_set_ ? static_cast<uint8_t>(0x7f) : static_cast<uint8_t>(0x3f),
is_iie ? 0x7f : 0x3f,
};
for(int c = column_; c < pixel_end; ++c) {
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
const uint8_t xor_mask = inverses[character >> 6];
const std::size_t character_address = static_cast<std::size_t>(((character & masks[character >> 7]) << 3) + pixel_row);
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
// The character ROM is output MSB to LSB rather than LSB to MSB.
pixel_pointer_[0] = character_pattern & 0x40;
@ -153,11 +284,82 @@ template <class BusHandler> class Video: public VideoBase {
pixel_pointer_[4] = character_pattern & 0x04;
pixel_pointer_[5] = character_pattern & 0x02;
pixel_pointer_[6] = character_pattern & 0x01;
graphics_carry_ = character_pattern & 0x40;
graphics_carry_ = character_pattern & 0x01;
pixel_pointer_ += 7;
}
} break;
case GraphicsMode::DoubleText: {
const uint8_t inverses[] = {
0xff,
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
0x00,
0x00
};
for(int c = column_; c < pixel_end; ++c) {
const uint16_t characters = bus_handler_.perform_aux_read(static_cast<uint16_t>(text_address + c));
const std::size_t character_addresses[2] = {
static_cast<std::size_t>((((characters >> 8) & 0x3f) << 3) + pixel_row),
static_cast<std::size_t>(((characters & 0x3f) << 3) + pixel_row),
};
const uint8_t character_patterns[2] = {
static_cast<uint8_t>(character_rom_[character_addresses[0]] ^ inverses[(characters >> 14) & 3]),
static_cast<uint8_t>(character_rom_[character_addresses[1]] ^ inverses[(characters >> 6) & 3]),
};
// The character ROM is output MSB to LSB rather than LSB to MSB.
pixel_pointer_[0] = character_patterns[0] & 0x40;
pixel_pointer_[1] = character_patterns[0] & 0x20;
pixel_pointer_[2] = character_patterns[0] & 0x10;
pixel_pointer_[3] = character_patterns[0] & 0x08;
pixel_pointer_[4] = character_patterns[0] & 0x04;
pixel_pointer_[5] = character_patterns[0] & 0x02;
pixel_pointer_[6] = character_patterns[0] & 0x01;
pixel_pointer_[7] = character_patterns[1] & 0x40;
pixel_pointer_[8] = character_patterns[1] & 0x20;
pixel_pointer_[9] = character_patterns[1] & 0x10;
pixel_pointer_[10] = character_patterns[1] & 0x08;
pixel_pointer_[11] = character_patterns[1] & 0x04;
pixel_pointer_[12] = character_patterns[1] & 0x02;
pixel_pointer_[13] = character_patterns[1] & 0x01;
graphics_carry_ = character_patterns[1] & 0x01;
pixel_pointer_ += 14;
}
} break;
case GraphicsMode::DoubleLowRes: {
const int row_shift = (row_&4);
for(int c = column_; c < pixel_end; ++c) {
const uint16_t nibble = (bus_handler_.perform_aux_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0xf0f;
if(c&1) {
pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 4;
pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 8;
pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 1;
pixel_pointer_[3] = (nibble >> 8) & 2;
pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4;
pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8;
pixel_pointer_[10] = nibble & 1;
pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2;
graphics_carry_ = nibble & 8;
} else {
pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 1;
pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 2;
pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 4;
pixel_pointer_[3] = (nibble >> 8) & 8;
pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1;
pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2;
pixel_pointer_[10] = nibble & 4;
pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8;
graphics_carry_ = nibble & 2;
}
pixel_pointer_ += 14;
}
} break;
case GraphicsMode::LowRes: {
const int row_shift = (row_&4);
// TODO: decompose into two loops, possibly.
@ -184,7 +386,7 @@ template <class BusHandler> class Video: public VideoBase {
} break;
case GraphicsMode::HighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const uint16_t graphics_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) {
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
@ -212,6 +414,30 @@ template <class BusHandler> class Video: public VideoBase {
pixel_pointer_ += 14;
}
} break;
case GraphicsMode::DoubleHighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) {
const uint16_t graphic = bus_handler_.perform_aux_read(static_cast<uint16_t>(graphics_address + c));
pixel_pointer_[0] = graphics_carry_;
pixel_pointer_[1] = (graphic >> 8) & 0x01;
pixel_pointer_[2] = (graphic >> 8) & 0x02;
pixel_pointer_[3] = (graphic >> 8) & 0x04;
pixel_pointer_[4] = (graphic >> 8) & 0x08;
pixel_pointer_[5] = (graphic >> 8) & 0x10;
pixel_pointer_[6] = (graphic >> 8) & 0x20;
pixel_pointer_[7] = (graphic >> 8) & 0x40;
pixel_pointer_[8] = graphic & 0x01;
pixel_pointer_[9] = graphic & 0x02;
pixel_pointer_[10] = graphic & 0x04;
pixel_pointer_[11] = graphic & 0x08;
pixel_pointer_[12] = graphic & 0x10;
pixel_pointer_[13] = graphic & 0x20;
graphics_carry_ = graphic & 0x40;
pixel_pointer_ += 14;
}
} break;
}
if(ending_column >= 40) {
@ -242,11 +468,11 @@ template <class BusHandler> class Video: public VideoBase {
}
int second_blank_start;
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
if(!is_text_mode(graphics_mode(row_+1))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14);
crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 128);
}
second_blank_start = std::max(first_sync_column + 7, column_);
@ -310,16 +536,49 @@ template <class BusHandler> class Video: public VideoBase {
return bus_handler_.perform_read(read_address);
}
/*!
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
*/
bool get_is_vertical_blank(Cycles offset) {
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
int mapped_column = column_ + offset.as_int();
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter and test it for location.
int mapped_row = row_ + (mapped_column / 65);
return (mapped_row % 262) >= 192;
}
private:
GraphicsMode graphics_mode(int row) {
if(text_) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(mixed_ && row >= 160 && row < 192) {
return (columns_80_ || double_high_resolution_) ? GraphicsMode::DoubleText : GraphicsMode::Text;
}
if(high_resolution_) {
return double_high_resolution_ ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
return double_high_resolution_ ? GraphicsMode::DoubleLowRes : GraphicsMode::LowRes;
}
}
int video_page() {
return (store_80_ || !page2_) ? 0 : 1;
}
uint16_t get_row_address(int row) {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
return (pixel_mode == GraphicsMode::HighRes) ?
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
}
static const int flash_length = 8406;

View File

@ -130,7 +130,6 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));

View File

@ -12,7 +12,8 @@
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
CSMachineAppleIIModelAppleII,
CSMachineAppleIIModelAppleIIPlus
CSMachineAppleIIModelAppleIIPlus,
CSMachineAppleIIModelAppleIIe
};
typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) {

View File

@ -168,7 +168,11 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
using Target = Analyser::Static::AppleII::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::AppleII;
target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus;
switch(model) {
default: target->model = Target::Model::II; break;
case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break;
case CSMachineAppleIIModelAppleIIe: target->model = Target::Model::IIe; break;
}
switch(diskController) {
default:
case CSMachineAppleIIDiskControllerNone: target->disk_controller = Target::DiskController::None; break;
@ -184,7 +188,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
- (NSString *)optionsPanelNibName {
switch(_targets.front()->machine) {
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
case Analyser::Machine::AppleII: return @"AppleIIOptions";
// case Analyser::Machine::AppleII: return @"AppleIIOptions";
case Analyser::Machine::Atari2600: return @"Atari2600Options";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -64,11 +64,11 @@ Gw
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="33" width="554" height="93"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="15" y="71" width="46" height="17"/>
<rect key="frame" x="15" y="72" width="46" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -76,7 +76,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<rect key="frame" x="15" y="40" width="96" height="17"/>
<rect key="frame" x="15" y="41" width="96" height="17"/>
<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"/>
@ -84,7 +84,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
<rect key="frame" x="65" y="66" width="91" height="26"/>
<rect key="frame" x="65" y="67" width="91" height="26"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -92,12 +92,13 @@ Gw
<items>
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
<menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/>
<menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
<rect key="frame" x="115" y="35" width="132" height="26"/>
<rect key="frame" x="115" y="36" width="132" height="26"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -128,11 +129,11 @@ Gw
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="33" width="554" height="99"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="65" y="72" width="94" height="26"/>
<rect key="frame" x="65" y="67" width="94" height="26"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -146,7 +147,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="15" y="77" width="46" height="17"/>
<rect key="frame" x="15" y="72" width="46" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@ -130,6 +130,7 @@ class MachinePicker: NSObject {
var model: CSMachineAppleIIModel = .appleII
switch appleIIModelButton!.selectedTag() {
case 1: model = .appleIIPlus
case 2: model = .appleIIe
case 0: fallthrough
default: model = .appleII
}

View File

@ -2,5 +2,12 @@ ROM files would ordinarily go here; they are copyright Apple so are not included
Expected files:
apple2o.rom — a 12kb image of the original Apple II's ROMs.
apple2-character.rom — a 2kb image of the Apple II+'s character ROM.
apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM.
apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM.
apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the Enhanced IIe ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100.
apple2eu.rom — as per apple2e.rom, but for the Unenhanced Apple II.
apple2-character.rom — a 2kb image of the Apple IIe's character ROM.
apple2eu-character.rom — a 4kb image of the Unenhanced IIe's character ROM.
Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM.

View File

@ -117,20 +117,22 @@ bool Drive::get_is_ready() {
}
void Drive::set_motor_on(bool motor_is_on) {
motor_is_on_ = motor_is_on;
if(motor_is_on_ != motor_is_on) {
motor_is_on_ = motor_is_on;
if(observer_) {
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
if(announce_motor_led_) {
observer_->set_led_status(drive_name_, motor_is_on_);
if(observer_) {
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
if(announce_motor_led_) {
observer_->set_led_status(drive_name_, motor_is_on_);
}
}
}
if(!motor_is_on) {
ready_index_count_ = 0;
if(disk_) disk_->flush_tracks();
if(!motor_is_on) {
ready_index_count_ = 0;
if(disk_) disk_->flush_tracks();
}
update_clocking_observer();
}
update_clocking_observer();
}
bool Drive::get_motor_on() {