diff --git a/Analyser/Static/AppleII/Target.hpp b/Analyser/Static/AppleII/Target.hpp index 6ac5616ad..25c4e78cf 100644 --- a/Analyser/Static/AppleII/Target.hpp +++ b/Analyser/Static/AppleII/Target.hpp @@ -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; }; diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index d02afdb37..e915b61a7 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -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(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 &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; diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index ccb3ce491..dfd5b364a 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -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 state_machine_; @@ -121,6 +122,7 @@ class DiskII: ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; uint8_t data_input_ = 0; + int flux_duration_ = 0; }; } diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index f6ed1eb20..c8228009e 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -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 #include #include -std::vector> AppleII::get_options() { - std::vector> options; - options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload")); - return options; -} - namespace { -class ConcreteMachine: +template 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(ram_[address] | (aux_ram_[address] << 8)); + } private: - uint8_t *ram_; + uint8_t *ram_, *aux_ram_; }; CPU::MOS6502::Processor m6502_; VideoBusHandler video_bus_handler_; - std::unique_ptr> video_; + std::unique_ptr> video_; int cycles_into_current_line_ = 0; Cycles cycles_since_video_update_; @@ -92,6 +85,7 @@ class ConcreteMachine: std::vector rom_; std::vector 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(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 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(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 rom_names = {"apple2-character.rom"}; + std::vector 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(rom_.size()) - 12*1024); + if(rom_.size() > rom_size) { + rom_.erase(rom_.begin(), rom_.end() - static_cast(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(video_bus_handler_)); + video_.reset(new AppleII::Video::Video(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( - (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( - 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(joysticks_[0].get())->buttons[0] || static_cast(joysticks_[1].get())->buttons[2]) + if( + static_cast(joysticks_[0].get())->buttons[0] || static_cast(joysticks_[1].get())->buttons[2] || + (is_iie && open_apple_is_pressed_) + ) *value |= 0x80; break; case 0xc062: // Switch input 1. *value &= 0x7f; - if(static_cast(joysticks_[0].get())->buttons[1] || static_cast(joysticks_[1].get())->buttons[1]) + if( + static_cast(joysticks_[0].get())->buttons[1] || static_cast(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(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(toupper(value) | 0x80); + // Prior to the IIe, the keyboard could produce uppercase only. + if(!is_iie) value = static_cast(toupper(value)); + + if(is_pressed) { + keyboard_input_ = static_cast(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> 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> &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(target); - return new ConcreteMachine(*appleii_target, rom_fetcher); + if(appleii_target->model == Target::Model::IIe) { + return new ConcreteMachine(*appleii_target, rom_fetcher); + } else { + return new ConcreteMachine(*appleii_target, rom_fetcher); + } } Machine::~Machine() {} - diff --git a/Machines/AppleII/AppleII.hpp b/Machines/AppleII/AppleII.hpp index c680b3163..b0b8180aa 100644 --- a/Machines/AppleII/AppleII.hpp +++ b/Machines/AppleII/AppleII.hpp @@ -18,9 +18,6 @@ namespace AppleII { -/// @returns The options available for an Apple II. -std::vector> get_options(); - class Machine { public: virtual ~Machine(); diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 552bad130..9cbfc7302 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -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 &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); + } + } } diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index ee7aa2607..92d94bdb4 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -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 &); @@ -45,25 +151,47 @@ class VideoBase { protected: std::unique_ptr 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 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 character_rom_; }; -template class Video: public VideoBase { +template 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 Video: public VideoBase { crt_->output_sync(static_cast(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 Video: public VideoBase { const int character_row = row_ >> 3; const int pixel_row = row_ & 7; const uint16_t row_address = static_cast((character_row >> 3) * 40 + ((character_row&7) << 7)); - const uint16_t text_address = static_cast(((video_page_+1) * 0x400) + row_address); + const uint16_t text_address = static_cast(((video_page()+1) * 0x400) + row_address); - switch(pixel_mode) { + switch(line_mode) { case GraphicsMode::Text: { const uint8_t inverses[] = { 0xff, - static_cast((flash_ / flash_length) * 0xff), + alternative_character_set_ ? static_cast(0xff) : static_cast((flash_ / flash_length) * 0xff), 0x00, 0x00 }; + const uint8_t masks[] = { + alternative_character_set_ ? static_cast(0x7f) : static_cast(0x3f), + is_iie ? 0x7f : 0x3f, + }; for(int c = column_; c < pixel_end; ++c) { const uint8_t character = bus_handler_.perform_read(static_cast(text_address + c)); - const std::size_t character_address = static_cast(((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(((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 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((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(text_address + c)); + const std::size_t character_addresses[2] = { + static_cast((((characters >> 8) & 0x3f) << 3) + pixel_row), + static_cast(((characters & 0x3f) << 3) + pixel_row), + }; + + const uint8_t character_patterns[2] = { + static_cast(character_rom_[character_addresses[0]] ^ inverses[(characters >> 14) & 3]), + static_cast(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(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 Video: public VideoBase { } break; case GraphicsMode::HighRes: { - const uint16_t graphics_address = static_cast(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); + const uint16_t graphics_address = static_cast(((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(graphics_address + c)); @@ -212,6 +414,30 @@ template class Video: public VideoBase { pixel_pointer_ += 14; } } break; + + case GraphicsMode::DoubleHighRes: { + const uint16_t graphics_address = static_cast(((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(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 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(colour_burst_end - colour_burst_start) * 14); + crt_->output_colour_burst(static_cast(colour_burst_end - colour_burst_start) * 14, 128); } second_blank_start = std::max(first_sync_column + 7, column_); @@ -310,16 +536,49 @@ template 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((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(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : - static_cast(((video_page_+1) * 0x400) + row_address); + const GraphicsMode pixel_mode = graphics_mode(row); + return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? + static_cast(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : + static_cast(((video_page()+1) * 0x400) + row_address); } static const int flash_length = 8406; diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index b6c2105a2..4d61e701e 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -130,7 +130,6 @@ std::map>> Machin std::map>> 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())); diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index e183d7217..943656c6c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -12,7 +12,8 @@ typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { CSMachineAppleIIModelAppleII, - CSMachineAppleIIModelAppleIIPlus + CSMachineAppleIIModelAppleIIPlus, + CSMachineAppleIIModelAppleIIe }; typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index fee228e7e..b804d2c59 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -168,7 +168,11 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K using Target = Analyser::Static::AppleII::Target; std::unique_ptr 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"; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 40721d21a..52727ce02 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -64,11 +64,11 @@ Gw - + - + @@ -76,7 +76,7 @@ Gw - + @@ -84,7 +84,7 @@ Gw - + @@ -92,12 +92,13 @@ Gw + - + @@ -128,11 +129,11 @@ Gw - + - + @@ -146,7 +147,7 @@ Gw - + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index 2bbd83287..da9a669e6 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -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 } diff --git a/ROMImages/AppleII/readme.txt b/ROMImages/AppleII/readme.txt index 397b6905e..0cb8c1131 100644 --- a/ROMImages/AppleII/readme.txt +++ b/ROMImages/AppleII/readme.txt @@ -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index cce6362b3..99fba1182 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -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() {