diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp new file mode 100644 index 000000000..4c3f2ab4f --- /dev/null +++ b/Components/1770/1770.cpp @@ -0,0 +1,460 @@ +// +// 1770.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "1770.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" + +using namespace WD; + +WD1770::WD1770() : + Storage::Disk::Controller(8000000, 16, 300), + status_(0), + interesting_event_mask_(Event::Command), + resume_point_(0), + delay_time_(0), + index_hole_count_target_(-1), + is_awaiting_marker_value_(false), + is_reading_data_(false) +{ + set_is_double_density(false); + posit_event(Event::Command); +} + +void WD1770::set_is_double_density(bool is_double_density) +{ + is_double_density_ = is_double_density; + Storage::Time bit_length; + bit_length.length = 1; + bit_length.clock_rate = is_double_density ? 500000 : 250000; + set_expected_bit_length(bit_length); + + if(!is_double_density) is_awaiting_marker_value_ = false; +} + +void WD1770::set_register(int address, uint8_t value) +{ + switch(address&3) + { + case 0: + command_ = value; + posit_event(Event::Command); + // TODO: is this force interrupt? + break; + case 1: track_ = value; break; + case 2: sector_ = value; break; + case 3: data_ = value; break; + } +} + +uint8_t WD1770::get_register(int address) +{ + switch(address&3) + { + default: return status_; + case 1: return track_; + case 2: return sector_; + case 3: status_ &= ~Flag::DataRequest; return data_; + } +} + +void WD1770::run_for_cycles(unsigned int number_of_cycles) +{ + Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); + + if(delay_time_) + { + if(delay_time_ <= number_of_cycles) + { + delay_time_ = 0; + posit_event(Event::Timer); + } + else + { + delay_time_ -= number_of_cycles; + } + } +} + +void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) +{ + shift_register_ = (shift_register_ << 1) | value; + bits_since_token_++; + + Token::Type token_type = Token::Byte; + if(!is_reading_data_) + { + if(!is_double_density_) + { + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::FMIndexAddressMark: + token_type = Token::Index; + break; + case Storage::Encodings::MFM::FMIDAddressMark: + token_type = Token::ID; + break; + case Storage::Encodings::MFM::FMDataAddressMark: + token_type = Token::Data; + break; + case Storage::Encodings::MFM::FMDeletedDataAddressMark: + token_type = Token::DeletedData; + break; + default: + break; + } + } + else + { + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::MFMIndexAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + case Storage::Encodings::MFM::MFMAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + default: + break; + } + } + + if(token_type != Token::Byte) + { + latest_token_.type = token_type; + bits_since_token_ = 0; + posit_event(Event::Token); + return; + } + } + + if(bits_since_token_ == 16) + { + latest_token_.type = Token::Byte; + latest_token_.byte_value = (uint8_t)( + ((shift_register_ & 0x0001) >> 0) | + ((shift_register_ & 0x0004) >> 1) | + ((shift_register_ & 0x0010) >> 2) | + ((shift_register_ & 0x0040) >> 3) | + ((shift_register_ & 0x0100) >> 4) | + ((shift_register_ & 0x0400) >> 5) | + ((shift_register_ & 0x1000) >> 6) | + ((shift_register_ & 0x4000) >> 7)); + bits_since_token_ = 0; + + if(is_awaiting_marker_value_ && is_double_density_) + { + is_awaiting_marker_value_ = false; + switch(latest_token_.byte_value) + { + case Storage::Encodings::MFM::MFMIndexAddressByte: + latest_token_.type = Token::Index; + break; + case Storage::Encodings::MFM::MFMIDAddressByte: + latest_token_.type = Token::ID; + break; + case Storage::Encodings::MFM::MFMDataAddressByte: + latest_token_.type = Token::Data; + break; + case Storage::Encodings::MFM::MFMDeletedDataAddressByte: + latest_token_.type = Token::DeletedData; + break; + default: break; + } + } + + posit_event(Event::Token); + return; + } +} + +void WD1770::process_index_hole() +{ + index_hole_count_++; + posit_event(Event::IndexHole); + if(index_hole_count_target_ == index_hole_count_) + { + posit_event(Event::IndexHoleTarget); + index_hole_count_target_ = -1; + } + + // motor power-down + if(index_hole_count_ == 9 && !(status_&Flag::Busy)) + { + status_ &= ~Flag::MotorOn; + set_motor_on(false); + } +} + +// +------+----------+-------------------------+ +// ! ! ! BITS ! +// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 ! +// +------+----------+-------------------------+ +// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 ! +// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 ! +// ! 1 ! Step ! 0 0 1 u h v r1 r0 ! +// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 ! +// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 ! +// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 ! +// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 ! +// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 ! +// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 ! +// ! 3 ! Wt track ! 1 1 1 1 h E P 0 ! +// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 ! +// +------+----------+-------------------------+ + +#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__: +#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__: +#define BEGIN_SECTION() switch(resume_point_) { default: +#define END_SECTION() 0; } + +#define READ_ID() \ + if(new_event_type == Event::Token) \ + { \ + if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \ + else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \ + { \ + header[distance_into_section_ - 1] = latest_token_.byte_value; \ + distance_into_section_++; \ + } \ + } + +#define CONCATENATE(x, y) x ## y +#define INDIRECT_CONCATENATE(x, y) TOKENPASTE(x, y) +#define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__) + +#define SPIN_UP() \ + status_ |= Flag::MotorOn; \ + set_motor_on(true); \ + index_hole_count_ = 0; \ + index_hole_count_target_ = 6; \ + WAIT_FOR_EVENT(Event::IndexHoleTarget); + +void WD1770::posit_event(Event new_event_type) +{ + if(!(interesting_event_mask_ & (int)new_event_type)) return; + interesting_event_mask_ &= ~new_event_type; + + BEGIN_SECTION() + + // Wait for a new command, branch to the appropriate handler. + wait_for_command: + printf("Idle...\n"); + is_reading_data_ = false; + status_ &= ~Flag::Busy; + index_hole_count_ = 0; + WAIT_FOR_EVENT(Event::Command); + printf("Starting %02x\n", command_); + status_ |= Flag::Busy; + if(!(command_ & 0x80)) goto begin_type_1; + if(!(command_ & 0x40)) goto begin_type_2; + goto begin_type_3; + + + /* + Type 1 entry point. + */ + begin_type_1: + // Set initial flags, skip spin-up if possible. + status_ &= ~(Flag::DataRequest | Flag::DataRequest | Flag::SeekError); + set_interrupt_request(false); + if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type1_type; + + // Perform spin up. + SPIN_UP(); + status_ |= Flag::SpinUp; + + test_type1_type: + // Set step direction if this is a step in or out. + if((command_ >> 5) == 2) step_direction_ = 1; + if((command_ >> 5) == 3) step_direction_ = 0; + if((command_ >> 5) != 0) goto perform_step_command; + + // This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00. + if(!(command_ & 0x10)) + { + track_ = 0xff; + data_ = 0; + } + + perform_seek_or_restore_command: + if(track_ == data_) goto verify; + step_direction_ = (data_ > track_); + + adjust_track: + if(step_direction_) track_++; else track_--; + + perform_step: + if(!step_direction_ && get_is_track_zero()) + { + track_ = 0; + goto verify; + } + step(step_direction_ ? 1 : -1); + int time_to_wait; + switch(command_ & 3) + { + default: + case 0: time_to_wait = 6; break; // 2 on a 1772 + case 1: time_to_wait = 12; break; // 3 on a 1772 + case 2: time_to_wait = 20; break; // 5 on a 1772 + case 3: time_to_wait = 30; break; // 6 on a 1772 + } + WAIT_FOR_TIME(time_to_wait); + if(command_ >> 5) goto verify; + goto perform_seek_or_restore_command; + + perform_step_command: + if(command_ & 0x10) goto adjust_track; + goto perform_step; + + verify: + if(!(command_ & 0x04)) + { + set_interrupt_request(true); + goto wait_for_command; + } + + index_hole_count_ = 0; + distance_into_section_ = 0; + + verify_read_data: + WAIT_FOR_EVENT(Event::IndexHole | Event::Token); + READ_ID(); + + if(index_hole_count_ == 6) + { + set_interrupt_request(true); + status_ |= Flag::SeekError; + goto wait_for_command; + } + if(distance_into_section_ == 7) + { + is_reading_data_ = false; + // TODO: CRC check + if(header[0] == track_) + { + printf("Reached track %d\n", track_); + status_ &= ~Flag::CRCError; + set_interrupt_request(true); + goto wait_for_command; + } + + distance_into_section_ = 0; + } + goto verify_read_data; + + + /* + Type 2 entry point. + */ + begin_type_2: + status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | Flag::WriteProtect | Flag::RecordType); + set_interrupt_request(false); + distance_into_section_ = 0; + if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type2_delay; + + // Perform spin up. + SPIN_UP(); + + test_type2_delay: + index_hole_count_ = 0; + if(!(command_ & 0x04)) goto test_type2_write_protection; + WAIT_FOR_TIME(30); + + test_type2_write_protection: + if(command_&0x20) // TODO:: && is_write_protected + { + set_interrupt_request(true); + status_ |= Flag::WriteProtect; + goto wait_for_command; + } + + type2_get_header: + WAIT_FOR_EVENT(Event::IndexHole | Event::Token); + READ_ID(); + + if(index_hole_count_ == 5) + { + set_interrupt_request(true); + status_ |= Flag::RecordNotFound; + goto wait_for_command; + } + if(distance_into_section_ == 7) + { + is_reading_data_ = false; + if(header[0] == track_ && header[2] == sector_) + { + // TODO: test CRC + goto type2_read_or_write_data; + } + distance_into_section_ = 0; + } + goto type2_get_header; + + + type2_read_or_write_data: + if(command_&0x20) goto type2_write_data; + goto type2_read_data; + + type2_read_data: + WAIT_FOR_EVENT(Event::Token); + // TODO: timeout + if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) + { + status_ |= (latest_token_.type == Token::DeletedData) ? Flag::RecordType : 0; + distance_into_section_ = 0; + is_reading_data_ = true; + goto type2_read_byte; + } + goto type2_read_data; + + type2_read_byte: + WAIT_FOR_EVENT(Event::Token); + if(latest_token_.type != Token::Byte) goto type2_read_byte; + if(status_ & Flag::DataRequest) status_ |= Flag::LostData; + data_ = latest_token_.byte_value; + status_ |= Flag::DataRequest; + distance_into_section_++; + if(distance_into_section_ == 128 << header[3]) + { + distance_into_section_ = 0; + goto type2_check_crc; + } + goto type2_read_byte; + + type2_check_crc: + WAIT_FOR_EVENT(Event::Token); + if(latest_token_.type != Token::Byte) goto type2_read_byte; + header[distance_into_section_] = latest_token_.byte_value; + distance_into_section_++; + if(distance_into_section_ == 2) + { + // TODO: check CRC + if(command_ & 0x10) + { + sector_++; + goto test_type2_write_protection; + } + set_interrupt_request(true); + printf("Read sector %d\n", sector_); + goto wait_for_command; + } + goto type2_check_crc; + + + type2_write_data: + printf("!!!TODO: data portion of sector!!!\n"); + + begin_type_3: + printf("!!!TODO: type 3 commands!!!\n"); + + + END_SECTION() +} diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp new file mode 100644 index 000000000..c6fabf718 --- /dev/null +++ b/Components/1770/1770.hpp @@ -0,0 +1,92 @@ +// +// 1770.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef _770_hpp +#define _770_hpp + +#include "../../Storage/Disk/DiskController.hpp" + +namespace WD { + +class WD1770: public Storage::Disk::Controller { + public: + WD1770(); + + void set_is_double_density(bool is_double_density); + void set_register(int address, uint8_t value); + uint8_t get_register(int address); + + void run_for_cycles(unsigned int number_of_cycles); + + enum Flag: uint8_t { + MotorOn = 0x80, + WriteProtect = 0x40, + RecordType = 0x20, + SpinUp = 0x20, + RecordNotFound = 0x10, + SeekError = 0x10, + CRCError = 0x08, + LostData = 0x04, + TrackZero = 0x04, + DataRequest = 0x02, + Index = 0x02, + Busy = 0x01 + }; + + private: + uint8_t status_; + uint8_t track_; + uint8_t sector_; + uint8_t data_; + uint8_t command_; + + int index_hole_count_; + int index_hole_count_target_; + int bits_since_token_; + int distance_into_section_; + bool is_awaiting_marker_value_; + + int step_direction_; + void set_interrupt_request(bool interrupt_request) {} + + // Tokeniser + bool is_reading_data_; + bool is_double_density_; + int shift_register_; + struct Token { + enum Type { + Index, ID, Data, DeletedData, Byte + } type; + uint8_t byte_value; + } latest_token_; + + // Events + enum Event: int { + Command = (1 << 0), // Indicates receipt of a new command. + Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details. + IndexHole = (1 << 2), // Indicates the passing of a physical index hole. + + Timer = (1 << 3), // Indicates that the delay_time_-powered timer has timed out. + IndexHoleTarget = (1 << 4) // Indicates that index_hole_count_ has reached index_hole_count_target_. + }; + void posit_event(Event type); + int interesting_event_mask_; + int resume_point_; + int delay_time_; + + // ID buffer + uint8_t header[6]; + + // + virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_index_hole(); +}; + +} + +#endif /* _770_hpp */ diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index 2756ad058..fc52a4006 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -14,7 +14,7 @@ using namespace Commodore::C1540; Machine::Machine() : _shift_register(0), - Storage::Disk::Drive(1000000, 4, 300) + Storage::Disk::Controller(1000000, 4, 300) { // create a serial port and a VIA to run it _serialPortVIA.reset(new SerialPortVIA); @@ -102,11 +102,19 @@ void Machine::set_rom(const uint8_t *rom) memcpy(_rom, rom, sizeof(_rom)); } +void Machine::set_disk(std::shared_ptr disk) +{ + std::shared_ptr drive(new Storage::Disk::Drive); + drive->set_disk(disk); + set_drive(drive); +} + void Machine::run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); + set_motor_on(_driveVIA.get_motor_enabled()); if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down - Storage::Disk::Drive::run_for_cycles(number_of_cycles); + Storage::Disk::Controller::run_for_cycles(number_of_cycles); } #pragma mark - 6522 delegate diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 7a1b5e0e4..cddefaeb8 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -15,7 +15,7 @@ #include "../SerialBus.hpp" #include "../../../Storage/Disk/Disk.hpp" -#include "../../../Storage/Disk/DiskDrive.hpp" +#include "../../../Storage/Disk/DiskController.hpp" namespace Commodore { namespace C1540 { @@ -216,7 +216,7 @@ class Machine: public CPU6502::Processor, public MOS::MOS6522IRQDelegate::Delegate, public DriveVIA::Delegate, - public Storage::Disk::Drive { + public Storage::Disk::Controller { public: Machine(); @@ -232,6 +232,7 @@ class Machine: void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); void run_for_cycles(int number_of_cycles); + void set_disk(std::shared_ptr disk); // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); diff --git a/Machines/Commodore/SerialBus.hpp b/Machines/Commodore/SerialBus.hpp index 324ef235e..381e441fd 100644 --- a/Machines/Commodore/SerialBus.hpp +++ b/Machines/Commodore/SerialBus.hpp @@ -9,7 +9,7 @@ #ifndef SerialBus_hpp #define SerialBus_hpp -#import +#include namespace Commodore { namespace Serial { diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 795f288f8..efd60ceb3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -120,276 +120,303 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(address >= 0xc000) +// if((address >> 8) == 0xfc) +// { +// printf("d"); +// } + switch(address & 0xff0f) { - if((address & 0xff00) == 0xfe00) - { - switch(address&0xf) - { - case 0x0: - if(isReadOperation(operation)) - { - *value = _interrupt_status; - _interrupt_status &= ~PowerOnReset; - } - else - { - _interrupt_control = (*value) & ~1; - evaluate_interrupts(); - } - break; - case 0x1: - break; - case 0x2: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x3: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x4: - if(isReadOperation(operation)) - { - *value = _tape.get_data_register(); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - } - else - { - _tape.set_data_register(*value); - _tape.clear_interrupts(Interrupt::TransmitDataEmpty); - } - break; - case 0x5: - if(!isReadOperation(operation)) - { - const uint8_t interruptDisable = (*value)&0xf0; - if( interruptDisable ) - { - if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; - if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; - if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; - evaluate_interrupts(); - - // TODO: NMI - } - - // latch the paged ROM in case external hardware is being emulated - _active_rom = (Electron::ROMSlot)(*value & 0xf); - - // apply the ULA's test - if(*value & 0x08) - { - if(*value & 0x04) - { - _keyboard_is_active = false; - _basic_is_active = false; - } - else - { - _keyboard_is_active = !(*value & 0x02); - _basic_is_active = !_keyboard_is_active; - } - } - } - break; - case 0x6: - if(!isReadOperation(operation)) - { - update_audio(); - _speaker->set_divider(*value); - _tape.set_counter(*value); - } - break; - case 0x7: - if(!isReadOperation(operation)) - { - // update screen mode - uint8_t new_screen_mode = ((*value) >> 3)&7; - if(new_screen_mode == 7) new_screen_mode = 4; - if(new_screen_mode != _screen_mode) - { -// printf("To mode %d, at %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); - update_display(); - _screen_mode = new_screen_mode; - switch(_screen_mode) - { - case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; - case 3: _screenModeBaseAddress = 0x4000; break; - case 4: case 5: _screenModeBaseAddress = 0x5800; break; - case 6: _screenModeBaseAddress = 0x6000; break; - } - } - - // update speaker mode - bool new_speaker_is_enabled = (*value & 6) == 2; - if(new_speaker_is_enabled != _speaker->get_is_enabled()) - { - update_audio(); - _speaker->set_is_enabled(new_speaker_is_enabled); - _tape.set_is_enabled(!new_speaker_is_enabled); - } - - _tape.set_is_running(((*value)&0x40) ? true : false); - _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); - - // TODO: caps lock LED - } - break; - default: - { - if(!isReadOperation(operation)) - { - update_display(); - - static const int registers[4][4] = { - {10, 8, 2, 0}, - {14, 12, 6, 4}, - {15, 13, 7, 5}, - {11, 9, 3, 1}, - }; - const int index = (address >> 1)&3; - const uint8_t colour = ~(*value); - if(address&1) - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); - _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); - _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); - _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); - - _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); - _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); - } - else - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); - _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); - _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); - _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); - - _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); - _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); - } - - // regenerate all palette tables for now -#define pack(a, b) (uint8_t)((a << 4) | (b)) - for(int byte = 0; byte < 256; byte++) - { - uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - - target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; - target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); - - target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); - target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); - - _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], - _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); - } -#undef pack - } - } - break; - } - } - else - { + case 0xfe00: if(isReadOperation(operation)) { - if( - _use_fast_tape_hack && - _tape.has_tape() && - (operation == CPU6502::BusOperation::ReadOpcode) && - ( - (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 - (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling - (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM - (address == 0xfa51) || (address == 0xfa52) || // pathway. - - (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be - // dispatched; we can check whether it would be call 14 - // (i.e. read byte) and, if so, whether the OS was about to - // issue a read byte call to a ROM despite being the tape - // FS being selected. If so then this is a get byte that - // we should service synthetically. Put the byte into Y - // and set A to zero to report that action was taken, then - // allow the PC read to return an RTS. - ) - ) + *value = _interrupt_status; + _interrupt_status &= ~PowerOnReset; + } + else + { + _interrupt_control = (*value) & ~1; + evaluate_interrupts(); + } + break; + case 0xfe02: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe03: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe04: + if(isReadOperation(operation)) + { + *value = _tape.get_data_register(); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + } + else + { + _tape.set_data_register(*value); + _tape.clear_interrupts(Interrupt::TransmitDataEmpty); + } + break; + case 0xfe05: + if(!isReadOperation(operation)) + { + const uint8_t interruptDisable = (*value)&0xf0; + if( interruptDisable ) { - uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); - if(address == 0xf0a8) + if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; + evaluate_interrupts(); + + // TODO: NMI + } + + // latch the paged ROM in case external hardware is being emulated + _active_rom = (Electron::ROMSlot)(*value & 0xf); + + // apply the ULA's test + if(*value & 0x08) + { + if(*value & 0x04) { - if(!_ram[0x247] && service_call == 14) - { - _tape.set_delegate(nullptr); - - // TODO: handle tape wrap around. - - int cycles_left_while_plausibly_in_data = 50; - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - while(1) - { - _tape.run_for_input_pulse(); - cycles_left_while_plausibly_in_data--; - if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; - if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && - (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) - ) break; - } - _tape.set_delegate(this); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - _interrupt_status |= _tape.get_interrupt_status(); - - _fast_load_is_in_data = true; - set_value_of_register(CPU6502::Register::A, 0); - set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); - *value = 0x60; // 0x60 is RTS - } - else - *value = _os[address & 16383]; + _keyboard_is_active = false; + _basic_is_active = false; } else - *value = 0xea; + { + _keyboard_is_active = !(*value & 0x02); + _basic_is_active = !_keyboard_is_active; + } + } + } + break; + case 0xfe06: + if(!isReadOperation(operation)) + { + update_audio(); + _speaker->set_divider(*value); + _tape.set_counter(*value); + } + break; + case 0xfe07: + if(!isReadOperation(operation)) + { + // update screen mode + uint8_t new_screen_mode = ((*value) >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != _screen_mode) + { + update_display(); + _screen_mode = new_screen_mode; + switch(_screen_mode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } + } + + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != _speaker->get_is_enabled()) + { + update_audio(); + _speaker->set_is_enabled(new_speaker_is_enabled); + _tape.set_is_enabled(!new_speaker_is_enabled); + } + + _tape.set_is_running(((*value)&0x40) ? true : false); + _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); + + // TODO: caps lock LED + } + break; + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: + { + if(!isReadOperation(operation)) + { + update_display(); + + static const int registers[4][4] = { + {10, 8, 2, 0}, + {14, 12, 6, 4}, + {15, 13, 7, 5}, + {11, 9, 3, 1}, + }; + const int index = (address >> 1)&3; + const uint8_t colour = ~(*value); + if(address&1) + { + _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); + _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); + _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); + _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); + + _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); + _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); } else { - *value = _os[address & 16383]; + _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); + _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); + _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); + _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); + + _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); + _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); } - } - } - } - else - { - if(isReadOperation(operation)) - { - *value = _roms[_active_rom][address & 16383]; - if(_keyboard_is_active) - { - *value &= 0xf0; - for(int address_line = 0; address_line < 14; address_line++) + + // regenerate all palette tables for now +#define pack(a, b) (uint8_t)((a << 4) | (b)) + for(int byte = 0; byte < 256; byte++) { - if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + + target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; + target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); + + target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); + target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); + + _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], + _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); } - } - if(_basic_is_active) - { - *value &= _roms[ROMSlotBASIC][address & 16383]; +#undef pack } } + break; + + case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: + if(_plus3 && (address&0x00f0) == 0x00c0) + { + if(is_holding_shift_ && address == 0xfcc4) + { + is_holding_shift_ = false; + set_key_state(KeyShift, false); + } + if(isReadOperation(operation)) + *value = _plus3->get_register(address); + else + _plus3->set_register(address, *value); + } + break; + case 0xfc00: + if(_plus3 && (address&0x00f0) == 0x00c0) + { + if(!isReadOperation(operation)) + { + _plus3->set_control_register(*value); + } + else + *value = 1; + } + break; + + default: + if(address >= 0xc000) + { + if(isReadOperation(operation)) + { + if( + _use_fast_tape_hack && + _tape.has_tape() && + (operation == CPU6502::BusOperation::ReadOpcode) && + ( + (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 + (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling + (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM + (address == 0xfa51) || (address == 0xfa52) || // pathway. + + (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be + // dispatched; we can check whether it would be call 14 + // (i.e. read byte) and, if so, whether the OS was about to + // issue a read byte call to a ROM despite being the tape + // FS being selected. If so then this is a get byte that + // we should service synthetically. Put the byte into Y + // and set A to zero to report that action was taken, then + // allow the PC read to return an RTS. + ) + ) + { + uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); + if(address == 0xf0a8) + { + if(!_ram[0x247] && service_call == 14) + { + _tape.set_delegate(nullptr); + + // TODO: handle tape wrap around. + + int cycles_left_while_plausibly_in_data = 50; + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + while(1) + { + _tape.run_for_input_pulse(); + cycles_left_while_plausibly_in_data--; + if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; + if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && + (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) + ) break; + } + _tape.set_delegate(this); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + _interrupt_status |= _tape.get_interrupt_status(); + + _fast_load_is_in_data = true; + set_value_of_register(CPU6502::Register::A, 0); + set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); + *value = 0x60; // 0x60 is RTS + } + else + *value = _os[address & 16383]; + } + else + *value = 0xea; + } + else + { + *value = _os[address & 16383]; + } + } + } + else + { + if(isReadOperation(operation)) + { + *value = _roms[_active_rom][address & 16383]; + if(_keyboard_is_active) + { + *value &= 0xf0; + for(int address_line = 0; address_line < 14; address_line++) + { + if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + } + } + if(_basic_is_active) + { + *value &= _roms[ROMSlotBASIC][address & 16383]; + } + } else if(_rom_write_masks[_active_rom]) + { + _roms[_active_rom][address & 16383] = *value; + } + } + break; } } @@ -448,6 +475,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _tape.run_for_cycles(cycles); if(_typer) _typer->update((int)cycles); + if(_plus3) _plus3->run_for_cycles(4*cycles); return cycles; } @@ -465,22 +493,57 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) _tape.set_tape(target.tapes.front()); } + if(target.disks.size()) + { + _plus3.reset(new Plus3); + + if(target.acorn.has_dfs) + { + set_rom(ROMSlot0, _dfs, true); + } + if(target.acorn.has_adfs) + { + set_rom(ROMSlot4, _adfs, true); + set_rom(ROMSlot5, std::vector(_adfs.begin() + 16384, _adfs.end()), true); + } + + _plus3->set_disk(target.disks.front(), 0); + } + + ROMSlot slot = ROMSlot12; + for(std::shared_ptr cartridge : target.cartridges) + { + set_rom(slot, cartridge->get_segments().front().data, false); + slot = (ROMSlot)(((int)slot + 1)&15); + } + if(target.loadingCommand.length()) // TODO: and automatic loading option enabled { set_typer_for_string(target.loadingCommand.c_str()); } + if(target.acorn.should_hold_shift) + { + set_key_state(KeyShift, true); + is_holding_shift_ = true; + } } -void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) +void Machine::set_rom(ROMSlot slot, std::vector data, bool is_writeable) { uint8_t *target = nullptr; switch(slot) { + case ROMSlotDFS: _dfs = data; return; + case ROMSlotADFS: _adfs = data; return; + case ROMSlotOS: target = _os; break; - default: target = _roms[slot]; break; + default: + target = _roms[slot]; + _rom_write_masks[slot] = is_writeable; + break; } - memcpy(target, data, std::min((size_t)16384, length)); + memcpy(target, &data[0], std::min((size_t)16384, data.size())); } inline void Machine::signal_interrupt(Electron::Interrupt interrupt) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 10a6f33b8..7b88f775a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -15,8 +15,10 @@ #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../Typer.hpp" +#include "Plus3.hpp" #include +#include namespace Electron { @@ -30,7 +32,7 @@ enum ROMSlot: uint8_t { ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15, - ROMSlotOS + ROMSlotOS, ROMSlotDFS, ROMSlotADFS }; enum Interrupt: uint8_t { @@ -146,7 +148,7 @@ class Machine: public: Machine(); - void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void set_rom(ROMSlot slot, std::vector data, bool is_writeable); void configure_as_target(const StaticAnalyser::Target &target); void set_key_state(Key key, bool isPressed); @@ -187,7 +189,9 @@ class Machine: // Things that directly constitute the memory map. uint8_t _roms[16][16384]; + bool _rom_write_masks[16]; uint8_t _os[16384], _ram[32768]; + std::vector _dfs, _adfs; // Things affected by registers, explicitly or otherwise. uint8_t _interrupt_status, _interrupt_control; @@ -227,6 +231,10 @@ class Machine: bool _use_fast_tape_hack; bool _fast_load_is_in_data; + // Disk + std::unique_ptr _plus3; + bool is_holding_shift_; + // Outputs std::shared_ptr _crt; std::shared_ptr _speaker; diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp new file mode 100644 index 000000000..809846dcb --- /dev/null +++ b/Machines/Electron/Plus3.cpp @@ -0,0 +1,35 @@ +// +// Plus3.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Plus3.hpp" + +using namespace Electron; + +void Plus3::set_disk(std::shared_ptr disk, int drive) +{ + if(!_drives[drive]) _drives[drive].reset(new Storage::Disk::Drive); + _drives[drive]->set_disk(disk); +} + +void Plus3::set_control_register(uint8_t control) +{ + // TODO: + // bit 0 => enable or disable drive 1 + // bit 1 => enable or disable drive 2 + // bit 2 => side select + // bit 3 => single density select + switch(control&3) + { + case 0: set_drive(nullptr); break; + default: set_drive(_drives[0]); break; + case 2: set_drive(_drives[1]); break; + } + if(_drives[0]) _drives[0]->set_head((control & 0x04) ? 1 : 0); + if(_drives[1]) _drives[1]->set_head((control & 0x04) ? 1 : 0); + set_is_double_density(!(control & 0x08)); +} diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp new file mode 100644 index 000000000..e7613367b --- /dev/null +++ b/Machines/Electron/Plus3.hpp @@ -0,0 +1,28 @@ +// +// Plus3.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Plus3_hpp +#define Plus3_hpp + +#include "../../Components/1770/1770.hpp" + +namespace Electron { + +class Plus3 : public WD::WD1770 { + public: + void set_disk(std::shared_ptr disk, int drive); + void set_control_register(uint8_t control); + + private: + std::shared_ptr _drives[2]; +}; + +} + +#endif /* Plus3_hpp */ + diff --git a/NumberTheory/CRC.cpp b/NumberTheory/CRC.cpp new file mode 100644 index 000000000..2833155fd --- /dev/null +++ b/NumberTheory/CRC.cpp @@ -0,0 +1,11 @@ +// +// CRC.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRC.hpp" + +using namespace NumberTheory; diff --git a/NumberTheory/CRC.hpp b/NumberTheory/CRC.hpp new file mode 100644 index 000000000..a705e3d4c --- /dev/null +++ b/NumberTheory/CRC.hpp @@ -0,0 +1,40 @@ +// +// CRC.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRC_hpp +#define CRC_hpp + +#include + +namespace NumberTheory { + +class CRC16 { + public: + CRC16(uint16_t polynomial, uint16_t reset_value) : + reset_value_(reset_value), value_(reset_value), polynomial_(polynomial) {} + + inline void reset() { value_ = reset_value_; } + inline void add(uint8_t value) { + // TODO: go table based + value_ ^= (uint16_t)value << 8; + for(int c = 0; c < 8; c++) + { + uint16_t exclusive_or = (value_&0x8000) ? polynomial_ : 0x0000; + value_ = (uint16_t)(value_ << 1) ^ exclusive_or; + } + } + inline uint16_t get_value() { return value_; } + + private: + uint16_t reset_value_, polynomial_; + uint16_t value_; +}; + +} + +#endif /* CRC_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index d05506a5e..8f4624052 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; + 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; }; + 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; }; 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; }; 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; }; 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; }; @@ -45,12 +47,13 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; - 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */; }; + 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; }; 4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; }; 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA22B051D8817CE0008C640 /* Disk.cpp */; }; + 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; }; 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; @@ -347,13 +350,20 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; }; + 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; + 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; }; + 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; + 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; }; + 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; }; + 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; }; + 4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -415,6 +425,10 @@ 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; 4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = ""; }; 4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = ""; }; + 4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = ""; }; + 4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = ""; }; + 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = ""; }; + 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = ""; }; 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = ""; }; 4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = ""; }; 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = ""; }; @@ -449,8 +463,8 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskDrive.cpp; sourceTree = ""; }; - 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = ""; }; + 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = ""; }; + 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = ""; }; 4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = ""; }; 4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; }; 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = ""; }; @@ -458,6 +472,8 @@ 4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = ""; }; 4BA22B051D8817CE0008C640 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Commodore/Disk.cpp; sourceTree = ""; }; 4BA22B061D8817CE0008C640 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Commodore/Disk.hpp; sourceTree = ""; }; + 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = ""; }; + 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = ""; }; 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = ""; }; 4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = ""; }; @@ -789,8 +805,12 @@ 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = ""; }; + 4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = ""; }; + 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; + 4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = ""; }; + 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = ""; }; @@ -802,6 +822,16 @@ 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = ""; }; 4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = ""; }; + 4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = ""; }; + 4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = ""; }; + 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = ""; }; + 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = ""; }; + 4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = ""; }; + 4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = ""; }; + 4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = ""; }; + 4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = ""; }; + 4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = ""; }; + 4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -904,6 +934,8 @@ 4B2A53961D117D36003C6002 /* CSMachine.mm */, 4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */, 4B2A53981D117D36003C6002 /* Wrappers */, + 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */, + 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */, ); path = Machine; sourceTree = ""; @@ -936,6 +968,8 @@ children = ( 4B2E2D9B1C3A070400138695 /* Electron.cpp */, 4B2E2D9C1C3A070400138695 /* Electron.hpp */, + 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */, + 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, ); name = Electron; sourceTree = ""; @@ -1089,11 +1123,13 @@ children = ( 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, - 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */, + 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */, + 4B30512B1D989E2200B4FED8 /* Drive.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, - 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */, + 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */, + 4B30512C1D989E2200B4FED8 /* Drive.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, @@ -1108,6 +1144,10 @@ 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */, 4B4C836E1D4F623200CD541F /* D64.cpp */, 4B4C836F1D4F623200CD541F /* D64.hpp */, + 4BF829611D8F536B001BAE39 /* SSD.cpp */, + 4BF829621D8F536B001BAE39 /* SSD.hpp */, + 4BD69F921D98760000243FE1 /* AcornADF.cpp */, + 4BD69F931D98760000243FE1 /* AcornADF.hpp */, ); path = Formats; sourceTree = ""; @@ -1388,6 +1428,8 @@ isa = PBXGroup; children = ( 4BB697C61D4B558F00248BDF /* Factors.hpp */, + 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */, + 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */, ); name = NumberTheory; sourceTree = ""; @@ -1397,6 +1439,8 @@ children = ( 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */, 4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */, + 4BF8295B1D8F048B001BAE39 /* MFM.cpp */, + 4BF8295C1D8F048B001BAE39 /* MFM.hpp */, ); name = Encodings; sourceTree = ""; @@ -1538,16 +1582,16 @@ 4BC830D21D6E7C6D0000A26F /* Commodore */ = { isa = PBXGroup; children = ( + 4BA22B051D8817CE0008C640 /* Disk.cpp */, + 4BA22B061D8817CE0008C640 /* Disk.hpp */, + 4BE77A2C1D84ADFB00BC3827 /* File.cpp */, + 4BE77A2D1D84ADFB00BC3827 /* File.hpp */, 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */, 4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */, 4BC830CF1D6E7C690000A26F /* Tape.cpp */, 4BC830D01D6E7C690000A26F /* Tape.hpp */, 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */, 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */, - 4BE77A2C1D84ADFB00BC3827 /* File.cpp */, - 4BE77A2D1D84ADFB00BC3827 /* File.hpp */, - 4BA22B051D8817CE0008C640 /* Disk.cpp */, - 4BA22B061D8817CE0008C640 /* Disk.hpp */, ); name = Commodore; sourceTree = ""; @@ -1558,6 +1602,7 @@ 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, + 4BD468F81D8DF4290084958B /* 1770 */, ); name = Components; path = ../../Components; @@ -1583,6 +1628,10 @@ 4BD14B121D7462810088EAD6 /* Acorn */ = { isa = PBXGroup; children = ( + 4BF829641D8F732B001BAE39 /* Disk.cpp */, + 4BF829651D8F732B001BAE39 /* Disk.hpp */, + 4BF829671D8F7361001BAE39 /* File.cpp */, + 4BF829681D8F7361001BAE39 /* File.hpp */, 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */, 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */, 4B96F7201D75119A0058BB2D /* Tape.cpp */, @@ -1591,6 +1640,15 @@ name = Acorn; sourceTree = ""; }; + 4BD468F81D8DF4290084958B /* 1770 */ = { + isa = PBXGroup; + children = ( + 4BD468F51D8DF41D0084958B /* 1770.cpp */, + 4BD468F61D8DF41D0084958B /* 1770.hpp */, + ); + name = 1770; + sourceTree = ""; + }; 4BD5F1961D1352A000631CD1 /* Updater */ = { isa = PBXGroup; children = ( @@ -2061,6 +2119,7 @@ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, + 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, @@ -2069,23 +2128,30 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */, 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, + 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, + 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, + 4BF829691D8F7361001BAE39 /* File.cpp in Sources */, + 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */, 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, + 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, + 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, - 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */, + 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, + 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, @@ -2095,6 +2161,8 @@ 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, + 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, + 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */, 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift b/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift index 4625bdc50..b2c7d2e18 100644 --- a/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift +++ b/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift @@ -21,6 +21,6 @@ class DocumentController: NSDocumentController { } } - return try! super.makeDocument(withContentsOf: url, ofType: typeName) + return try super.makeDocument(withContentsOf: url, ofType: typeName) } } diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 73aab5fee..a577b2b77 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -31,15 +31,27 @@ class ElectronDocument: MachineDocument { return dataForResource(name, ofType: "rom", inDirectory: "ROMImages/Electron") } - override func windowControllerDidLoadNib(_ aController: NSWindowController) { - super.windowControllerDidLoadNib(aController) + override init() { + super.init(); if let os = rom("os"), let basic = rom("basic") { self.electron.setOSROM(os) self.electron.setBASICROM(basic) } + if let dfs = rom("DFS-1770-2.20") { + self.electron.setDFSROM(dfs) + } + if let adfs1 = rom("ADFS-E00_1"), let adfs2 = rom("ADFS-E00_2") { + var fullADFS = adfs1 + fullADFS.append(adfs2) + self.electron.setADFSROM(fullADFS as Data) + } } +// override func windowControllerDidLoadNib(_ aController: NSWindowController) { +// super.windowControllerDidLoadNib(aController) +// } + override var windowNibName: String? { return "ElectronDocument" } diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index cd2e9b763..5db4e8201 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -130,6 +130,22 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).Vic20Document + + CFBundleTypeExtensions + + ssd + dsd + adf + adl + adm + + CFBundleTypeName + Electron/BBC Disk Image + CFBundleTypeRole + Viewer + NSDocumentClass + $(PRODUCT_MODULE_NAME).ElectronDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h new file mode 100644 index 000000000..3e1749ef9 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h @@ -0,0 +1,18 @@ +// +// NSData+StdVector.h +// Clock Signal +// +// Created by Thomas Harte on 20/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +#include +#include + +@interface NSData (StdVector) + +- (std::vector)stdVector8; + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm new file mode 100644 index 000000000..88a32c457 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm @@ -0,0 +1,19 @@ +// +// NSData+StdVector.m +// Clock Signal +// +// Created by Thomas Harte on 20/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "NSData+StdVector.h" + +@implementation NSData (StdVector) + +- (std::vector)stdVector8 +{ + uint8_t *bytes8 = (uint8_t *)self.bytes; + return std::vector(bytes8, bytes8 + self.length); +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h index 13e44e4f8..d96647745 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h @@ -16,7 +16,8 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; -- (void)setROM:(nonnull NSData *)rom slot:(int)slot; +- (void)setDFSROM:(nonnull NSData *)rom; +- (void)setADFSROM:(nonnull NSData *)rom; @property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) BOOL useTelevisionOutput; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index d3635d2a5..97f3ed865 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -10,6 +10,7 @@ #include "Electron.hpp" #import "CSMachine+Subclassing.h" +#import "NSData+StdVector.h" #include "StaticAnalyser.hpp" #include "TapeUEF.hpp" @@ -25,21 +26,14 @@ StaticAnalyser::GetTargets([url fileSystemRepresentation]); } -- (void)setOSROM:(nonnull NSData *)rom { - @synchronized(self) { - _electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes); - } -} - -- (void)setBASICROM:(nonnull NSData *)rom { - @synchronized(self) { - _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); - } -} +- (void)setOSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotOS]; } +- (void)setBASICROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotBASIC]; } +- (void)setADFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotADFS]; } +- (void)setDFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotDFS]; } - (void)setROM:(nonnull NSData *)rom slot:(int)slot { @synchronized(self) { - _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); + _electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false); } } diff --git a/ROMImages/Electron/readme.txt b/ROMImages/Electron/readme.txt index 8e81ed5e6..accfc031e 100644 --- a/ROMImages/Electron/readme.txt +++ b/ROMImages/Electron/readme.txt @@ -5,12 +5,12 @@ Expected files: basic.rom os.rom plus1.rom +DFS-1770-2.20.rom +ADFS-E00_1.rom +ADFS-E00_2.rom Likely to be desired in the future: adfs.rom -ADFS-E00_1.rom -ADFS-E00_2.rom -DFSE00r3.rom ElectronExpansionRomPresAP2-v1.23.rom os300.rom diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp new file mode 100644 index 000000000..20fa8a0a6 --- /dev/null +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -0,0 +1,280 @@ +// +// Disk.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Disk.hpp" +#include "../../Storage/Disk/DiskController.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" +#include "../../NumberTheory/CRC.hpp" +#include + +using namespace StaticAnalyser::Acorn; + +class FMParser: public Storage::Disk::Controller { + public: + std::shared_ptr drive; + + FMParser(bool is_mfm) : + Storage::Disk::Controller(4000000, 1, 300), + crc_generator_(0x1021, 0xffff), + shift_register_(0), track_(0), is_mfm_(is_mfm) + { + Storage::Time bit_length; + bit_length.length = 1; + bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) + set_expected_bit_length(bit_length); + + drive.reset(new Storage::Disk::Drive); + set_drive(drive); + set_motor_on(true); + } + + /*! + Attempts to read the sector located at @c track and @c sector. + + @returns a sector if one was found; @c nullptr otherwise. + */ + std::shared_ptr get_sector(uint8_t track, uint8_t sector) + { + int difference = (int)track - (int)track_; + track_ = track; + + if(difference) + { + int direction = difference < 0 ? -1 : 1; + difference *= direction; + + for(int c = 0; c < difference; c++) step(direction); + } + + return get_sector(sector); + } + + private: + unsigned int shift_register_; + int index_count_; + uint8_t track_; + int bit_count_; + std::shared_ptr sector_cache_[65536]; + NumberTheory::CRC16 crc_generator_; + bool is_mfm_; + + void process_input_bit(int value, unsigned int cycles_since_index_hole) + { + shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; + bit_count_++; + } + + void process_index_hole() + { + index_count_++; + } + + uint8_t get_next_byte() + { + bit_count_ = 0; + while(bit_count_ < 16) run_for_cycles(1); + uint8_t byte = (uint8_t)( + ((shift_register_&0x0001) >> 0) | + ((shift_register_&0x0004) >> 1) | + ((shift_register_&0x0010) >> 2) | + ((shift_register_&0x0040) >> 3) | + ((shift_register_&0x0100) >> 4) | + ((shift_register_&0x0400) >> 5) | + ((shift_register_&0x1000) >> 6) | + ((shift_register_&0x4000) >> 7)); + crc_generator_.add(byte); + return byte; + } + + std::shared_ptr get_next_sector() + { + std::shared_ptr sector(new Storage::Encodings::MFM::Sector); + index_count_ = 0; + + while(index_count_ < 2) + { + // look for an ID address mark + while(1) + { + run_for_cycles(1); + if(is_mfm_) + { + if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) break; + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break; + } + if(index_count_ >= 2) return nullptr; + } + + crc_generator_.reset(); + sector->track = get_next_byte(); + sector->side = get_next_byte(); + sector->sector = get_next_byte(); + uint8_t size = get_next_byte(); + uint16_t header_crc = crc_generator_.get_value(); + if((header_crc >> 8) != get_next_byte()) continue; + if((header_crc & 0xff) != get_next_byte()) continue; + + // look for data mark + while(1) + { + run_for_cycles(1); + if(is_mfm_) + { + if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMDataAddressByte) break; + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr; + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break; + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; + } + if(index_count_ >= 2) return nullptr; + } + + size_t data_size = (size_t)(128 << size); + sector->data.reserve(data_size); + crc_generator_.reset(); + for(size_t c = 0; c < data_size; c++) + { + sector->data.push_back(get_next_byte()); + } + uint16_t data_crc = crc_generator_.get_value(); + if((data_crc >> 8) != get_next_byte()) continue; + if((data_crc & 0xff) != get_next_byte()) continue; + + return sector; + } + + return nullptr; + } + + std::shared_ptr get_sector(uint8_t sector) + { +// uint16_t sector_address = (uint16_t)((track_ << 8) | sector); +// if(sector_cache_[sector_address]) return sector_cache_[sector_address]; + + std::shared_ptr first_sector = get_next_sector(); + if(!first_sector) return first_sector; + if(first_sector->sector == sector) return first_sector; + + while(1) + { + std::shared_ptr next_sector = get_next_sector(); + if(next_sector->sector == first_sector->sector) return nullptr; + if(next_sector->sector == sector) return next_sector; + } + } +}; + +std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr &disk) +{ + // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format + std::unique_ptr catalogue(new Catalogue); + FMParser parser(false); + parser.drive->set_disk(disk); + + std::shared_ptr names = parser.get_sector(0, 0); + std::shared_ptr details = parser.get_sector(0, 1); + + if(!names || !details) return nullptr; + if(names->data.size() != 256 || details->data.size() != 256) return nullptr; + + uint8_t final_file_offset = details->data[5]; + if(final_file_offset&7) return nullptr; + + char disk_name[13]; + snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]); + catalogue->name = disk_name; + + switch((details->data[6] >> 4)&3) + { + case 0: catalogue->bootOption = Catalogue::BootOption::None; break; + case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; + case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; + case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break; + } + + // DFS files are stored contiguously, and listed in descending order of distance from track 0. + // So iterating backwards implies the least amount of seeking. + for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) + { + File new_file; + char name[10]; + snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]); + new_file.name = name; + new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14)); + new_file.execution_address = (uint32_t)(details->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10)); + new_file.is_protected = !!(names->data[file_offset + 7] & 0x80); + + long data_length = (long)(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12)); + int start_sector = details->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8); + new_file.data.reserve((size_t)data_length); + + if(start_sector < 2) continue; + while(data_length > 0) + { + uint8_t sector = (uint8_t)(start_sector % 10); + uint8_t track = (uint8_t)(start_sector / 10); + start_sector++; + + std::shared_ptr next_sector = parser.get_sector(track, sector); + if(!next_sector) break; + + long length_from_sector = std::min(data_length, 256l); + new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector); + data_length -= length_from_sector; + } + if(!data_length) catalogue->files.push_front(new_file); + } + + return catalogue; +} +std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr &disk) +{ + std::unique_ptr catalogue(new Catalogue); + FMParser parser(true); + parser.drive->set_disk(disk); + + std::shared_ptr free_space_map_second_half = parser.get_sector(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); + if(!sector) return nullptr; + root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); + } + + // Quick sanity checks. + if(root_directory[0x4cb]) return nullptr; + if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr; + if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr; + + switch(free_space_map_second_half->data[0xfd]) + { + default: catalogue->bootOption = Catalogue::BootOption::None; break; + case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; + case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; + case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break; + } + + return catalogue; +} diff --git a/StaticAnalyser/Acorn/Disk.hpp b/StaticAnalyser/Acorn/Disk.hpp new file mode 100644 index 000000000..5b7bc3ed7 --- /dev/null +++ b/StaticAnalyser/Acorn/Disk.hpp @@ -0,0 +1,35 @@ +// +// Disk.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Acorn_Disk_hpp +#define StaticAnalyser_Acorn_Disk_hpp + +#include "File.hpp" +#include "../../Storage/Disk/Disk.hpp" + +namespace StaticAnalyser { +namespace Acorn { + +struct Catalogue { + std::string name; + std::list files; + enum class BootOption { + None, + LoadBOOT, + RunBOOT, + ExecBOOT + } bootOption; +}; + +std::unique_ptr GetDFSCatalogue(const std::shared_ptr &disk); +std::unique_ptr GetADFSCatalogue(const std::shared_ptr &disk); + +} +} + +#endif /* Disk_hpp */ diff --git a/StaticAnalyser/Acorn/File.cpp b/StaticAnalyser/Acorn/File.cpp new file mode 100644 index 000000000..b175127be --- /dev/null +++ b/StaticAnalyser/Acorn/File.cpp @@ -0,0 +1,9 @@ +// +// File.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "File.hpp" diff --git a/StaticAnalyser/Acorn/File.hpp b/StaticAnalyser/Acorn/File.hpp new file mode 100644 index 000000000..a7f942705 --- /dev/null +++ b/StaticAnalyser/Acorn/File.hpp @@ -0,0 +1,47 @@ +// +// File.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef File_hpp +#define File_hpp + +#include +#include +#include +#include + +namespace StaticAnalyser { +namespace Acorn { + +struct File { + std::string name; + uint32_t load_address; + uint32_t execution_address; + bool is_protected; + std::vector data; + + struct Chunk { + std::string name; + uint32_t load_address; + uint32_t execution_address; + uint16_t block_number; + uint16_t block_length; + uint8_t block_flag; + uint32_t next_address; + + bool header_crc_matched; + bool data_crc_matched; + std::vector data; + }; + + std::list chunks; +}; + +} +} + +#endif /* File_hpp */ diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index 42efa33be..f15bf1a65 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -8,6 +8,7 @@ #include "StaticAnalyser.hpp" +#include "Disk.hpp" #include "Tape.hpp" using namespace StaticAnalyser::Acorn; @@ -66,6 +67,9 @@ void StaticAnalyser::Acorn::AddTargets( Target target; target.machine = Target::Electron; target.probability = 1.0; // TODO: a proper estimation + target.acorn.has_dfs = false; + target.acorn.has_adfs = false; + target.acorn.should_hold_shift = false; // strip out inappropriate cartridges target.cartridges = AcornCartridgesFrom(cartridges); @@ -110,8 +114,35 @@ void StaticAnalyser::Acorn::AddTargets( } } - // TODO: disks + if(disks.size() > 0) + { + std::shared_ptr disk = disks.front(); + std::unique_ptr dfs_catalogue, adfs_catalogue; + dfs_catalogue = GetDFSCatalogue(disk); + if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); + if(dfs_catalogue || adfs_catalogue) + { + target.disks = disks; + target.acorn.has_dfs = !!dfs_catalogue; + target.acorn.has_adfs = !!adfs_catalogue; - if(target.tapes.size() || target.cartridges.size()) + std::string adfs_command; + Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; + switch(bootOption) + { + case Catalogue::BootOption::None: adfs_command = "*CAT\n"; break; + case Catalogue::BootOption::LoadBOOT: adfs_command = "*MOUNT\n*LOAD !BOOT\n"; break; + case Catalogue::BootOption::RunBOOT: adfs_command = "*MOUNT\n*RUN !BOOT\n"; break; + case Catalogue::BootOption::ExecBOOT: adfs_command = "*MOUNT\n*EXEC !BOOT\n"; break; + } + + if(target.acorn.has_dfs && bootOption != Catalogue::BootOption::None) + target.acorn.should_hold_shift = true; + else + target.loadingCommand = adfs_command; + } + } + + if(target.tapes.size() || target.disks.size() || target.cartridges.size()) destination.push_back(target); } diff --git a/StaticAnalyser/Acorn/Tape.cpp b/StaticAnalyser/Acorn/Tape.cpp index c91573094..056b05ccb 100644 --- a/StaticAnalyser/Acorn/Tape.cpp +++ b/StaticAnalyser/Acorn/Tape.cpp @@ -10,6 +10,7 @@ #include #include "../TapeParser.hpp" +#include "../../NumberTheory/CRC.hpp" using namespace StaticAnalyser::Acorn; @@ -23,7 +24,9 @@ enum class SymbolType { class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser { public: - Acorn1200BaudTapeParser(const std::shared_ptr &tape) : TapeParser(tape) {} + Acorn1200BaudTapeParser(const std::shared_ptr &tape) : + TapeParser(tape), + _crc(0x1021, 0x0000) {} int get_next_bit() { @@ -49,7 +52,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser GetNextChunk(Acorn1200BaudTapeParser &parser) diff --git a/StaticAnalyser/Acorn/Tape.hpp b/StaticAnalyser/Acorn/Tape.hpp index 570ebab1e..ac13b6ab3 100644 --- a/StaticAnalyser/Acorn/Tape.hpp +++ b/StaticAnalyser/Acorn/Tape.hpp @@ -9,40 +9,14 @@ #ifndef StaticAnalyser_Acorn_Tape_hpp #define StaticAnalyser_Acorn_Tape_hpp -#include #include -#include -#include +#include "File.hpp" #include "../../Storage/Tape/Tape.hpp" namespace StaticAnalyser { namespace Acorn { -struct File { - std::string name; - uint32_t load_address; - uint32_t execution_address; - bool is_protected; - std::vector data; - - struct Chunk { - std::string name; - uint32_t load_address; - uint32_t execution_address; - uint16_t block_number; - uint16_t block_length; - uint8_t block_flag; - uint32_t next_address; - - bool header_crc_matched; - bool data_crc_matched; - std::vector data; - }; - - std::list chunks; -}; - std::list GetFiles(const std::shared_ptr &tape); } diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index ee7e8b457..5929fc65e 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -7,7 +7,7 @@ // #include "Disk.hpp" -#include "../../Storage/Disk/DiskDrive.hpp" +#include "../../Storage/Disk/DiskController.hpp" #include "../../Storage/Disk/Encodings/CommodoreGCR.hpp" #include "Utilities.hpp" @@ -17,12 +17,14 @@ using namespace StaticAnalyser::Commodore; -class CommodoreGCRParser: public Storage::Disk::Drive { +class CommodoreGCRParser: public Storage::Disk::Controller { public: - CommodoreGCRParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(1) + std::shared_ptr drive; + + CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { - // Make sure this drive really is at track '1'. - while(!get_is_track_zero()) step(-1); + drive.reset(new Storage::Disk::Drive); + set_drive(drive); } struct Sector @@ -186,7 +188,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptr files; CommodoreGCRParser parser; - parser.set_disk(disk); + parser.drive->set_disk(disk); // find any sector whatsoever to establish the current track std::shared_ptr sector; diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 2f57788f2..78647447e 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -20,8 +20,10 @@ #include "../Storage/Cartridge/Formats/PRG.hpp" // Disks +#include "../Storage/Disk/Formats/AcornADF.hpp" #include "../Storage/Disk/Formats/D64.hpp" #include "../Storage/Disk/Formats/G64.hpp" +#include "../Storage/Disk/Formats/SSD.hpp" // Tapes #include "../Storage/Tape/Formats/CommodoreTAP.hpp" @@ -79,8 +81,10 @@ std::list StaticAnalyser::GetTargets(const char *file_name) } Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 + Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64 + Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64 // PRG @@ -98,8 +102,8 @@ std::list StaticAnalyser::GetTargets(const char *file_name) } } - // ROM Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM + Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 691d5e364..94df52560 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -46,6 +46,7 @@ struct Target { struct { bool has_adfs; bool has_dfs; + bool should_hold_shift; } acorn; }; diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index c17712b85..50466161e 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -73,10 +73,15 @@ class Disk { */ virtual unsigned int get_head_position_count() = 0; + /*! + Returns the number of heads (and, therefore, impliedly surfaces) available on this disk. + */ + virtual unsigned int get_head_count() { return 1; } + /*! Returns the @c Track at @c position if there are any detectable events there; returns @c nullptr otherwise. */ - virtual std::shared_ptr get_track_at_position(unsigned int position) = 0; + virtual std::shared_ptr get_track_at_position(unsigned int head, unsigned int position) = 0; }; } diff --git a/Storage/Disk/DiskDrive.cpp b/Storage/Disk/DiskController.cpp similarity index 64% rename from Storage/Disk/DiskDrive.cpp rename to Storage/Disk/DiskController.cpp index 681a9e94d..d8302e605 100644 --- a/Storage/Disk/DiskDrive.cpp +++ b/Storage/Disk/DiskController.cpp @@ -1,68 +1,37 @@ // -// DiskDrive.cpp +// DiskController.cpp // Clock Signal // // Created by Thomas Harte on 14/07/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "DiskDrive.hpp" +#include "DiskController.hpp" using namespace Storage::Disk; -Drive::Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) : +Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) : _clock_rate(clock_rate * clock_rate_multiplier), _clock_rate_multiplier(clock_rate_multiplier), - _head_position(0), TimedEventLoop(clock_rate * clock_rate_multiplier) { _rotational_multiplier.length = 60; _rotational_multiplier.clock_rate = revolutions_per_minute; _rotational_multiplier.simplify(); + + // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later + Time one; + set_expected_bit_length(one); } -void Drive::set_expected_bit_length(Time bit_length) +void Controller::setup_track() // Time initial_offset { - _bit_length = bit_length; + _track = _drive->get_track(); +// _track = _disk->get_track_at_position(0, (unsigned int)_head_position); - // this conversion doesn't need to be exact because there's a lot of variation to be taken - // account of in rotation speed, air turbulence, etc, so a direct conversion will do - int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate); - _pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3)); - _pll->set_delegate(this); -} - -void Drive::set_disk(std::shared_ptr disk) -{ - _disk = disk; - set_track(Time()); -} - -bool Drive::has_disk() -{ - return (bool)_disk; -} - -bool Drive::get_is_track_zero() -{ - return _head_position == 0; -} - -void Drive::step(int direction) -{ - _head_position = std::max(_head_position + direction, 0); - Time extra_time = get_time_into_next_event() / _rotational_multiplier; - extra_time.simplify(); - _time_into_track += extra_time; - set_track(_time_into_track); -} - -void Drive::set_track(Time initial_offset) -{ - _track = _disk->get_track_at_position((unsigned int)_head_position); // TODO: probably a better implementation of the empty track? - Time offset; +/* Time offset; if(_track && _time_into_track.length > 0) { Time time_found = _track->seek_to(_time_into_track).simplify(); @@ -73,17 +42,18 @@ void Drive::set_track(Time initial_offset) { offset = _time_into_track; _time_into_track.set_zero(); - } + }*/ reset_timer(); get_next_event(); - reset_timer_to_offset(offset * _rotational_multiplier); +// reset_timer_to_offset(offset * _rotational_multiplier); } -void Drive::run_for_cycles(int number_of_cycles) +void Controller::run_for_cycles(int number_of_cycles) { - if(has_disk()) + if(_drive && _drive->has_disk() && _motor_is_on) { + if(!_track) setup_track(); number_of_cycles *= _clock_rate_multiplier; while(number_of_cycles) { @@ -99,7 +69,7 @@ void Drive::run_for_cycles(int number_of_cycles) #pragma mark - Track timed event loop -void Drive::get_next_event() +void Controller::get_next_event() { if(_track) _current_event = _track->get_next_event(); @@ -115,7 +85,7 @@ void Drive::get_next_event() set_next_event_time_interval(_current_event.length * _rotational_multiplier); } -void Drive::process_next_event() +void Controller::process_next_event() { switch(_current_event.type) { @@ -132,9 +102,50 @@ void Drive::process_next_event() get_next_event(); } -#pragma mark - PLL delegate +#pragma mark - PLL control and delegate -void Drive::digital_phase_locked_loop_output_bit(int value) +void Controller::set_expected_bit_length(Time bit_length) +{ + _bit_length = bit_length; + + // this conversion doesn't need to be exact because there's a lot of variation to be taken + // account of in rotation speed, air turbulence, etc, so a direct conversion will do + int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate); + _pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3)); + _pll->set_delegate(this); +} + +void Controller::digital_phase_locked_loop_output_bit(int value) { process_input_bit(value, _cycles_since_index_hole); } + +#pragma mark - Drive actions + +bool Controller::get_is_track_zero() +{ + if(!_drive) return false; + return _drive->get_is_track_zero(); +} + +void Controller::step(int direction) +{ + if(_drive) _drive->step(direction); + invalidate_track(); +} + +void Controller::set_motor_on(bool motor_on) +{ + _motor_is_on = motor_on; +} + +void Controller::set_drive(std::shared_ptr drive) +{ + _drive = drive; + invalidate_track(); +} + +void Controller::invalidate_track() +{ + _track = nullptr; +} diff --git a/Storage/Disk/DiskDrive.hpp b/Storage/Disk/DiskController.hpp similarity index 61% rename from Storage/Disk/DiskDrive.hpp rename to Storage/Disk/DiskController.hpp index 017e34464..b83165c98 100644 --- a/Storage/Disk/DiskDrive.hpp +++ b/Storage/Disk/DiskController.hpp @@ -1,15 +1,15 @@ // -// DiskDrive.hpp +// DiskController.hpp // Clock Signal // // Created by Thomas Harte on 14/07/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef DiskDrive_hpp -#define DiskDrive_hpp +#ifndef Storage_Disk_Controller_hpp +#define Storage_Disk_Controller_hpp -#include "Disk.hpp" +#include "Drive.hpp" #include "DigitalPhaseLockedLoop.hpp" #include "../TimedEventLoop.hpp" @@ -17,64 +17,43 @@ namespace Storage { namespace Disk { /*! - Provides the shell for emulating a disk drive — something that takes a disk and has a drive head - that steps between tracks, using a phase locked loop ('PLL') to decode a bit stream from the surface of - the disk. + Provides the shell for emulating a disk controller — something that is connected to a disk drive and uses a + phase locked loop ('PLL') to decode a bit stream from the surface of the disk. Partly abstract; it is expected that subclasses will provide methods to deal with receiving a newly-recognised bit from the PLL and with crossing the index hole. - TODO: double sided disks, communication of head size and permissible stepping extents, appropriate - simulation of gain. + TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { - public: +class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { + protected: /*! Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, spinning inserted disks at @c revolutions_per_minute. */ - Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); + Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); /*! - Communicates to the PLL the expected length of a bit. + Communicates to the PLL the expected length of a bit as a fraction of a second. */ void set_expected_bit_length(Time bit_length); - /*! - Inserts @c disk into the drive. - */ - void set_disk(std::shared_ptr disk); - - /*! - @returns @c true if a disk is currently inserted; @c false otherwise. - */ - bool has_disk(); - /*! Advances the drive by @c number_of_cycles cycles. */ void run_for_cycles(int number_of_cycles); /*! - @returns @c true if the drive head is currently at track zero; @c false otherwise. + Sets the current drive. */ - bool get_is_track_zero(); - - /*! - Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers - step outwards. - */ - void step(int direction); + void set_drive(std::shared_ptr drive); + void invalidate_track(); /*! Enables or disables the disk motor. */ void set_motor_on(bool motor_on); - // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); - - protected: /*! Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying the amount of time since the index hole was last seen. @@ -89,6 +68,12 @@ class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { // for TimedEventLoop virtual void process_next_event(); + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); + + bool get_is_track_zero(); + void step(int direction); + private: Time _bit_length; unsigned int _clock_rate; @@ -96,15 +81,16 @@ class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { Time _rotational_multiplier; std::shared_ptr _pll; - std::shared_ptr _disk; + std::shared_ptr _drive; std::shared_ptr _track; - int _head_position; unsigned int _cycles_since_index_hole; - void set_track(Time initial_offset); inline void get_next_event(); Track::Event _current_event; Time _time_into_track; + bool _motor_is_on; + + void setup_track(); }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp new file mode 100644 index 000000000..564dee21b --- /dev/null +++ b/Storage/Disk/Drive.cpp @@ -0,0 +1,46 @@ +// +// Drive.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Drive.hpp" +#include + +using namespace Storage::Disk; + +Drive::Drive() + : _head_position(0), _head(0) {} + +void Drive::set_disk(std::shared_ptr disk) +{ + _disk = disk; +} + +bool Drive::has_disk() +{ + return (bool)_disk; +} + +bool Drive::get_is_track_zero() +{ + return _head_position == 0; +} + +void Drive::step(int direction) +{ + _head_position = std::max(_head_position + direction, 0); +} + +void Drive::set_head(unsigned int head) +{ + _head = head; +} + +std::shared_ptr Drive::get_track() +{ + if(_disk) return _disk->get_track_at_position(_head, (unsigned int)_head_position); + return nullptr; +} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp new file mode 100644 index 000000000..e63434f7f --- /dev/null +++ b/Storage/Disk/Drive.hpp @@ -0,0 +1,59 @@ +// +// Drive.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Drive_hpp +#define Drive_hpp + +#include +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +class Drive { + public: + Drive(); + + /*! + Inserts @c disk into the drive. + */ + void set_disk(std::shared_ptr disk); + + /*! + @returns @c true if a disk is currently inserted; @c false otherwise. + */ + bool has_disk(); + + /*! + @returns @c true if the drive head is currently at track zero; @c false otherwise. + */ + bool get_is_track_zero(); + + /*! + Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers + step outwards. + */ + void step(int direction); + + /*! + */ + void set_head(unsigned int head); + + std::shared_ptr get_track(); + + private: + std::shared_ptr _disk; + int _head_position; + unsigned int _head; +}; + + +} +} + +#endif /* Drive_hpp */ diff --git a/Storage/Disk/Encodings/CommodoreGCR.hpp b/Storage/Disk/Encodings/CommodoreGCR.hpp index 34346e253..9b8a0c055 100644 --- a/Storage/Disk/Encodings/CommodoreGCR.hpp +++ b/Storage/Disk/Encodings/CommodoreGCR.hpp @@ -6,8 +6,8 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef CommodoreGCR_hpp -#define CommodoreGCR_hpp +#ifndef Storage_Disk_Encodings_CommodoreGCR_hpp +#define Storage_Disk_Encodings_CommodoreGCR_hpp #include "../../Storage.hpp" #include diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp new file mode 100644 index 000000000..949588528 --- /dev/null +++ b/Storage/Disk/Encodings/MFM.cpp @@ -0,0 +1,217 @@ +// +// MFM.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "MFM.hpp" + +#include "../PCMTrack.hpp" +#include "../../../NumberTheory/CRC.hpp" + +using namespace Storage::Encodings::MFM; + +template class Shifter { + public: + virtual void add_byte(uint8_t input) = 0; + virtual void add_index_address_mark() = 0; + virtual void add_ID_address_mark() = 0; + virtual void add_data_address_mark() = 0; + virtual void add_deleted_data_address_mark() = 0; + + protected: + /*! + Intended to be overridden by subclasses; should write value out as PCM data, + MSB first. + */ + void output_short(uint16_t value); +}; + +template class MFMShifter: public Shifter { + public: + void add_byte(uint8_t input) { + uint16_t spread_value = + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) + ); + uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15)); + output_ = spread_value | ((~or_bits) & 0xaaaa); + static_cast(this)->output_short(output_); + } + + void add_index_address_mark() { + static_cast(this)->output_short(output_ = MFMIndexAddressMark); + add_byte(MFMIndexAddressByte); + } + + void add_ID_address_mark() { + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMIDAddressByte); + } + + void add_data_address_mark() { + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMDataAddressByte); + } + + void add_deleted_data_address_mark() { + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMDeletedDataAddressByte); + } + + private: + uint16_t output_; +}; + +template class FMShifter: public Shifter { + // encodes each 16-bit part as clock, data, clock, data [...] + public: + void add_byte(uint8_t input) { + static_cast(this)->output_short( + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) | + 0xaaaa + )); + } + + void add_index_address_mark() { static_cast(this)->output_short(FMIndexAddressMark); } + void add_ID_address_mark() { static_cast(this)->output_short(FMIDAddressMark); } + void add_data_address_mark() { static_cast(this)->output_short(FMDataAddressMark); } + void add_deleted_data_address_mark() { static_cast(this)->output_short(FMDeletedDataAddressMark); } +}; + +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_data_mark_bytes, size_t post_data_bytes, + size_t inter_sector_gap, + size_t expected_track_bytes) +{ + T shifter; + NumberTheory::CRC16 crc_generator(0x1021, 0xffff); + + // output the index mark + shifter.add_index_address_mark(); + + // add the post-index mark + for(int c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value); + + // add sectors + for(const Sector §or : sectors) + { + // gap + for(int c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00); + + // sector header + shifter.add_ID_address_mark(); + 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); + + // header CRC + crc_generator.reset(); + crc_generator.add(sector.track); + crc_generator.add(sector.side); + crc_generator.add(sector.sector); + crc_generator.add(size); + uint16_t crc_value = crc_generator.get_value(); + shifter.add_byte(crc_value >> 8); + shifter.add_byte(crc_value & 0xff); + + // gap + for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); + for(int c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); + + // data + shifter.add_data_address_mark(); + crc_generator.reset(); + for(size_t c = 0; c < sector.data.size(); c++) + { + shifter.add_byte(sector.data[c]); + crc_generator.add(sector.data[c]); + } + + // data CRC + crc_value = crc_generator.get_value(); + shifter.add_byte(crc_value >> 8); + shifter.add_byte(crc_value & 0xff); + + // gap + for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); + for(int c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e); + } + + while(shifter.segment.data.size() < expected_track_bytes) shifter.add_byte(0x00); + + shifter.segment.number_of_bits = (unsigned int)(shifter.segment.data.size() * 8); + return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(shifter.segment))); +} + +struct VectorReceiver { + void output_short(uint16_t value) { + segment.data.push_back(value >> 8); + segment.data.push_back(value & 0xff); + } + Storage::Disk::PCMSegment segment; +}; + +std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) +{ + struct VectorShifter: public FMShifter, VectorReceiver { + using VectorReceiver::output_short; + }; + return GetTrackWithSectors( + sectors, + 16, 0x00, + 6, 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) +{ + struct VectorShifter: public MFMShifter, VectorReceiver { + using VectorReceiver::output_short; + }; + return GetTrackWithSectors( + sectors, + 50, 0x4e, + 12, 22, + 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 new file mode 100644 index 000000000..affb2fb78 --- /dev/null +++ b/Storage/Disk/Encodings/MFM.hpp @@ -0,0 +1,45 @@ +// +// MFM.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Disk_Encodings_MFM_hpp +#define Storage_Disk_Encodings_MFM_hpp + +#include +#include +#include "../Disk.hpp" + +namespace Storage { +namespace Encodings { +namespace MFM { + +const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010 +const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110 +const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 +const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 + +const uint16_t MFMIndexAddressMark = 0x5224; +const uint16_t MFMAddressMark = 0x4489; +const uint8_t MFMIndexAddressByte = 0xfc; +const uint8_t MFMIDAddressByte = 0xfe; +const uint8_t MFMDataAddressByte = 0xfb; +const uint8_t MFMDeletedDataAddressByte = 0xf8; + + +struct Sector { + uint8_t track, side, sector; + std::vector data; +}; + +std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors); +std::shared_ptr GetFMTrackWithSectors(const std::vector §ors); + +} +} +} + +#endif /* MFM_hpp */ diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp new file mode 100644 index 000000000..c82d27e65 --- /dev/null +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -0,0 +1,87 @@ +// +// AcornADF.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "AcornADF.hpp" + +#include +#include "../Encodings/MFM.hpp" + +namespace { + static const unsigned int sectors_per_track = 16; + static const unsigned int bytes_per_sector = 256; +} + +using namespace Storage::Disk; + +AcornADF::AcornADF(const char *file_name) : _file(nullptr) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // very loose validation: the file needs to be a multiple of 256 bytes + // and not ungainly large + if(file_stats.st_size % bytes_per_sector) throw ErrorNotAcornADF; + if(file_stats.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF; + + _file = fopen(file_name, "rb"); + if(!_file) throw ErrorCantOpen; + + // check that the initial directory's 'Hugo's are present + fseek(_file, 513, SEEK_SET); + uint8_t bytes[4]; + fread(bytes, 1, 4, _file); + if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; + + fseek(_file, 0x6fb, SEEK_SET); + fread(bytes, 1, 4, _file); + if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; +} + +AcornADF::~AcornADF() +{ + if(_file) fclose(_file); +} + +unsigned int AcornADF::get_head_position_count() +{ + return 80; +} + +unsigned int AcornADF::get_head_count() +{ + return 1; +} + +std::shared_ptr AcornADF::get_track_at_position(unsigned int head, unsigned int position) +{ + std::shared_ptr track; + + if(head >= 2) return track; + long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track; + fseek(_file, file_offset, SEEK_SET); + + std::vector sectors; + for(int sector = 0; sector < sectors_per_track; sector++) + { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = (uint8_t)head; + new_sector.sector = (uint8_t)sector; + + new_sector.data.resize(bytes_per_sector); + fread(&new_sector.data[0], 1, bytes_per_sector, _file); + if(feof(_file)) + break; + + sectors.push_back(std::move(new_sector)); + } + + if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors); + + return track; +} diff --git a/Storage/Disk/Formats/AcornADF.hpp b/Storage/Disk/Formats/AcornADF.hpp new file mode 100644 index 000000000..64aeb54b1 --- /dev/null +++ b/Storage/Disk/Formats/AcornADF.hpp @@ -0,0 +1,48 @@ +// +// AcornADF.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef AcornADF_hpp +#define AcornADF_hpp + +#include "../Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provies a @c Disk containing an ADF disk image — a decoded sector dump of an Acorn ADFS disk. +*/ +class AcornADF: public Disk { + public: + /*! + Construct an @c AcornADF containing content from the file with name @c file_name. + + @throws ErrorCantOpen if this file can't be opened. + @throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image. + */ + AcornADF(const char *file_name); + ~AcornADF(); + + enum { + ErrorCantOpen, + ErrorNotAcornADF, + }; + + // implemented to satisfy @c Disk + unsigned int get_head_position_count(); + unsigned int get_head_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + + private: + FILE *_file; +}; + +} +} + +#endif /* AcornADF_hpp */ diff --git a/Storage/Disk/Formats/D64.cpp b/Storage/Disk/Formats/D64.cpp index 4bba3eb5a..f0397b10b 100644 --- a/Storage/Disk/Formats/D64.cpp +++ b/Storage/Disk/Formats/D64.cpp @@ -53,10 +53,10 @@ unsigned int D64::get_head_position_count() return _number_of_tracks*2; } -std::shared_ptr D64::get_track_at_position(unsigned int position) +std::shared_ptr D64::get_track_at_position(unsigned int head, unsigned int position) { - // every other track is missing - if(position&1) + // every other track is missing, as is any head above 0 + if(position&1 || head) return std::shared_ptr(); // figure out where this track starts on the disk @@ -102,8 +102,8 @@ std::shared_ptr D64::get_track_at_position(unsigned int position) PCMSegment track; size_t track_bytes = 349 * (size_t)sectors_by_zone[zone]; track.number_of_bits = (unsigned int)track_bytes * 8; - uint8_t *data = new uint8_t[track_bytes]; - track.data.reset(data); + track.data.resize(track_bytes); + uint8_t *data = &track.data[0]; memset(data, 0, track_bytes); diff --git a/Storage/Disk/Formats/D64.hpp b/Storage/Disk/Formats/D64.hpp index 93c4d4cb5..7b3b20291 100644 --- a/Storage/Disk/Formats/D64.hpp +++ b/Storage/Disk/Formats/D64.hpp @@ -35,7 +35,7 @@ class D64: public Disk { // implemented to satisfy @c Disk unsigned int get_head_position_count(); - std::shared_ptr get_track_at_position(unsigned int position); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: FILE *_file; diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 546155433..86df3022f 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -54,13 +54,14 @@ unsigned int G64::get_head_position_count() return _number_of_tracks > 84 ? _number_of_tracks : 84; } -std::shared_ptr G64::get_track_at_position(unsigned int position) +std::shared_ptr G64::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr resulting_track; // if there's definitely no track here, return the empty track // (TODO: should be supplying one with an index hole?) if(position >= _number_of_tracks) return resulting_track; + if(head >= 1) return resulting_track; // seek to this track's entry in the track table fseek(_file, (long)((position * 4) + 0xc), SEEK_SET); @@ -84,8 +85,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) track_length |= (uint16_t)fgetc(_file) << 8; // grab the byte contents of this track - std::unique_ptr track_contents(new uint8_t[track_length]); - fread(track_contents.get(), 1, track_length, _file); + std::vector track_contents(track_length); + fread(&track_contents[0], 1, track_length, _file); // seek to this track's entry in the speed zone table fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET); @@ -123,8 +124,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) PCMSegment segment; segment.number_of_bits = number_of_bytes * 8; segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed); - segment.data.reset(new uint8_t[number_of_bytes]); - memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes); + segment.data.resize(number_of_bytes); + memcpy(&segment.data[0], &track_contents[start_byte_in_current_speed], number_of_bytes); segments.push_back(std::move(segment)); current_speed = byte_speed; diff --git a/Storage/Disk/Formats/G64.hpp b/Storage/Disk/Formats/G64.hpp index 82ab9ae97..35db52efb 100644 --- a/Storage/Disk/Formats/G64.hpp +++ b/Storage/Disk/Formats/G64.hpp @@ -37,7 +37,7 @@ class G64: public Disk { // implemented to satisfy @c Disk unsigned int get_head_position_count(); - std::shared_ptr get_track_at_position(unsigned int position); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: FILE *_file; diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp new file mode 100644 index 000000000..333fac821 --- /dev/null +++ b/Storage/Disk/Formats/SSD.cpp @@ -0,0 +1,81 @@ +// +// SSD.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "SSD.hpp" + +#include +#include "../Encodings/MFM.hpp" + +using namespace Storage::Disk; + +SSD::SSD(const char *file_name) : _file(nullptr) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // very loose validation: the file needs to be a multiple of 256 bytes + // and not ungainly large + + if(file_stats.st_size & 255) throw ErrorNotSSD; + if(file_stats.st_size < 512) throw ErrorNotSSD; + if(file_stats.st_size > 800*256) throw ErrorNotSSD; + + _file = fopen(file_name, "rb"); + + if(!_file) throw ErrorCantOpen; + + // this has two heads if the suffix is .dsd, one if it's .ssd + _head_count = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1; + _track_count = (unsigned int)(file_stats.st_size / (256 * 10)); + if(_track_count < 40) _track_count = 40; + else if(_track_count < 80) _track_count = 80; +} + +SSD::~SSD() +{ + if(_file) fclose(_file); +} + +unsigned int SSD::get_head_position_count() +{ + return _track_count; +} + +unsigned int SSD::get_head_count() +{ + return _head_count; +} + +std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned int position) +{ + std::shared_ptr track; + + if(head >= _head_count) return track; + long file_offset = (position * _head_count + head) * 256 * 10; + fseek(_file, file_offset, SEEK_SET); + + std::vector sectors; + for(int sector = 0; sector < 10; sector++) + { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = 0; + new_sector.sector = (uint8_t)sector; + + new_sector.data.resize(256); + fread(&new_sector.data[0], 1, 256, _file); + if(feof(_file)) + break; + + sectors.push_back(std::move(new_sector)); + } + + if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors); + + return track; +} diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp new file mode 100644 index 000000000..543e554ea --- /dev/null +++ b/Storage/Disk/Formats/SSD.hpp @@ -0,0 +1,50 @@ +// +// SSD.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef SSD_hpp +#define SSD_hpp + +#include "../Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provies a @c Disk containing a DSD or SSD disk image — a decoded sector dump of an Acorn DFS disk. +*/ +class SSD: public Disk { + public: + /*! + Construct an @c SSD containing content from the file with name @c file_name. + + @throws ErrorCantOpen if this file can't be opened. + @throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image. + */ + SSD(const char *file_name); + ~SSD(); + + enum { + ErrorCantOpen, + ErrorNotSSD, + }; + + // implemented to satisfy @c Disk + unsigned int get_head_position_count(); + unsigned int get_head_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + + private: + FILE *_file; + unsigned int _head_count; + unsigned int _track_count; +}; + +} +} + +#endif /* SSD_hpp */ diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index a655ab969..509dae780 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -36,7 +36,7 @@ PCMTrack::Event PCMTrack::get_next_event() unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate; unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length; - const uint8_t *segment_data = _segments[_segment_pointer].data.get(); + const uint8_t *segment_data = &_segments[_segment_pointer].data[0]; while(_bit_pointer < _segments[_segment_pointer].number_of_bits) { // for timing simplicity, bits are modelled as happening at the end of their window diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp index 751473ef5..36d464e50 100644 --- a/Storage/Disk/PCMTrack.hpp +++ b/Storage/Disk/PCMTrack.hpp @@ -23,7 +23,7 @@ namespace Disk { struct PCMSegment { Time length_of_a_bit; unsigned int number_of_bits; - std::unique_ptr data; + std::vector data; }; /*!