From 1b86bc21ab9c5235986a44ab96434b450d62bbee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Aug 2017 17:58:54 -0400 Subject: [PATCH 01/36] Might as well get official on my ongoing efforts at CPC emulation. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21eec8f70..8491654f4 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ So its aims are: It currently contains emulations of the: * Acorn Electron; +* Amstrad CPC; * Atari 2600; -* Oric 1/Atmos; -* Commodore Vic-20 (and Commodore 1540/1); and +* Commodore Vic-20 (and Commodore 1540/1); +* Oric 1/Atmos; and * Sinclair ZX80/81. ## Single-click Loading From 021ff8674e7791aaf5382d6fc97c54585d1b361a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Aug 2017 20:30:54 -0400 Subject: [PATCH 02/36] Added something for sense drive status. --- Components/8272/i8272.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index a98aab74e..b8daf62b2 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -416,8 +416,17 @@ void i8272::posit_event(int event_type) { goto wait_for_command; sense_drive_status: - printf("Sense drive status unimplemented!!\n"); - goto wait_for_command; + { + int drive = command_[1] & 3; + result_stack_.push_back( + (command_[1] & 7) | // drive and head number + 0x08 | // single sided + (drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) | + (drives_[drive].drive->has_disk() ? 0x20 : 0x00) | // ready, approximately (TODO) + 0x40 // write protected + ); + } + goto post_result; // Performs any invalid command. invalid: From b11d142cff980f556c40bdd93542551a6eff637d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Aug 2017 20:35:41 -0400 Subject: [PATCH 03/36] Switched to descriptive names. --- Components/8272/i8272.cpp | 62 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index b8daf62b2..47322dbcc 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -13,10 +13,10 @@ using namespace Intel; namespace { -const uint8_t StatusRQM = 0x80; // Set: ready to send or receive from processor. -const uint8_t StatusDIO = 0x40; // Set: data is expected to be taken from the 8272 by the processor. -const uint8_t StatusNDM = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled. -const uint8_t StatusCB = 0x10; // Set: the FDC is busy. +const uint8_t StatusRequest = 0x80; // Set: ready to send or receive from processor. +const uint8_t StatusDirection = 0x40; // Set: data is expected to be taken from the 8272 by the processor. +const uint8_t StatusNonDMAExecuting = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled. +const uint8_t StatusBusy = 0x10; // Set: the FDC is busy. //const uint8_t StatusD3B = 0x08; // Set: drive 3 is seeking. //const uint8_t StatusD2B = 0x04; // Set: drive 2 is seeking. //const uint8_t StatusD1B = 0x02; // Set: drive 1 is seeking. @@ -25,7 +25,7 @@ const uint8_t StatusCB = 0x10; // Set: the FDC is busy. i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), - main_status_(StatusRQM), + main_status_(0), interesting_event_mask_((int)Event8272::CommandByte), resume_point_(0), delay_time_(0), @@ -76,7 +76,7 @@ void i8272::set_register(int address, uint8_t value) { if(!address) return; // if not ready for commands, do nothing - if(!(main_status_ & StatusRQM)) return; + if(!(main_status_ & StatusRequest)) return; // accumulate latest byte in the command byte sequence command_.push_back(value); @@ -135,7 +135,7 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { set_data_mode(Scanning); #define SET_DRIVE_HEAD_MFM() \ - if(!dma_mode_) main_status_ |= StatusNDM; \ + if(!dma_mode_) main_status_ |= StatusNonDMAExecuting; \ set_drive(drives_[command_[1]&3].drive); \ set_is_double_density(command_[0] & 0x40); \ invalidate_track(); @@ -150,94 +150,94 @@ void i8272::posit_event(int event_type) { // into wait_for_complete_command_sequence. wait_for_command: set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); - main_status_ &= ~(StatusCB | StatusNDM); + main_status_ &= ~(StatusBusy | StatusNonDMAExecuting); command_.clear(); // Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes // until it has a quantity that make up an entire command, then resets the data request bit and // branches to that command. wait_for_complete_command_sequence: - main_status_ |= StatusRQM; + main_status_ |= StatusRequest; WAIT_FOR_EVENT(Event8272::CommandByte) - main_status_ |= StatusCB; + main_status_ |= StatusBusy; switch(command_[0] & 0x1f) { case 0x06: // read data if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto read_data; case 0x0b: // read deleted data if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto read_deleted_data; case 0x05: // write data if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto write_data; case 0x09: // write deleted data if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto write_deleted_data; case 0x02: // read track if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto read_track; case 0x0a: // read ID if(command_.size() < 2) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto read_id; case 0x0d: // format track if(command_.size() < 6) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto format_track; case 0x11: // scan low if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto scan_low; case 0x19: // scan low or equal if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto scan_low_or_equal; case 0x1d: // scan high or equal if(command_.size() < 9) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto scan_high_or_equal; case 0x07: // recalibrate if(command_.size() < 2) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto recalibrate; case 0x08: // sense interrupt status - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto sense_interrupt_status; case 0x03: // specify if(command_.size() < 3) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto specify; case 0x04: // sense drive status if(command_.size() < 2) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto sense_drive_status; case 0x0f: // seek if(command_.size() < 3) goto wait_for_complete_command_sequence; - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto seek; default: // invalid - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; goto invalid; } @@ -277,9 +277,9 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::Token); result_stack_.push_back(get_latest_token().byte_value); distance_into_section_++; - main_status_ |= StatusRQM | StatusDIO; + main_status_ |= StatusRequest | StatusDirection; WAIT_FOR_EVENT(Event8272::ResultEmpty); - main_status_ &= ~StatusRQM; + main_status_ &= ~StatusRequest; if(distance_into_section_ < (128 << size_)) goto get_byte; // read CRC, without transferring it @@ -451,15 +451,15 @@ void i8272::posit_event(int event_type) { // last thing in it will be returned first. post_result: // Set ready to send data to the processor, no longer in non-DMA execution phase. - main_status_ |= StatusRQM | StatusDIO; - main_status_ &= ~StatusNDM; + main_status_ |= StatusRequest | StatusDirection; + main_status_ &= ~StatusNonDMAExecuting; // The actual stuff of unwinding result_stack_ is handled by ::get_register; wait // until the processor has read all result bytes. WAIT_FOR_EVENT(Event8272::ResultEmpty); // Reset data direction and end the command. - main_status_ &= ~StatusDIO; + main_status_ &= ~StatusDirection; goto wait_for_command; END_SECTION() From 7d1023ea98bd9464dd4bd10209bc4d5ef7dda83a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Aug 2017 21:15:56 -0400 Subject: [PATCH 04/36] Added a 'ready' getter to `Drive`, formally to let the drive take ownership of that test. --- Storage/Disk/Drive.cpp | 5 +++++ Storage/Disk/Drive.hpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index a08398a47..1911d7916 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -46,6 +46,11 @@ bool Drive::get_is_read_only() { return false; } +bool Drive::get_is_ready() { + // TODO: a real test for this. + return disk_ != nullptr; +} + std::shared_ptr Drive::get_track() { if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); if(track_) return track_; diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 82e34fcd6..c67f5d1b6 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -65,6 +65,11 @@ class Drive { */ void set_track(const std::shared_ptr &track); + /*! + @returns @c true if the drive is ready; @c false otherwise. + */ + bool get_is_ready(); + private: std::shared_ptr track_; std::shared_ptr disk_; From 2eed24e859678b03606ef6e3ba57fcce256688db Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 11:11:26 -0400 Subject: [PATCH 05/36] Made an initial attempt at [a subset of] multi-sector reads. --- Components/8272/i8272.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 47322dbcc..e4c68be49 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -253,6 +253,8 @@ void i8272::posit_event(int event_type) { sector_ = command_[4]; size_ = command_[5]; + read_next_data: + // Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until // the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the // values in the internal registers. @@ -286,6 +288,12 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::Token); WAIT_FOR_EVENT(Event::Token); + // check whether that's it + if(sector_ != command_[6]) { + sector_++; + goto read_next_data; + } + // For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N goto post_st012chrn; From a4c910f1dea2d716576f1e3b53ddb62d72eb4482 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 11:12:53 -0400 Subject: [PATCH 06/36] =?UTF-8?q?This=20appears=20to=20be=20a=20more=20acc?= =?UTF-8?q?urate=20take=20on=206845=20address=20advancement=20=E2=80=94=20?= =?UTF-8?q?it=20is=20necessary=20that=20character=20output=20has=20finishe?= =?UTF-8?q?d=20for=20the=20line=20address=20to=20be=20updated.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Components/6845/CRTC6845.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 2e113ed9e..15a50e6ba 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -71,9 +71,6 @@ template class CRTC6845 { // check for end-of-line if(is_end_of_line) { - character_counter_ = 0; - character_is_visible_ = true; - // check for end of vertical sync if(vsync_down_counter_) { vsync_down_counter_--; @@ -94,7 +91,8 @@ template class CRTC6845 { } else { // advance vertical counter if(bus_state_.row_address == registers_[9]) { - line_address_ = bus_state_.refresh_address; + if(!character_is_visible_) + line_address_ = bus_state_.refresh_address; bus_state_.row_address = 0; bool is_at_end_of_frame = line_counter_ == registers_[4]; @@ -126,9 +124,12 @@ template class CRTC6845 { } } else { bus_state_.row_address++; - bus_state_.refresh_address = line_address_; } + bus_state_.refresh_address = line_address_; } + + character_counter_ = 0; + character_is_visible_ = true; } bus_state_.display_enable = character_is_visible_ && line_is_visible_; From be8e7a41448315b46f1a63a225d363d124399c4f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 11:22:30 -0400 Subject: [PATCH 07/36] Eliminated false register aliasing, restricted register sizes and locked out reading and writing where appropriate. --- Components/6845/CRTC6845.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 15a50e6ba..0f3d1e768 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -138,7 +138,7 @@ template class CRTC6845 { } void select_register(uint8_t r) { - selected_register_ = (int)r & 15; + selected_register_ = r; } uint8_t get_status() { @@ -146,18 +146,25 @@ template class CRTC6845 { } uint8_t get_register() { + if(selected_register_ < 12 || selected_register_ > 17) return 0xff; return registers_[selected_register_]; } void set_register(uint8_t value) { - registers_[selected_register_] = value; + static uint8_t masks[] = { + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, + 0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff + }; + + if(selected_register_ < 16) + registers_[selected_register_] = value & masks[selected_register_]; } private: T &bus_handler_; BusState bus_state_; - uint8_t registers_[16]; + uint8_t registers_[18]; int selected_register_; uint8_t character_counter_; From 02d792c003591b1b4e9626867072c73198e824e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 11:48:37 -0400 Subject: [PATCH 08/36] Simplified logic slightly, avoiding repetition. --- Components/6845/CRTC6845.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 0f3d1e768..1181c6cb1 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -58,15 +58,14 @@ template class CRTC6845 { if(hsync_down_counter_) bus_state_.hsync = true; } + // update refresh address + if(character_is_visible_) { + bus_state_.refresh_address++; + } + // check for end of visible characters if(character_counter_ == registers_[1]) { - bus_state_.refresh_address++; character_is_visible_ = false; - } else { - // update refresh address - if(character_is_visible_) { - bus_state_.refresh_address++; - } } // check for end-of-line From 6a6e5ae79c14599ca1c60a6f9eb7453356436102 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 12:28:57 -0400 Subject: [PATCH 09/36] Forced users of the 6845 to be explicit about which type. So far with no effect. --- Components/6845/CRTC6845.hpp | 12 +++++++++++- Machines/AmstradCPC/AmstradCPC.cpp | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 1181c6cb1..5a87c649c 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -31,9 +31,18 @@ class BusHandler { void perform_bus_cycle(const BusState &) {} }; +enum Personality { + HD6845S, // + UM6845R, // + MC6845, // + AMS40226 // +}; + template class CRTC6845 { public: - CRTC6845(T &bus_handler) : bus_handler_(bus_handler) {} + + CRTC6845(Personality p, T &bus_handler) : + personality_(p), bus_handler_(bus_handler) {} void run_for(Cycles cycles) { int cyles_remaining = cycles.as_int(); @@ -160,6 +169,7 @@ template class CRTC6845 { } private: + Personality personality_; T &bus_handler_; BusState bus_state_; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 7b7e8b0c4..43fa74d95 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -528,7 +528,7 @@ class ConcreteMachine: ConcreteMachine() : z80_(*this), crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses - crtc_(crtc_bus_handler_), + crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), crtc_bus_handler_(ram_, interrupt_timer_), i8255_(i8255_port_handler_), i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_), From 4961fda2a980231e43fddb1a62ae95939934bf5f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 12:39:19 -0400 Subject: [PATCH 10/36] Ensured counter-intuitive CRTC writes get through, taking the opportunity to correct my handling of port IO in general: selecting multiple devices for input results in a logical AND (i.e. open collector mode), and both the CRTC and gate array will receive data from 'input's if applicable. --- Machines/AmstradCPC/AmstradCPC.cpp | 120 ++++++++++++++++------------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 43fa74d95..9d7359321 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -579,48 +579,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Output: // Check for a gate array access. if((address & 0xc000) == 0x4000) { - switch(*cycle.value >> 6) { - case 0: crtc_bus_handler_.select_pen(*cycle.value & 0x1f); break; - case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break; - case 2: - // Perform ROM paging. - read_pointers_[0] = (*cycle.value & 4) ? write_pointers_[0] : roms_[rom_model_].data(); - - upper_rom_is_paged_ = !(*cycle.value & 8); - read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; - - // Reset the interrupt timer if requested. - if(*cycle.value & 0x10) interrupt_timer_.reset_count(); - - // Post the next mode. - crtc_bus_handler_.set_next_mode(*cycle.value & 3); - break; - case 3: - // Perform RAM paging, if 128kb is permitted. - if(has_128k_) { - bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0]; - bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3]; -#define RAM_BANK(x) &ram_[x * 16384] -#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d); - switch(*cycle.value & 7) { - case 0: RAM_CONFIG(0, 1, 2, 3); break; - case 1: RAM_CONFIG(0, 1, 2, 7); break; - case 2: RAM_CONFIG(4, 5, 6, 7); break; - case 3: RAM_CONFIG(0, 3, 2, 7); break; - case 4: RAM_CONFIG(0, 4, 2, 3); break; - case 5: RAM_CONFIG(0, 5, 2, 3); break; - case 6: RAM_CONFIG(0, 6, 2, 3); break; - case 7: RAM_CONFIG(0, 7, 2, 3); break; - } -#undef RAM_CONFIG -#undef RAM_BANK - if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0]; - read_pointers_[1] = write_pointers_[1]; - read_pointers_[2] = write_pointers_[2]; - if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3]; - } - break; - } + write_to_gate_array(*cycle.value); } // Check for an upper ROM selection @@ -634,7 +593,7 @@ class ConcreteMachine: switch((address >> 8) & 3) { case 0: crtc_.select_register(*cycle.value); break; case 1: crtc_.set_register(*cycle.value); break; - case 2: case 3: printf("Illegal CRTC write?\n"); break; + default: break; } } @@ -657,23 +616,31 @@ class ConcreteMachine: // Default to nothing answering *cycle.value = 0xff; - // Check for a CRTC access - if(!(address & 0x4000)) { - switch((address >> 8) & 3) { - case 0: case 1: printf("Illegal CRTC read?\n"); break; - case 2: *cycle.value = crtc_.get_status(); break; - case 3: *cycle.value = crtc_.get_register(); break; - } - } - // Check for a PIO access if(!(address & 0x800)) { - *cycle.value = i8255_.get_register((address >> 8) & 3); + *cycle.value &= i8255_.get_register((address >> 8) & 3); } // Check for an FDC access if(has_fdc_ && (address & 0x580) == 0x100) { - *cycle.value = fdc_.get_register(address & 1); + *cycle.value &= fdc_.get_register(address & 1); + } + + // Check for a CRTC access; the below is not a typo — the CRTC can be selected + // for writing via an input, and will sample whatever happens to be available + if(!(address & 0x4000)) { + switch((address >> 8) & 3) { + case 0: crtc_.select_register(*cycle.value); break; + case 1: crtc_.set_register(*cycle.value); break; + case 2: *cycle.value &= crtc_.get_status(); break; + case 3: *cycle.value &= crtc_.get_register(); break; + } + } + + // As with the CRTC, the gate array will sample the bus if the address decoding + // implies that it should, unaware of data direction + if((address & 0xc000) == 0x4000) { + write_to_gate_array(*cycle.value); } break; @@ -794,6 +761,51 @@ class ConcreteMachine: } private: + inline void write_to_gate_array(uint8_t value) { + switch(value >> 6) { + case 0: crtc_bus_handler_.select_pen(value & 0x1f); break; + case 1: crtc_bus_handler_.set_colour(value & 0x1f); break; + case 2: + // Perform ROM paging. + read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data(); + + upper_rom_is_paged_ = !(value & 8); + read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; + + // Reset the interrupt timer if requested. + if(value & 0x10) interrupt_timer_.reset_count(); + + // Post the next mode. + crtc_bus_handler_.set_next_mode(value & 3); + break; + case 3: + // Perform RAM paging, if 128kb is permitted. + if(has_128k_) { + bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0]; + bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3]; +#define RAM_BANK(x) &ram_[x * 16384] +#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d); + switch(value & 7) { + case 0: RAM_CONFIG(0, 1, 2, 3); break; + case 1: RAM_CONFIG(0, 1, 2, 7); break; + case 2: RAM_CONFIG(4, 5, 6, 7); break; + case 3: RAM_CONFIG(0, 3, 2, 7); break; + case 4: RAM_CONFIG(0, 4, 2, 3); break; + case 5: RAM_CONFIG(0, 5, 2, 3); break; + case 6: RAM_CONFIG(0, 6, 2, 3); break; + case 7: RAM_CONFIG(0, 7, 2, 3); break; + } +#undef RAM_CONFIG +#undef RAM_BANK + if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0]; + read_pointers_[1] = write_pointers_[1]; + read_pointers_[2] = write_pointers_[2]; + if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3]; + } + break; + } + } + CPU::Z80::Processor z80_; CRTCBusHandler crtc_bus_handler_; From cf810d8357f74249394d485981a758110a8e2580 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 14:42:47 -0400 Subject: [PATCH 11/36] Minor: ensure the CRT is set to output as a monitor. --- Machines/AmstradCPC/AmstradCPC.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 9d7359321..7859fdd58 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -291,6 +291,7 @@ class CRTCBusHandler { "return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" "}"); crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); + crt_->set_output_device(Outputs::CRT::Monitor); } /// Destructs the CRT. From a1e2646301877f5d356dd30b5fddca88aad6bac6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 14:58:24 -0400 Subject: [PATCH 12/36] Imposed counter size limits. --- Components/6845/CRTC6845.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 5a87c649c..419c4dc63 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -104,7 +104,7 @@ template class CRTC6845 { bus_state_.row_address = 0; bool is_at_end_of_frame = line_counter_ == registers_[4]; - line_counter_++; + line_counter_ = (line_counter_ + 1) & 0x7f; // check for end of visible lines if(line_counter_ == registers_[6]) { @@ -131,7 +131,7 @@ template class CRTC6845 { line_counter_ = 0; } } else { - bus_state_.row_address++; + bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f; } bus_state_.refresh_address = line_address_; } @@ -141,6 +141,7 @@ template class CRTC6845 { } bus_state_.display_enable = character_is_visible_ && line_is_visible_; + bus_state_.refresh_address &= 0x3fff; bus_handler_.perform_bus_cycle(bus_state_); } } From a5593bec79978dc60281e3c2b9f1c901d9923bd5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 15:00:14 -0400 Subject: [PATCH 13/36] Threw in support for the light-pen trigger. --- Components/6845/CRTC6845.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 419c4dc63..1fe909a51 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -169,6 +169,11 @@ template class CRTC6845 { registers_[selected_register_] = value & masks[selected_register_]; } + void trigger_light_pen() { + registers_[17] = bus_state_.refresh_address & 0xff; + registers_[16] = bus_state_.refresh_address >> 8; + } + private: Personality personality_; T &bus_handler_; From ad8c8166bc7fc79a31c3fa59ecba1de00b6819a9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 15:17:08 -0400 Subject: [PATCH 14/36] Built in gamma conversion for all machines, assuming an output of 2.8 for PAL, 2.2 for NTSC. --- .../Mac/Clock Signal/Machine/CSMachine.mm | 3 +++ Outputs/CRT/CRT.cpp | 17 +++++++++++++++++ Outputs/CRT/CRT.hpp | 9 +++++++++ Outputs/CRT/Internals/CRTOpenGL.cpp | 5 +++++ Outputs/CRT/Internals/CRTOpenGL.hpp | 7 +++++++ Outputs/CRT/Internals/Shaders/OutputShader.cpp | 7 ++++++- Outputs/CRT/Internals/Shaders/OutputShader.hpp | 2 ++ 7 files changed, 49 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 9fbc7ffbb..a5aaa10ef 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -116,6 +116,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { - (void)setupOutputWithAspectRatio:(float)aspectRatio { self.machine->setup_output(aspectRatio); + + // Since OS X v10.6, Macs have had a gamma of 2.2. + self.machine->get_crt()->set_output_gamma(2.2f); } - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index aed91f634..acd68cbfc 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -56,10 +56,12 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display switch(displayType) { case DisplayType::PAL50: set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync + set_input_gamma(2.8f); break; case DisplayType::NTSC60: set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync + set_input_gamma(2.2f); break; } } @@ -72,6 +74,21 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_ } } +void CRT::set_input_gamma(float gamma) { + input_gamma_ = gamma; + update_gamma(); +} + +void CRT::set_output_gamma(float gamma) { + output_gamma_ = gamma; + update_gamma(); +} + +void CRT::update_gamma() { + float gamma_ratio = output_gamma_ / input_gamma_; + openGL_output_builder_.set_gamma(gamma_ratio); +} + CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : is_receiving_sync_(false), common_output_divisor_(common_output_divisor), diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 13e22085b..19d890ec3 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -97,6 +97,9 @@ class CRT { unsigned int cycles_per_line_; + float input_gamma_, output_gamma_; + void update_gamma(); + public: /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, @@ -238,6 +241,12 @@ class CRT { openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty); } + /*! Sets the gamma exponent for the simulated screen. */ + void set_input_gamma(float gamma); + + /*! Sets the gamma exponent for the real, tangible screen on which content will be drawn. */ + void set_output_gamma(float gamma); + /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 81a2d7a55..2568dd2cf 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -89,6 +89,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out set_timing_uniforms(); set_colour_space_uniforms(); + set_gamma(); } if(fence_ != nullptr) { @@ -375,6 +376,10 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() { if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); } +void OpenGLOutputBuilder::set_gamma() { + if(output_shader_program_) output_shader_program_->set_gamma_ratio(gamma_); +} + float OpenGLOutputBuilder::get_composite_output_width() const { return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 5be73cca9..164e65395 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -34,6 +34,7 @@ class OpenGLOutputBuilder { unsigned int colour_cycle_numerator_; unsigned int colour_cycle_denominator_; OutputDevice output_device_; + float gamma_; // timing information to allow reasoning about input information unsigned int input_frequency_; @@ -89,6 +90,7 @@ class OpenGLOutputBuilder { void set_timing_uniforms(); void set_colour_space_uniforms(); + void set_gamma(); void establish_OpenGL_state(); void reset_all_OpenGL_state(); @@ -118,6 +120,11 @@ class OpenGLOutputBuilder { visible_area_ = visible_area; } + inline void set_gamma(float gamma) { + gamma_ = gamma; + set_gamma(); + } + inline std::unique_lock get_output_lock() { return std::unique_lock(output_mutex_); } diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index 7435d059a..1c773828a 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -74,12 +74,13 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_met "out vec4 fragColour;" "uniform %s texID;" + "uniform float gamma;" "\n%s\n" "void main(void)" "{" - "fragColour = vec4(%s, 0.5);"//*cos(lateralVarying) + "fragColour = vec4(pow(%s, vec3(gamma)), 0.5);"//*cos(lateralVarying) "}", sampler_type, fragment_methods, colour_expression); @@ -116,6 +117,10 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider); } +void OutputShader::set_gamma_ratio(float ratio) { + set_uniform("gamma", ratio); +} + void OutputShader::set_input_width_scaler(float input_scaler) { set_uniform("inputScaler", input_scaler); } diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.hpp b/Outputs/CRT/Internals/Shaders/OutputShader.hpp index 0df8143f4..02db19cdb 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.hpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.hpp @@ -63,6 +63,8 @@ public: */ void set_origin_is_double_height(bool is_double_height); + void set_gamma_ratio(float ratio); + /*! Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available space, 0.5 means use half, etc. From 62eadbb51aa35798ef53d986c1154cd692c66bc0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 15:36:27 -0400 Subject: [PATCH 15/36] Adjusted gamma ratio to be the correct way around. The PAL midrange should be slightly darker now. --- Outputs/CRT/CRT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index acd68cbfc..e75648445 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -85,7 +85,7 @@ void CRT::set_output_gamma(float gamma) { } void CRT::update_gamma() { - float gamma_ratio = output_gamma_ / input_gamma_; + float gamma_ratio = input_gamma_ / output_gamma_; openGL_output_builder_.set_gamma(gamma_ratio); } From 6a65c7a52a79bad49c129730efd0545b7249f70f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 17:10:21 -0400 Subject: [PATCH 16/36] Started working on a CPC-oriented analyser; for now I just want to be able to make a good guess at the appropriate file to load from a disk. As it turns out, the CPC simply adopts the CP/M format, so a generic parser is appropriate. This is its beginning. --- .../Clock Signal.xcodeproj/project.pbxproj | 22 ++++++++-- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 13 ++++++ Storage/Disk/Parsers/CPM.cpp | 43 +++++++++++++++++++ Storage/Disk/Parsers/CPM.hpp | 39 +++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 Storage/Disk/Parsers/CPM.cpp create mode 100644 Storage/Disk/Parsers/CPM.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1522bb3df..263dd9bb3 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; }; 4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; }; 4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; }; + 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; }; 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; }; 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; @@ -562,6 +563,8 @@ 4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = ""; }; 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = ""; }; 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = ""; }; + 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = ""; }; + 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = ""; }; 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = ""; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = ""; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = ""; }; @@ -1330,6 +1333,15 @@ path = Bridges; sourceTree = ""; }; + 4B3FE75F1F3CF6BA00448EE4 /* Parsers */ = { + isa = PBXGroup; + children = ( + 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */, + 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */, + ); + name = Parsers; + sourceTree = ""; + }; 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { isa = PBXGroup; children = ( @@ -1446,12 +1458,12 @@ 4B69FB3A1C4D908A00B5F0AA /* Tape */ = { isa = PBXGroup; children = ( + 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */, + 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, + 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */, + 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, 4B69FB411C4D941400B5F0AA /* Formats */, 4B8805F11DCFC9A2003085B1 /* Parsers */, - 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */, - 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */, - 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, - 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, ); path = Tape; sourceTree = ""; @@ -1545,6 +1557,7 @@ 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, + 4B3FE75F1F3CF6BA00448EE4 /* Parsers */, ); path = Disk; sourceTree = ""; @@ -2700,6 +2713,7 @@ 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, + 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 8e15fdd2b..9f0b2273f 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -7,6 +7,7 @@ // #include "StaticAnalyser.hpp" +#include "../../Storage/Disk/Parsers/CPM.hpp" void StaticAnalyser::AmstradCPC::AddTargets( const std::list> &disks, @@ -22,5 +23,17 @@ void StaticAnalyser::AmstradCPC::AddTargets( target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC6128; + if(!target.disks.empty()) { + // This is CPC data format. + Storage::Disk::CPM::ParameterBlock parameters; + parameters.sectors_per_track = 9; + parameters.sector_size = 512; + parameters.first_sector = 0xc1; + parameters.catalogue_allocation_bitmap = 0xc000; + parameters.reserved_tracks = 0; + + Storage::Disk::CPM::GetCatalogue(target.disks.front(), parameters); + } + destination.push_back(target); } diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp new file mode 100644 index 000000000..96302b788 --- /dev/null +++ b/Storage/Disk/Parsers/CPM.cpp @@ -0,0 +1,43 @@ +// +// CPM.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "CPM.hpp" + +#include "../Encodings/MFM.hpp" + +using namespace Storage::Disk::CPM; + +std::unique_ptr Storage::Disk::CPM::GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters) { + Storage::Encodings::MFM::Parser parser(true, disk); + + // Assemble the actual bytes of the catalogue. + std::vector catalogue; + uint16_t catalogue_allocation_bitmap = parameters.catalogue_allocation_bitmap; + int sector = 0; + int track = parameters.reserved_tracks; + while(catalogue_allocation_bitmap) { + if(catalogue_allocation_bitmap & 0x8000) { + std::shared_ptr sector_contents = parser.get_sector((uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + if(!sector_contents) { + return nullptr; + } + + catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end()); + } + + catalogue_allocation_bitmap <<= 1; + + sector++; + if(sector == parameters.sectors_per_track) { + sector = 0; + track++; + } + } + + return nullptr; +} diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp new file mode 100644 index 000000000..4ee333e98 --- /dev/null +++ b/Storage/Disk/Parsers/CPM.hpp @@ -0,0 +1,39 @@ +// +// CPM.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Disk_Parsers_CPM_hpp +#define Storage_Disk_Parsers_CPM_hpp + +#include "../Disk.hpp" +#include + +namespace Storage { +namespace Disk { +namespace CPM { + +struct ParameterBlock { + int sectors_per_track; + int sector_size; + int first_sector; + uint16_t catalogue_allocation_bitmap; + int reserved_tracks; +}; + +struct File { +}; + +struct Catalogue { +}; + +std::unique_ptr GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters); + +} +} +} + +#endif /* Storage_Disk_Parsers_CPM_hpp */ From 994179f1883b046e709b3e18a4b642cf6b9c5ffb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 22:33:08 -0400 Subject: [PATCH 17/36] Taking a whole bunch of guesses, this might be correct. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 3 +- Storage/Disk/Parsers/CPM.cpp | 73 +++++++++++++++++++- Storage/Disk/Parsers/CPM.hpp | 14 +++- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 9f0b2273f..73ee8930e 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -27,9 +27,10 @@ void StaticAnalyser::AmstradCPC::AddTargets( // This is CPC data format. Storage::Disk::CPM::ParameterBlock parameters; parameters.sectors_per_track = 9; - parameters.sector_size = 512; + parameters.block_size = 1024; parameters.first_sector = 0xc1; parameters.catalogue_allocation_bitmap = 0xc000; + parameters.logic_extents_per_physical = 1; parameters.reserved_tracks = 0; Storage::Disk::CPM::GetCatalogue(target.disks.front(), parameters); diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index 96302b788..cf340ca18 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -17,7 +17,9 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( // Assemble the actual bytes of the catalogue. std::vector catalogue; + size_t sector_size = 1; uint16_t catalogue_allocation_bitmap = parameters.catalogue_allocation_bitmap; + if(!catalogue_allocation_bitmap) return nullptr; int sector = 0; int track = parameters.reserved_tracks; while(catalogue_allocation_bitmap) { @@ -28,6 +30,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end()); + sector_size = sector_contents->data.size(); } catalogue_allocation_bitmap <<= 1; @@ -39,5 +42,73 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } } - return nullptr; + std::unique_ptr result(new Catalogue); + + // From the catalogue, create files. + std::map, size_t> indices_by_name; + File empty_file; + for(size_t c = 0; c < catalogue.size(); c += 32) { + // Skip this file if it's deleted; this is marked by it having 0xe5 as its user number + if(catalogue[c] == 0xe5) continue; + + // Check whether this file has yet been seen; if not then add it to the list + std::vector descriptor; + size_t index; + descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]); + auto iterator = indices_by_name.find(descriptor); + if(iterator != indices_by_name.end()) { + index = iterator->second; + } else { + File new_file; + new_file.user_number = catalogue[c]; + for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]); + for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f); + new_file.read_only = catalogue[c + 9] & 0x80; + new_file.system = catalogue[c + 10] & 0x80; + index = result->files.size(); + result->files.push_back(new_file); + indices_by_name[descriptor] = index; + + printf("%s\n", new_file.name.c_str()); + } + + // figure out where this data needs to be pasted in + int extent = catalogue[c + 12] + (catalogue[c + 14] << 5); + int number_of_records = catalogue[c + 15]; + + size_t required_size = (size_t)(extent * 128 + number_of_records) * 128; + if(result->files[index].data.size() < required_size) { + result->files[index].data.resize(required_size); + } + + printf("%d records for extent %d: ", number_of_records, extent); + int sectors_per_block = parameters.block_size / (int)sector_size; + int records_per_sector = (int)sector_size / 128; + int record = 0; + for(size_t block = 0; block < 16; block++) { + int block_number = catalogue[c + 16 + block]; + if(!block_number) continue; + int first_sector = block_number * sectors_per_block; + + sector = first_sector % parameters.sectors_per_track; + track = first_sector / parameters.sectors_per_track; + + for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { + std::shared_ptr sector_contents = parser.get_sector((uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + if(!sector_contents) break; + sector++; + if(sector == parameters.sectors_per_track) { + sector = 0; + track++; + } + + int records_to_copy = std::min(number_of_records - record, records_per_sector); + memcpy(&result->files[index].data[extent * 16384 + record * 128], sector_contents->data.data(), records_to_copy * 128); + record += records_to_copy; + } + } + printf("\n"); + } + + return result; } diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp index 4ee333e98..78f62d5b5 100644 --- a/Storage/Disk/Parsers/CPM.hpp +++ b/Storage/Disk/Parsers/CPM.hpp @@ -10,7 +10,11 @@ #define Storage_Disk_Parsers_CPM_hpp #include "../Disk.hpp" + +#include #include +#include +#include namespace Storage { namespace Disk { @@ -18,16 +22,24 @@ namespace CPM { struct ParameterBlock { int sectors_per_track; - int sector_size; + int block_size; int first_sector; + int logic_extents_per_physical; uint16_t catalogue_allocation_bitmap; int reserved_tracks; }; struct File { + uint8_t user_number; + std::string name; + std::string type; + bool read_only; + bool system; + std::vector data; }; struct Catalogue { + std::vector files; }; std::unique_ptr GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters); From 6be58514846649ed30ef2e69c70fd5ea327d9965 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Aug 2017 22:34:29 -0400 Subject: [PATCH 18/36] Cleaned up. --- Storage/Disk/Parsers/CPM.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index cf340ca18..2ebbe2e1f 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -68,8 +68,6 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( index = result->files.size(); result->files.push_back(new_file); indices_by_name[descriptor] = index; - - printf("%s\n", new_file.name.c_str()); } // figure out where this data needs to be pasted in @@ -81,7 +79,6 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( result->files[index].data.resize(required_size); } - printf("%d records for extent %d: ", number_of_records, extent); int sectors_per_block = parameters.block_size / (int)sector_size; int records_per_sector = (int)sector_size / 128; int record = 0; @@ -103,11 +100,10 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } int records_to_copy = std::min(number_of_records - record, records_per_sector); - memcpy(&result->files[index].data[extent * 16384 + record * 128], sector_contents->data.data(), records_to_copy * 128); + memcpy(&result->files[index].data[(size_t)(extent * 16384 + record * 128)], sector_contents->data.data(), (size_t)records_to_copy * 128); record += records_to_copy; } } - printf("\n"); } return result; From 734099a956518eb39a07135dca18c1b707d56be0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 10:29:13 -0400 Subject: [PATCH 19/36] Threw a sector cache into my MFM parser, in an attempt to cut down analysis costs. Also made it aware of multiple heads. --- StaticAnalyser/Acorn/Disk.cpp | 10 ++++---- Storage/Disk/Encodings/MFM.cpp | 39 ++++++++++++++++++++++++------- Storage/Disk/Encodings/MFM.hpp | 9 ++++--- Storage/Disk/Formats/AcornADF.cpp | 2 +- Storage/Disk/Formats/SSD.cpp | 2 +- Storage/Disk/Parsers/CPM.cpp | 4 ++-- 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 7a3f9aa6f..8f9245814 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -19,8 +19,8 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha std::unique_ptr catalogue(new Catalogue); Storage::Encodings::MFM::Parser parser(false, disk); - std::shared_ptr names = parser.get_sector(0, 0); - std::shared_ptr details = parser.get_sector(0, 1); + std::shared_ptr names = parser.get_sector(0, 0, 0); + std::shared_ptr details = parser.get_sector(0, 0, 1); if(!names || !details) return nullptr; if(names->data.size() != 256 || details->data.size() != 256) return nullptr; @@ -61,7 +61,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha uint8_t track = (uint8_t)(start_sector / 10); start_sector++; - std::shared_ptr next_sector = parser.get_sector(track, sector); + std::shared_ptr next_sector = parser.get_sector(0, track, sector); if(!next_sector) break; long length_from_sector = std::min(data_length, 256l); @@ -77,13 +77,13 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh std::unique_ptr catalogue(new Catalogue); Storage::Encodings::MFM::Parser parser(true, disk); - std::shared_ptr free_space_map_second_half = parser.get_sector(0, 1); + std::shared_ptr free_space_map_second_half = parser.get_sector(0, 0, 1); if(!free_space_map_second_half) return nullptr; std::vector root_directory; root_directory.reserve(5 * 256); for(uint8_t c = 2; c < 7; c++) { - std::shared_ptr sector = parser.get_sector(0, c); + std::shared_ptr sector = parser.get_sector(0, 0, c); if(!sector) return nullptr; root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); } diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 1aa908e15..1c2f19a4c 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -228,25 +228,26 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector &disk) : Parser(is_mfm) { - drive->set_disk(disk); + drive_->set_disk(disk); } Parser::Parser(bool is_mfm, const std::shared_ptr &track) : Parser(is_mfm) { - drive->set_disk_with_track(track); + drive_->set_disk_with_track(track); } void Parser::seek_to_track(uint8_t track) { @@ -261,7 +262,20 @@ void Parser::seek_to_track(uint8_t track) { } } -std::shared_ptr Parser::get_sector(uint8_t track, uint8_t sector) { +std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) { + // Check cache for sector. + int index = get_index(head, track, sector); + auto cached_sector = sectors_by_index_.find(index); + if(cached_sector != sectors_by_index_.end()) { + return cached_sector->second; + } + + // Failing that, set the proper head and track, and search for the sector. get_sector automatically + // inserts everything found into sectors_by_index_. + if(head_ != head) { + drive_->set_head(head); + invalidate_track(); + } seek_to_track(track); return get_sector(sector); } @@ -384,8 +398,7 @@ std::vector Parser::get_track() { } -std::shared_ptr Parser::get_next_sector() -{ +std::shared_ptr Parser::get_next_sector() { std::shared_ptr sector(new Sector); index_count_ = 0; @@ -455,6 +468,10 @@ std::shared_ptr Parser::get_next_sector() if((data_crc >> 8) != get_next_byte()) continue; if((data_crc & 0xff) != get_next_byte()) continue; + // Put this sector into the cache. + int index = get_index(head_, track_, sector->sector); + sectors_by_index_[index] = sector; + return sector; } @@ -465,7 +482,7 @@ std::shared_ptr Parser::get_sector(uint8_t sector) { std::shared_ptr first_sector; index_count_ = 0; while(!first_sector && index_count_ < 2) first_sector = get_next_sector(); - if(!first_sector) return first_sector; + if(!first_sector) return nullptr; if(first_sector->sector == sector) return first_sector; while(1) { @@ -475,3 +492,7 @@ std::shared_ptr Parser::get_sector(uint8_t sector) { if(next_sector->sector == sector) return next_sector; } } + +int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) { + return head | (track << 8) | (sector << 16); +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index f482c93bd..37a8178f3 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -75,7 +75,7 @@ class Parser: public Storage::Disk::Controller { @returns a sector if one was found; @c nullptr otherwise. */ - std::shared_ptr get_sector(uint8_t track, uint8_t sector); + std::shared_ptr get_sector(uint8_t head, uint8_t track, uint8_t sector); /*! Attempts to read the track at @c track, starting from the index hole. @@ -92,10 +92,10 @@ class Parser: public Storage::Disk::Controller { private: Parser(bool is_mfm); - std::shared_ptr drive; + std::shared_ptr drive_; unsigned int shift_register_; int index_count_; - uint8_t track_; + uint8_t track_, head_; int bit_count_; NumberTheory::CRC16 crc_generator_; bool is_mfm_; @@ -110,6 +110,9 @@ class Parser: public Storage::Disk::Controller { std::shared_ptr get_next_sector(); std::shared_ptr get_sector(uint8_t sector); std::vector get_track(); + + std::map> sectors_by_index_; + int get_index(uint8_t head, uint8_t track, uint8_t sector); }; diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 779bc29c0..f4e0664d7 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -87,7 +87,7 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p std::vector parsed_track; Storage::Encodings::MFM::Parser parser(true, track); for(unsigned int c = 0; c < sectors_per_track; c++) { - std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + std::shared_ptr sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c); if(sector) { parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); } else { diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 4903784d5..5045e536b 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -81,7 +81,7 @@ void SSD::store_updated_track_at_position(unsigned int head, unsigned int positi std::vector parsed_track; Storage::Encodings::MFM::Parser parser(false, track); for(unsigned int c = 0; c < 10; c++) { - std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + std::shared_ptr sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c); if(sector) { parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); } else { diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index 2ebbe2e1f..749e8387b 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -24,7 +24,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( int track = parameters.reserved_tracks; while(catalogue_allocation_bitmap) { if(catalogue_allocation_bitmap & 0x8000) { - std::shared_ptr sector_contents = parser.get_sector((uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + std::shared_ptr sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); if(!sector_contents) { return nullptr; } @@ -91,7 +91,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( track = first_sector / parameters.sectors_per_track; for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { - std::shared_ptr sector_contents = parser.get_sector((uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + std::shared_ptr sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); if(!sector_contents) break; sector++; if(sector == parameters.sectors_per_track) { From 026101a2688a435f7785ae84c2f413c715365cd9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 10:46:50 -0400 Subject: [PATCH 20/36] Killed logic_extents_per_physical, since I don't know how to handle it, and instituted tracks, to allow a decision about short versus long allocation units. --- Storage/Disk/Parsers/CPM.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp index 78f62d5b5..a51c8b5cc 100644 --- a/Storage/Disk/Parsers/CPM.hpp +++ b/Storage/Disk/Parsers/CPM.hpp @@ -22,9 +22,9 @@ namespace CPM { struct ParameterBlock { int sectors_per_track; + int tracks; int block_size; int first_sector; - int logic_extents_per_physical; uint16_t catalogue_allocation_bitmap; int reserved_tracks; }; From 388dd997624d78a263f66e188aa585863be9264c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 10:47:12 -0400 Subject: [PATCH 21/36] Advanced this just enough to suggest a loading command for most things. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 68 +++++++++++++++++--- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 73ee8930e..0a49946a2 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -9,6 +9,35 @@ #include "StaticAnalyser.hpp" #include "../../Storage/Disk/Parsers/CPM.hpp" +static void InspectDataCatalogue( + const std::unique_ptr &data_catalogue, + StaticAnalyser::Target &target) { + // If there's just one file, run that. + if(data_catalogue->files.size() == 1) { + target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n"; + return; + } + + // If only one file is [potentially] BASIC, run that one. + int basic_files = 0; + size_t last_basic_file = 0; + for(size_t c = 0; c < data_catalogue->files.size(); c++) { + if(!((data_catalogue->files[c].data[18] >> 1) & 7)) { + basic_files++; + last_basic_file = c; + } + } + if(basic_files == 1) { + target.loadingCommand = "run\"" + data_catalogue->files[last_basic_file].name + "\n"; + return; + } +} + +static void InspectSystemCatalogue( + const std::unique_ptr &data_catalogue, + StaticAnalyser::Target &target) { +} + void StaticAnalyser::AmstradCPC::AddTargets( const std::list> &disks, const std::list> &tapes, @@ -21,19 +50,38 @@ void StaticAnalyser::AmstradCPC::AddTargets( target.tapes = tapes; target.cartridges = cartridges; - target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC6128; + target.amstradcpc.model = AmstradCPCModel::CPC6128; + + if(!target.tapes.empty()) { + target.loadingCommand = "|tape\nrun\"\n"; + } if(!target.disks.empty()) { - // This is CPC data format. - Storage::Disk::CPM::ParameterBlock parameters; - parameters.sectors_per_track = 9; - parameters.block_size = 1024; - parameters.first_sector = 0xc1; - parameters.catalogue_allocation_bitmap = 0xc000; - parameters.logic_extents_per_physical = 1; - parameters.reserved_tracks = 0; + Storage::Disk::CPM::ParameterBlock data_format; + data_format.sectors_per_track = 9; + data_format.tracks = 40; + data_format.block_size = 1024; + data_format.first_sector = 0xc1; + data_format.catalogue_allocation_bitmap = 0xc000; + data_format.reserved_tracks = 0; - Storage::Disk::CPM::GetCatalogue(target.disks.front(), parameters); + std::unique_ptr data_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), data_format); + if(data_catalogue) { + InspectDataCatalogue(data_catalogue, target); + } else { + Storage::Disk::CPM::ParameterBlock system_format; + data_format.sectors_per_track = 9; + data_format.tracks = 40; + data_format.block_size = 1024; + data_format.first_sector = 0x41; + data_format.catalogue_allocation_bitmap = 0xc000; + data_format.reserved_tracks = 2; + + std::unique_ptr system_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), system_format); + if(system_catalogue) { + InspectSystemCatalogue(data_catalogue, target); + } + } } destination.push_back(target); From dea782cff9dbe5096e82b71fc4caa09e1607fb83 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 10:47:45 -0400 Subject: [PATCH 22/36] Added a "yeah, I don't know" fallback. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 0a49946a2..91059e263 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -31,6 +31,9 @@ static void InspectDataCatalogue( target.loadingCommand = "run\"" + data_catalogue->files[last_basic_file].name + "\n"; return; } + + // Desperation. + target.loadingCommand = "cat\n"; } static void InspectSystemCatalogue( From 0411b51582b772dc6ba9ce87b0b5541651d667da Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 10:59:37 -0400 Subject: [PATCH 23/36] Added an attempt to deal with 16-bit allocation units, and to ensure middle-of-file holes are respected. --- Storage/Disk/Parsers/CPM.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index 749e8387b..eaf737404 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -43,6 +43,8 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } std::unique_ptr result(new Catalogue); + bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256; + size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size; // From the catalogue, create files. std::map, size_t> indices_by_name; @@ -71,10 +73,10 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } // figure out where this data needs to be pasted in - int extent = catalogue[c + 12] + (catalogue[c + 14] << 5); + size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5)); int number_of_records = catalogue[c + 15]; - size_t required_size = (size_t)(extent * 128 + number_of_records) * 128; + size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128; if(result->files[index].data.size() < required_size) { result->files[index].data.resize(required_size); } @@ -82,9 +84,17 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( int sectors_per_block = parameters.block_size / (int)sector_size; int records_per_sector = (int)sector_size / 128; int record = 0; - for(size_t block = 0; block < 16; block++) { - int block_number = catalogue[c + 16 + block]; - if(!block_number) continue; + for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) { + int block_number; + if(has_long_allocation_units) { + block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8); + } else { + block_number = catalogue[c + 16 + block]; + } + if(!block_number) { + record += parameters.block_size / 128; + continue; + } int first_sector = block_number * sectors_per_block; sector = first_sector % parameters.sectors_per_track; @@ -100,7 +110,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } int records_to_copy = std::min(number_of_records - record, records_per_sector); - memcpy(&result->files[index].data[(size_t)(extent * 16384 + record * 128)], sector_contents->data.data(), (size_t)records_to_copy * 128); + memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128); record += records_to_copy; } } From 570d25214e10ff5372bc07b11b9e3c88de6cd77d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 11:21:07 -0400 Subject: [PATCH 24/36] Made an initial attempt at typer support for the CPC. --- Machines/AmstradCPC/AmstradCPC.cpp | 34 ++++++- Machines/AmstradCPC/CharacterMapper.cpp | 89 +++++++++++++++++++ Machines/AmstradCPC/CharacterMapper.hpp | 23 +++++ .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 Machines/AmstradCPC/CharacterMapper.cpp create mode 100644 Machines/AmstradCPC/CharacterMapper.hpp diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 7859fdd58..c4053c023 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -8,6 +8,8 @@ #include "AmstradCPC.hpp" +#include "CharacterMapper.hpp" + #include "../../Processors/Z80/Z80.hpp" #include "../../Components/6845/CRTC6845.hpp" @@ -15,6 +17,9 @@ #include "../../Components/8272/i8272.hpp" #include "../../Components/AY38910/AY38910.hpp" +#include "../MemoryFuzzer.hpp" +#include "../Typer.hpp" + #include "../../Storage/Tape/Tape.hpp" namespace AmstradCPC { @@ -523,6 +528,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. */ class ConcreteMachine: + public Utility::TypeRecipient, public CPU::Z80::BusHandler, public Machine { public: @@ -536,6 +542,9 @@ class ConcreteMachine: tape_player_(8000000) { // primary clock is 4Mhz set_clock_rate(4000000); + + // ensure memory starts in a random state + Memory::Fuzz(ram_, sizeof(ram_)); } /// The entry point for performing a partial Z80 machine cycle. @@ -557,12 +566,15 @@ class ConcreteMachine: // run_for as HalfCycles tape_player_.run_for(cycle.length.as_int()); - // Pump the AY. + // Pump the AY ay_.run_for(cycle.length); // Clock the FDC, if connected, using a lazy scale by two if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int())); + // Update typing activity + if(typer_) typer_->run_for(cycle.length); + // Stop now if no action is strictly required. if(!cycle.is_terminal()) return HalfCycles(0); @@ -742,6 +754,11 @@ class ConcreteMachine: c++; if(c == 4) break; } + + // Type whatever is required. + if(target.loadingCommand.length()) { + set_typer_for_string(target.loadingCommand.c_str()); + } } // See header; provides the system ROMs. @@ -749,6 +766,21 @@ class ConcreteMachine: roms_[(int)type] = data; } +#pragma mark - Keyboard + + void set_typer_for_string(const char *string) { + std::unique_ptr mapper(new CharacterMapper()); + Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); + } + + HalfCycles get_typer_delay() { + return Cycles(625*25*128); + } + + HalfCycles get_typer_frequency() { + return Cycles(625*128*2); + } + // See header; sets a key as either pressed or released. void set_key_state(uint16_t key, bool isPressed) { int line = key >> 4; diff --git a/Machines/AmstradCPC/CharacterMapper.cpp b/Machines/AmstradCPC/CharacterMapper.cpp new file mode 100644 index 000000000..e1fef2118 --- /dev/null +++ b/Machines/AmstradCPC/CharacterMapper.cpp @@ -0,0 +1,89 @@ +// +// CharacterMapper.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "CharacterMapper.hpp" +#include "AmstradCPC.hpp" + +using namespace AmstradCPC; + +uint16_t *CharacterMapper::sequence_for_character(char character) { +#define KEYS(...) {__VA_ARGS__, EndSequence} +#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence} +#define X {NotMapped} + static KeySequence key_sequences[] = { + /* NUL */ X, /* SOH */ X, + /* STX */ X, /* ETX */ X, + /* EOT */ X, /* ENQ */ X, + /* ACK */ X, /* BEL */ X, + /* BS */ KEYS(KeyDelete), /* HT */ X, + /* LF */ KEYS(KeyReturn), /* VT */ X, + /* FF */ X, /* CR */ X, + /* SO */ X, /* SI */ X, + /* DLE */ X, /* DC1 */ X, + /* DC2 */ X, /* DC3 */ X, + /* DC4 */ X, /* NAK */ X, + /* SYN */ X, /* ETB */ X, + /* CAN */ X, /* EM */ X, + /* SUB */ X, /* ESC */ X, + /* FS */ X, /* GS */ X, + /* RS */ X, /* US */ X, + /* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1), + /* " */ SHIFT(Key2), /* # */ SHIFT(Key3), + /* $ */ SHIFT(Key4), /* % */ SHIFT(Key5), + /* & */ SHIFT(Key6), /* ' */ SHIFT(Key7), + /* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9), + /* * */ SHIFT(KeyColon), /* + */ SHIFT(KeySemicolon), + /* , */ KEYS(KeyComma), /* - */ KEYS(KeyMinus), + /* . */ KEYS(KeyFullStop), /* / */ KEYS(KeyForwardSlash), + /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1), + /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3), + /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5), + /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7), + /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9), + /* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon), + /* < */ SHIFT(KeyComma), /* = */ SHIFT(KeyMinus), + /* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeyForwardSlash), + /* @ */ SHIFT(KeyAt), /* A */ SHIFT(KeyA), + /* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC), + /* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE), + /* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG), + /* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI), + /* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK), + /* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM), + /* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO), + /* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ), + /* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS), + /* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU), + /* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW), + /* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY), + /* Z */ SHIFT(KeyZ), /* [ */ KEYS(KeyLeftSquareBracket), + /* \ */ KEYS(KeyBackSlash), /* ] */ KEYS(KeyRightSquareBracket), + /* ^ */ SHIFT(KeyCaret), /* _ */ SHIFT(Key0), + /* ` */ X, /* a */ KEYS(KeyA), + /* b */ KEYS(KeyB), /* c */ KEYS(KeyC), + /* d */ KEYS(KeyD), /* e */ KEYS(KeyE), + /* f */ KEYS(KeyF), /* g */ KEYS(KeyG), + /* h */ KEYS(KeyH), /* i */ KEYS(KeyI), + /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK), + /* l */ KEYS(KeyL), /* m */ KEYS(KeyM), + /* n */ KEYS(KeyN), /* o */ KEYS(KeyO), + /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ), + /* r */ KEYS(KeyR), /* s */ KEYS(KeyS), + /* t */ KEYS(KeyT), /* u */ KEYS(KeyU), + /* v */ KEYS(KeyV), /* w */ KEYS(KeyW), + /* x */ KEYS(KeyX), /* y */ KEYS(KeyY), + /* z */ KEYS(KeyZ), /* { */ X, + /* | */ SHIFT(KeyAt), /* } */ X, + /* ~ */ X + }; +#undef KEYS +#undef SHIFT +#undef X + + return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); +} diff --git a/Machines/AmstradCPC/CharacterMapper.hpp b/Machines/AmstradCPC/CharacterMapper.hpp new file mode 100644 index 000000000..89f985ae2 --- /dev/null +++ b/Machines/AmstradCPC/CharacterMapper.hpp @@ -0,0 +1,23 @@ +// +// CharacterMapper.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Machines_AmstradCPC_CharacterMapper_hpp +#define Machines_AmstradCPC_CharacterMapper_hpp + +#include "../Typer.hpp" + +namespace AmstradCPC { + +class CharacterMapper: public ::Utility::CharacterMapper { + public: + uint16_t *sequence_for_character(char character); +}; + +} + +#endif /* CharacterMapper_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 263dd9bb3..f7a8f670a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -120,6 +120,7 @@ 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; }; + 4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; }; 4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; }; 4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; }; 4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; @@ -670,6 +671,8 @@ 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = ""; }; 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = ""; }; 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = ""; }; + 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = AmstradCPC/CharacterMapper.cpp; sourceTree = ""; }; + 4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = ""; }; 4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; @@ -1297,6 +1300,8 @@ children = ( 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */, 4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */, + 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */, + 4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */, ); name = AmstradCPC; sourceTree = ""; @@ -2724,6 +2729,7 @@ 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, + 4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */, 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, From dd4bc87d5245351c5086aeb27470e0fb1132d0e6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 11:21:33 -0400 Subject: [PATCH 25/36] Fixed: should be a full-path #ifdef guard, given that this is one of the classes named relative to its namespace. --- Machines/ZX8081/CharacterMapper.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/ZX8081/CharacterMapper.hpp b/Machines/ZX8081/CharacterMapper.hpp index 1d2b996da..b78b66dfd 100644 --- a/Machines/ZX8081/CharacterMapper.hpp +++ b/Machines/ZX8081/CharacterMapper.hpp @@ -6,8 +6,8 @@ // Copyright © 2017 Thomas Harte. All rights reserved. // -#ifndef CharacterMapper_hpp -#define CharacterMapper_hpp +#ifndef Machines_ZX8081_CharacterMapper_hpp +#define Machines_ZX8081_CharacterMapper_hpp #include "../Typer.hpp" From 4ecd093891bb3a1c11d8912380169de3a988f139 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 11:35:14 -0400 Subject: [PATCH 26/36] Fixed test for termination of a key sequence; the previous error will have seen this reduce all multi-key sequences to just the one, and expand single-key sequences to "probably" two, posting an out-of-bounds code to the machine at completion. --- Machines/Typer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Typer.cpp b/Machines/Typer.cpp index 7ff39395d..ff60c04b9 100644 --- a/Machines/Typer.cpp +++ b/Machines/Typer.cpp @@ -51,7 +51,7 @@ bool Typer::try_type_next_character() { if(!phase_) delegate_->clear_all_keys(); else { delegate_->set_key_state(sequence[phase_ - 1], true); - return sequence[phase_] == CharacterMapper::EndSequence; + return sequence[phase_] != CharacterMapper::EndSequence; } return true; From 44da9de5b00d92ea6d56aa50abe593eeab74c796 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 11:35:28 -0400 Subject: [PATCH 27/36] Tweaked typing timing expectations. --- Machines/AmstradCPC/AmstradCPC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index c4053c023..3cdd37139 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -774,11 +774,11 @@ class ConcreteMachine: } HalfCycles get_typer_delay() { - return Cycles(625*25*128); + return Cycles(4000000); } HalfCycles get_typer_frequency() { - return Cycles(625*128*2); + return Cycles(80000); } // See header; sets a key as either pressed or released. From 4785e316ff86388ae5d215f4fc68128a696675be Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 11:36:03 -0400 Subject: [PATCH 28/36] Now with exposition. --- Machines/AmstradCPC/AmstradCPC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 3cdd37139..d58273fbe 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -774,11 +774,11 @@ class ConcreteMachine: } HalfCycles get_typer_delay() { - return Cycles(4000000); + return Cycles(4000000); // Wait 1 second before typing. } HalfCycles get_typer_frequency() { - return Cycles(80000); + return Cycles(80000); // Type one character per frame. } // See header; sets a key as either pressed or released. From 1d8edf58dde8f4ef7fd678493916162527f17d28 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 12:07:48 -0400 Subject: [PATCH 29/36] Ensured that a virtual destructor is declared, so that the various automatically-generated real constructors get in on the action. --- Machines/AmstradCPC/AmstradCPC.cpp | 2 ++ Machines/AmstradCPC/AmstradCPC.hpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index d58273fbe..328c233c3 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -881,3 +881,5 @@ using namespace AmstradCPC; Machine *Machine::AmstradCPC() { return new AmstradCPC::ConcreteMachine; } + +Machine::~Machine() {} diff --git a/Machines/AmstradCPC/AmstradCPC.hpp b/Machines/AmstradCPC/AmstradCPC.hpp index 50ddeeda3..91c29e69f 100644 --- a/Machines/AmstradCPC/AmstradCPC.hpp +++ b/Machines/AmstradCPC/AmstradCPC.hpp @@ -52,6 +52,8 @@ class Machine: public ConfigurationTarget::Machine, public KeyboardMachine::Machine { public: + virtual ~Machine(); + /// Creates an returns an Amstrad CPC on the heap. static Machine *AmstradCPC(); From 3831fbaca21af355a204f4aca012d50f8c406deb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 12:11:01 -0400 Subject: [PATCH 30/36] Ensured the ZX80 and '81 also provide the necessary hook for destruction. --- Machines/ZX8081/ZX8081.cpp | 2 ++ Machines/ZX8081/ZX8081.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 6b6089faf..6806f14c2 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -384,3 +384,5 @@ using namespace ZX8081; Machine *Machine::ZX8081() { return new ZX8081::ConcreteMachine; } + +Machine::~Machine() {} diff --git a/Machines/ZX8081/ZX8081.hpp b/Machines/ZX8081/ZX8081.hpp index 91670c55a..7efbbe3fd 100644 --- a/Machines/ZX8081/ZX8081.hpp +++ b/Machines/ZX8081/ZX8081.hpp @@ -39,6 +39,7 @@ class Machine: public KeyboardMachine::Machine { public: static Machine *ZX8081(); + virtual ~Machine(); virtual void set_rom(ROMType type, std::vector data) = 0; From 14ab03d1e0fe965460569436f6817c3f42df3869 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 12:27:50 -0400 Subject: [PATCH 31/36] Added a further fallback: if all files have an extension but one doesn't, take that one. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 91059e263..3d476696a 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -18,17 +18,28 @@ static void InspectDataCatalogue( return; } - // If only one file is [potentially] BASIC, run that one. + // If only one file is [potentially] BASIC, run that one; otherwise if only one has no suffix, + // pick that one. int basic_files = 0; + int nonsuffixed_files = 0; size_t last_basic_file = 0; + size_t last_nonsuffixed_file = 0; for(size_t c = 0; c < data_catalogue->files.size(); c++) { + // Check for whether this is [potentially] BASIC. if(!((data_catalogue->files[c].data[18] >> 1) & 7)) { basic_files++; last_basic_file = c; } + + // Check suffix for emptiness. + if(data_catalogue->files[c].type == " ") { + nonsuffixed_files++; + last_nonsuffixed_file = c; + } } - if(basic_files == 1) { - target.loadingCommand = "run\"" + data_catalogue->files[last_basic_file].name + "\n"; + if(basic_files == 1 || nonsuffixed_files == 1) { + size_t selected_file = (basic_files == 1) ? last_basic_file : last_nonsuffixed_file; + target.loadingCommand = "run\"" + data_catalogue->files[selected_file].name + "\n"; return; } @@ -56,7 +67,7 @@ void StaticAnalyser::AmstradCPC::AddTargets( target.amstradcpc.model = AmstradCPCModel::CPC6128; if(!target.tapes.empty()) { - target.loadingCommand = "|tape\nrun\"\n"; + target.loadingCommand = "|tape\nrun\"\n\n"; } if(!target.disks.empty()) { From 1f2bfc9581eae718b6c2516abd72da7f7ff6f847 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 12:30:36 -0400 Subject: [PATCH 32/36] Ensured tape loading really begins. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 3d476696a..d166887d2 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -67,7 +67,10 @@ void StaticAnalyser::AmstradCPC::AddTargets( target.amstradcpc.model = AmstradCPCModel::CPC6128; if(!target.tapes.empty()) { - target.loadingCommand = "|tape\nrun\"\n\n"; + // Ugliness flows here: assume the CPC isn't smart enough to pause between pressing + // enter and responding to the follow-on prompt to press a key, so just type for + // a while. Yuck! + target.loadingCommand = "|tape\nrun\"\n1234567890"; } if(!target.disks.empty()) { From fcf63a7547bd6becbc8c44cdb87c4f935fdb5fe9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 14:24:50 -0400 Subject: [PATCH 33/36] Expands the [M]FM encoder to respect some new Sector flags: it will now wilfully make CRC errors, omit data, include data that is different than the ID's declared length, write deleted data, and can be commanded as to header/data gaps and what should be within them. All based around expanding towards the needs for reproduction of the CPC's .DSK file format. --- Storage/Disk/Encodings/MFM.cpp | 63 ++++++++++++++++--------------- Storage/Disk/Encodings/MFM.hpp | 21 +++++++++-- Storage/Disk/Formats/AcornADF.cpp | 2 + Storage/Disk/Formats/CPCDSK.cpp | 13 ++++--- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 1c2f19a4c..846ece7f6 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -114,22 +114,12 @@ class FMEncoder: public Encoder { } }; -static uint8_t logarithmic_size_for_size(size_t size) { - switch(size) { - default: return 0; - case 256: return 1; - case 512: return 2; - case 1024: return 3; - case 2048: return 4; - case 4196: return 5; - } -} - template std::shared_ptr GetTrackWithSectors( const std::vector §ors, size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, - size_t pre_address_mark_bytes, size_t post_address_mark_bytes, + size_t pre_address_mark_bytes, + size_t post_address_mark_bytes, uint8_t post_address_mark_value, size_t pre_data_mark_bytes, size_t post_data_bytes, size_t inter_sector_gap, size_t expected_track_bytes) { @@ -153,21 +143,30 @@ template std::shared_ptr shifter.add_byte(sector.track); shifter.add_byte(sector.side); shifter.add_byte(sector.sector); - uint8_t size = logarithmic_size_for_size(sector.data.size()); - shifter.add_byte(size); - shifter.add_crc(); + shifter.add_byte(sector.size); + shifter.add_crc(sector.has_header_crc_error); // gap - for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); + for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value); for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); - // data - shifter.add_data_address_mark(); - for(size_t c = 0; c < sector.data.size(); c++) - { - shifter.add_byte(sector.data[c]); + // data, if attached + if(!sector.data.empty()) { + if(sector.is_deleted) + shifter.add_deleted_data_address_mark(); + else + shifter.add_data_address_mark(); + + size_t c = 0; + size_t declared_length = (size_t)(128 << sector.size); + for(c = 0; c < sector.data.size() && c < declared_length; c++) { + shifter.add_byte(sector.data[c]); + } + for(; c < declared_length; c++) { + shifter.add_byte(0x00); + } + shifter.add_crc(sector.has_data_crc_error); } - shifter.add_crc(); // gap for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); @@ -189,28 +188,32 @@ void Encoder::output_short(uint16_t value) { target_.push_back(value & 0xff); } -void Encoder::add_crc() { +void Encoder::add_crc(bool incorrectly) { uint16_t crc_value = crc_generator_.get_value(); add_byte(crc_value >> 8); - add_byte(crc_value & 0xff); + add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0)); } -std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) { +const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0; + +std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( sectors, 16, 0x00, - 6, 0, - 17, 14, + 6, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 0, sector_gap_filler_byte, + (sector_gap_length != DefaultSectorGapLength) ? 0 : 17, 14, 0, 6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation } -std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) { +std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( sectors, 50, 0x4e, - 12, 22, - 12, 18, + 12, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 22, sector_gap_filler_byte, + (sector_gap_length != DefaultSectorGapLength) ? 0 : 12, 18, 32, 12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm) } diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 37a8178f3..d1e5b7e15 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -36,13 +36,24 @@ const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator sho const uint8_t MFMIndexSyncByteValue = 0xc2; const uint8_t MFMSyncByteValue = 0xa1; +/*! + Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data + and a few extra flags of metadata. +*/ struct Sector { - uint8_t track, side, sector; + uint8_t track, side, sector, size; std::vector data; + + bool has_data_crc_error; + bool has_header_crc_error; + bool is_deleted; + + Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {} }; -std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors); -std::shared_ptr GetFMTrackWithSectors(const std::vector §ors); +extern const size_t DefaultSectorGapLength; +std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); +std::shared_ptr GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); class Encoder { public: @@ -53,7 +64,9 @@ class Encoder { virtual void add_data_address_mark() = 0; virtual void add_deleted_data_address_mark() = 0; virtual void output_short(uint16_t value); - void add_crc(); + + /// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC. + void add_crc(bool incorrectly); protected: NumberTheory::CRC16 crc_generator_; diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index f4e0664d7..91f4846d2 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -14,6 +14,7 @@ namespace { static const unsigned int sectors_per_track = 16; static const unsigned int bytes_per_sector = 256; + static const unsigned int sector_size = 1; } using namespace Storage::Disk; @@ -69,6 +70,7 @@ std::shared_ptr AcornADF::get_uncached_track_at_position(unsigned int hea new_sector.track = (uint8_t)position; new_sector.side = (uint8_t)head; new_sector.sector = (uint8_t)sector; + new_sector.size = sector_size; new_sector.data.resize(bytes_per_sector); fread(&new_sector.data[0], 1, bytes_per_sector, file_); diff --git a/Storage/Disk/Formats/CPCDSK.cpp b/Storage/Disk/Formats/CPCDSK.cpp index 99f25132e..973344ff4 100644 --- a/Storage/Disk/Formats/CPCDSK.cpp +++ b/Storage/Disk/Formats/CPCDSK.cpp @@ -75,8 +75,8 @@ std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, // Grab the track information. fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector int number_of_sectors = fgetc(file_); - __unused uint8_t gap3_length = (uint8_t)fgetc(file_); - __unused uint8_t filler_byte = (uint8_t)fgetc(file_); + uint8_t gap3_length = (uint8_t)fgetc(file_); + uint8_t filler_byte = (uint8_t)fgetc(file_); // Grab the sector information struct SectorInfo { @@ -111,6 +111,7 @@ std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, new_sector.track = sector_info.track; new_sector.side = sector_info.side; new_sector.sector = sector_info.sector; + new_sector.size = sector_info.length; size_t data_size; if(is_extended_) { @@ -126,28 +127,30 @@ std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, if(sector_info.status1 || sector_info.status2) { if(sector_info.status1 & 0x08) { // The CRC failed in the ID field. + new_sector.has_header_crc_error = true; } if(sector_info.status2 & 0x20) { // The CRC failed in the data field. + new_sector.has_data_crc_error = true; } if(sector_info.status2 & 0x40) { // This sector is marked as deleted. + new_sector.is_deleted = true; } if(sector_info.status2 & 0x01) { // Data field wasn't found. + new_sector.data.clear(); } - - printf("Unhandled: status errors\n"); } sectors.push_back(std::move(new_sector)); } // TODO: supply gay 3 length and filler byte - if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors); + if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte); return nullptr; } From cf1403bc791cfa7c6c3c19893c49a6202666bfdc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 14:27:07 -0400 Subject: [PATCH 34/36] Increased documentation. --- Storage/Disk/Encodings/MFM.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index d1e5b7e15..eed23e5b3 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -52,7 +52,22 @@ struct Sector { }; extern const size_t DefaultSectorGapLength; +/*! + Converts a vector of sectors into a properly-encoded MFM track. + + @param sectors The sectors to write. + @param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data. + @param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data. +*/ std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); + +/*! + Converts a vector of sectors into a properly-encoded FM track. + + @param sectors The sectors to write. + @param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data. + @param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data. +*/ std::shared_ptr GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); class Encoder { From 80ebc63101f5ebe34fa2a6602aeb3da206fea275 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 14:30:35 -0400 Subject: [PATCH 35/36] Updated the SSD file format container to specify sector sizes, now that it's no longer implicit. --- Storage/Disk/Formats/SSD.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 5045e536b..8bea6b936 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -60,6 +60,7 @@ std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, un new_sector.track = (uint8_t)position; new_sector.side = 0; new_sector.sector = (uint8_t)sector; + new_sector.size = 1; new_sector.data.resize(256); fread(new_sector.data.data(), 1, 256, file_); From edb088526f9eeca2dd0470130aa9a7eaa41fb0ca Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Aug 2017 14:33:34 -0400 Subject: [PATCH 36/36] Simplified slightly, and updated TODO as to still-missing functionality. --- Storage/Disk/Formats/CPCDSK.cpp | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Storage/Disk/Formats/CPCDSK.cpp b/Storage/Disk/Formats/CPCDSK.cpp index 973344ff4..bf3082384 100644 --- a/Storage/Disk/Formats/CPCDSK.cpp +++ b/Storage/Disk/Formats/CPCDSK.cpp @@ -123,33 +123,33 @@ std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, new_sector.data.resize(data_size); fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_); - // TODO: obey the status bytes, somehow (?) - if(sector_info.status1 || sector_info.status2) { - if(sector_info.status1 & 0x08) { - // The CRC failed in the ID field. - new_sector.has_header_crc_error = true; - } + if(sector_info.status1 & 0x08) { + // The CRC failed in the ID field. + new_sector.has_header_crc_error = true; + } - if(sector_info.status2 & 0x20) { - // The CRC failed in the data field. - new_sector.has_data_crc_error = true; - } + if(sector_info.status2 & 0x20) { + // The CRC failed in the data field. + new_sector.has_data_crc_error = true; + } - if(sector_info.status2 & 0x40) { - // This sector is marked as deleted. - new_sector.is_deleted = true; - } + if(sector_info.status2 & 0x40) { + // This sector is marked as deleted. + new_sector.is_deleted = true; + } - if(sector_info.status2 & 0x01) { - // Data field wasn't found. - new_sector.data.clear(); - } + if(sector_info.status2 & 0x01) { + // Data field wasn't found. + new_sector.data.clear(); } sectors.push_back(std::move(new_sector)); } - // TODO: supply gay 3 length and filler byte + // TODO: extensions to the extended format; John Elliot's addition of single-density support, + // and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential + // FM/MFM encoding that can produce specified weak values. + if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte); return nullptr;