1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-13 22:32:03 +00:00

Merge branch 'master' into AYFidelity

This commit is contained in:
Thomas Harte 2017-08-11 14:41:08 -04:00
commit e4f04d0977
30 changed files with 784 additions and 190 deletions

View File

@ -31,9 +31,18 @@ class BusHandler {
void perform_bus_cycle(const BusState &) {} void perform_bus_cycle(const BusState &) {}
}; };
enum Personality {
HD6845S, //
UM6845R, //
MC6845, //
AMS40226 //
};
template <class T> class CRTC6845 { template <class T> class CRTC6845 {
public: public:
CRTC6845(T &bus_handler) : bus_handler_(bus_handler) {}
CRTC6845(Personality p, T &bus_handler) :
personality_(p), bus_handler_(bus_handler) {}
void run_for(Cycles cycles) { void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int(); int cyles_remaining = cycles.as_int();
@ -58,22 +67,18 @@ template <class T> class CRTC6845 {
if(hsync_down_counter_) bus_state_.hsync = true; if(hsync_down_counter_) bus_state_.hsync = true;
} }
// update refresh address
if(character_is_visible_) {
bus_state_.refresh_address++;
}
// check for end of visible characters // check for end of visible characters
if(character_counter_ == registers_[1]) { if(character_counter_ == registers_[1]) {
bus_state_.refresh_address++;
character_is_visible_ = false; character_is_visible_ = false;
} else {
// update refresh address
if(character_is_visible_) {
bus_state_.refresh_address++;
}
} }
// check for end-of-line // check for end-of-line
if(is_end_of_line) { if(is_end_of_line) {
character_counter_ = 0;
character_is_visible_ = true;
// check for end of vertical sync // check for end of vertical sync
if(vsync_down_counter_) { if(vsync_down_counter_) {
vsync_down_counter_--; vsync_down_counter_--;
@ -94,11 +99,12 @@ template <class T> class CRTC6845 {
} else { } else {
// advance vertical counter // advance vertical counter
if(bus_state_.row_address == registers_[9]) { if(bus_state_.row_address == registers_[9]) {
line_address_ = bus_state_.refresh_address; if(!character_is_visible_)
line_address_ = bus_state_.refresh_address;
bus_state_.row_address = 0; bus_state_.row_address = 0;
bool is_at_end_of_frame = line_counter_ == registers_[4]; bool is_at_end_of_frame = line_counter_ == registers_[4];
line_counter_++; line_counter_ = (line_counter_ + 1) & 0x7f;
// check for end of visible lines // check for end of visible lines
if(line_counter_ == registers_[6]) { if(line_counter_ == registers_[6]) {
@ -125,19 +131,23 @@ template <class T> class CRTC6845 {
line_counter_ = 0; line_counter_ = 0;
} }
} else { } else {
bus_state_.row_address++; bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
bus_state_.refresh_address = line_address_;
} }
bus_state_.refresh_address = line_address_;
} }
character_counter_ = 0;
character_is_visible_ = true;
} }
bus_state_.display_enable = character_is_visible_ && line_is_visible_; bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_state_.refresh_address &= 0x3fff;
bus_handler_.perform_bus_cycle(bus_state_); bus_handler_.perform_bus_cycle(bus_state_);
} }
} }
void select_register(uint8_t r) { void select_register(uint8_t r) {
selected_register_ = (int)r & 15; selected_register_ = r;
} }
uint8_t get_status() { uint8_t get_status() {
@ -145,18 +155,31 @@ template <class T> class CRTC6845 {
} }
uint8_t get_register() { uint8_t get_register() {
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_]; return registers_[selected_register_];
} }
void set_register(uint8_t value) { void set_register(uint8_t value) {
registers_[selected_register_] = value; static uint8_t masks[] = {
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
};
if(selected_register_ < 16)
registers_[selected_register_] = value & masks[selected_register_];
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
} }
private: private:
Personality personality_;
T &bus_handler_; T &bus_handler_;
BusState bus_state_; BusState bus_state_;
uint8_t registers_[16]; uint8_t registers_[18];
int selected_register_; int selected_register_;
uint8_t character_counter_; uint8_t character_counter_;

View File

@ -13,10 +13,10 @@
using namespace Intel; using namespace Intel;
namespace { namespace {
const uint8_t StatusRQM = 0x80; // Set: ready to send or receive from processor. const uint8_t StatusRequest = 0x80; // Set: ready to send or receive from processor.
const uint8_t StatusDIO = 0x40; // Set: data is expected to be taken from the 8272 by the processor. const uint8_t StatusDirection = 0x40; // Set: data is expected to be taken from the 8272 by the processor.
const uint8_t StatusNDM = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled. const uint8_t StatusNonDMAExecuting = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled.
const uint8_t StatusCB = 0x10; // Set: the FDC is busy. const uint8_t StatusBusy = 0x10; // Set: the FDC is busy.
//const uint8_t StatusD3B = 0x08; // Set: drive 3 is seeking. //const uint8_t StatusD3B = 0x08; // Set: drive 3 is seeking.
//const uint8_t StatusD2B = 0x04; // Set: drive 2 is seeking. //const uint8_t StatusD2B = 0x04; // Set: drive 2 is seeking.
//const uint8_t StatusD1B = 0x02; // Set: drive 1 is seeking. //const uint8_t StatusD1B = 0x02; // Set: drive 1 is seeking.
@ -25,7 +25,7 @@ const uint8_t StatusCB = 0x10; // Set: the FDC is busy.
i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
main_status_(StatusRQM), main_status_(0),
interesting_event_mask_((int)Event8272::CommandByte), interesting_event_mask_((int)Event8272::CommandByte),
resume_point_(0), resume_point_(0),
delay_time_(0), delay_time_(0),
@ -76,7 +76,7 @@ void i8272::set_register(int address, uint8_t value) {
if(!address) return; if(!address) return;
// if not ready for commands, do nothing // if not ready for commands, do nothing
if(!(main_status_ & StatusRQM)) return; if(!(main_status_ & StatusRequest)) return;
// accumulate latest byte in the command byte sequence // accumulate latest byte in the command byte sequence
command_.push_back(value); command_.push_back(value);
@ -135,7 +135,7 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
set_data_mode(Scanning); set_data_mode(Scanning);
#define SET_DRIVE_HEAD_MFM() \ #define SET_DRIVE_HEAD_MFM() \
if(!dma_mode_) main_status_ |= StatusNDM; \ if(!dma_mode_) main_status_ |= StatusNonDMAExecuting; \
set_drive(drives_[command_[1]&3].drive); \ set_drive(drives_[command_[1]&3].drive); \
set_is_double_density(command_[0] & 0x40); \ set_is_double_density(command_[0] & 0x40); \
invalidate_track(); invalidate_track();
@ -150,94 +150,94 @@ void i8272::posit_event(int event_type) {
// into wait_for_complete_command_sequence. // into wait_for_complete_command_sequence.
wait_for_command: wait_for_command:
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
main_status_ &= ~(StatusCB | StatusNDM); main_status_ &= ~(StatusBusy | StatusNonDMAExecuting);
command_.clear(); command_.clear();
// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes // Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
// until it has a quantity that make up an entire command, then resets the data request bit and // until it has a quantity that make up an entire command, then resets the data request bit and
// branches to that command. // branches to that command.
wait_for_complete_command_sequence: wait_for_complete_command_sequence:
main_status_ |= StatusRQM; main_status_ |= StatusRequest;
WAIT_FOR_EVENT(Event8272::CommandByte) WAIT_FOR_EVENT(Event8272::CommandByte)
main_status_ |= StatusCB; main_status_ |= StatusBusy;
switch(command_[0] & 0x1f) { switch(command_[0] & 0x1f) {
case 0x06: // read data case 0x06: // read data
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto read_data; goto read_data;
case 0x0b: // read deleted data case 0x0b: // read deleted data
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto read_deleted_data; goto read_deleted_data;
case 0x05: // write data case 0x05: // write data
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto write_data; goto write_data;
case 0x09: // write deleted data case 0x09: // write deleted data
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto write_deleted_data; goto write_deleted_data;
case 0x02: // read track case 0x02: // read track
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto read_track; goto read_track;
case 0x0a: // read ID case 0x0a: // read ID
if(command_.size() < 2) goto wait_for_complete_command_sequence; if(command_.size() < 2) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto read_id; goto read_id;
case 0x0d: // format track case 0x0d: // format track
if(command_.size() < 6) goto wait_for_complete_command_sequence; if(command_.size() < 6) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto format_track; goto format_track;
case 0x11: // scan low case 0x11: // scan low
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto scan_low; goto scan_low;
case 0x19: // scan low or equal case 0x19: // scan low or equal
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto scan_low_or_equal; goto scan_low_or_equal;
case 0x1d: // scan high or equal case 0x1d: // scan high or equal
if(command_.size() < 9) goto wait_for_complete_command_sequence; if(command_.size() < 9) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto scan_high_or_equal; goto scan_high_or_equal;
case 0x07: // recalibrate case 0x07: // recalibrate
if(command_.size() < 2) goto wait_for_complete_command_sequence; if(command_.size() < 2) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto recalibrate; goto recalibrate;
case 0x08: // sense interrupt status case 0x08: // sense interrupt status
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto sense_interrupt_status; goto sense_interrupt_status;
case 0x03: // specify case 0x03: // specify
if(command_.size() < 3) goto wait_for_complete_command_sequence; if(command_.size() < 3) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto specify; goto specify;
case 0x04: // sense drive status case 0x04: // sense drive status
if(command_.size() < 2) goto wait_for_complete_command_sequence; if(command_.size() < 2) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto sense_drive_status; goto sense_drive_status;
case 0x0f: // seek case 0x0f: // seek
if(command_.size() < 3) goto wait_for_complete_command_sequence; if(command_.size() < 3) goto wait_for_complete_command_sequence;
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto seek; goto seek;
default: // invalid default: // invalid
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
goto invalid; goto invalid;
} }
@ -253,6 +253,8 @@ void i8272::posit_event(int event_type) {
sector_ = command_[4]; sector_ = command_[4];
size_ = command_[5]; size_ = command_[5];
read_next_data:
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until // Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the // the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
// values in the internal registers. // values in the internal registers.
@ -277,15 +279,21 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event::Token); WAIT_FOR_EVENT(Event::Token);
result_stack_.push_back(get_latest_token().byte_value); result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++; distance_into_section_++;
main_status_ |= StatusRQM | StatusDIO; main_status_ |= StatusRequest | StatusDirection;
WAIT_FOR_EVENT(Event8272::ResultEmpty); WAIT_FOR_EVENT(Event8272::ResultEmpty);
main_status_ &= ~StatusRQM; main_status_ &= ~StatusRequest;
if(distance_into_section_ < (128 << size_)) goto get_byte; if(distance_into_section_ < (128 << size_)) goto get_byte;
// read CRC, without transferring it // read CRC, without transferring it
WAIT_FOR_EVENT(Event::Token); WAIT_FOR_EVENT(Event::Token);
WAIT_FOR_EVENT(Event::Token); WAIT_FOR_EVENT(Event::Token);
// check whether that's it
if(sector_ != command_[6]) {
sector_++;
goto read_next_data;
}
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N // For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
goto post_st012chrn; goto post_st012chrn;
@ -416,8 +424,17 @@ void i8272::posit_event(int event_type) {
goto wait_for_command; goto wait_for_command;
sense_drive_status: sense_drive_status:
printf("Sense drive status unimplemented!!\n"); {
goto wait_for_command; int drive = command_[1] & 3;
result_stack_.push_back(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
(drives_[drive].drive->has_disk() ? 0x20 : 0x00) | // ready, approximately (TODO)
0x40 // write protected
);
}
goto post_result;
// Performs any invalid command. // Performs any invalid command.
invalid: invalid:
@ -442,15 +459,15 @@ void i8272::posit_event(int event_type) {
// last thing in it will be returned first. // last thing in it will be returned first.
post_result: post_result:
// Set ready to send data to the processor, no longer in non-DMA execution phase. // Set ready to send data to the processor, no longer in non-DMA execution phase.
main_status_ |= StatusRQM | StatusDIO; main_status_ |= StatusRequest | StatusDirection;
main_status_ &= ~StatusNDM; main_status_ &= ~StatusNonDMAExecuting;
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait // The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
// until the processor has read all result bytes. // until the processor has read all result bytes.
WAIT_FOR_EVENT(Event8272::ResultEmpty); WAIT_FOR_EVENT(Event8272::ResultEmpty);
// Reset data direction and end the command. // Reset data direction and end the command.
main_status_ &= ~StatusDIO; main_status_ &= ~StatusDirection;
goto wait_for_command; goto wait_for_command;
END_SECTION() END_SECTION()

View File

@ -8,6 +8,8 @@
#include "AmstradCPC.hpp" #include "AmstradCPC.hpp"
#include "CharacterMapper.hpp"
#include "../../Processors/Z80/Z80.hpp" #include "../../Processors/Z80/Z80.hpp"
#include "../../Components/6845/CRTC6845.hpp" #include "../../Components/6845/CRTC6845.hpp"
@ -15,6 +17,9 @@
#include "../../Components/8272/i8272.hpp" #include "../../Components/8272/i8272.hpp"
#include "../../Components/AY38910/AY38910.hpp" #include "../../Components/AY38910/AY38910.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
namespace AmstradCPC { namespace AmstradCPC {
@ -291,6 +296,7 @@ class CRTCBusHandler {
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" "return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}"); "}");
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
crt_->set_output_device(Outputs::CRT::Monitor);
} }
/// Destructs the CRT. /// Destructs the CRT.
@ -522,19 +528,23 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
*/ */
class ConcreteMachine: class ConcreteMachine:
public Utility::TypeRecipient,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public Machine { public Machine {
public: public:
ConcreteMachine() : ConcreteMachine() :
z80_(*this), z80_(*this),
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses
crtc_(crtc_bus_handler_), crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
crtc_bus_handler_(ram_, interrupt_timer_), crtc_bus_handler_(ram_, interrupt_timer_),
i8255_(i8255_port_handler_), i8255_(i8255_port_handler_),
i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_), i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_),
tape_player_(8000000) { tape_player_(8000000) {
// primary clock is 4Mhz // primary clock is 4Mhz
set_clock_rate(4000000); set_clock_rate(4000000);
// ensure memory starts in a random state
Memory::Fuzz(ram_, sizeof(ram_));
} }
/// The entry point for performing a partial Z80 machine cycle. /// The entry point for performing a partial Z80 machine cycle.
@ -556,12 +566,15 @@ class ConcreteMachine:
// run_for as HalfCycles // run_for as HalfCycles
tape_player_.run_for(cycle.length.as_int()); tape_player_.run_for(cycle.length.as_int());
// Pump the AY. // Pump the AY
ay_.run_for(cycle.length); ay_.run_for(cycle.length);
// Clock the FDC, if connected, using a lazy scale by two // Clock the FDC, if connected, using a lazy scale by two
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int())); if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
// Update typing activity
if(typer_) typer_->run_for(cycle.length);
// Stop now if no action is strictly required. // Stop now if no action is strictly required.
if(!cycle.is_terminal()) return HalfCycles(0); if(!cycle.is_terminal()) return HalfCycles(0);
@ -579,48 +592,7 @@ class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Output: case CPU::Z80::PartialMachineCycle::Output:
// Check for a gate array access. // Check for a gate array access.
if((address & 0xc000) == 0x4000) { if((address & 0xc000) == 0x4000) {
switch(*cycle.value >> 6) { write_to_gate_array(*cycle.value);
case 0: crtc_bus_handler_.select_pen(*cycle.value & 0x1f); break;
case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break;
case 2:
// Perform ROM paging.
read_pointers_[0] = (*cycle.value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
upper_rom_is_paged_ = !(*cycle.value & 8);
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
// Reset the interrupt timer if requested.
if(*cycle.value & 0x10) interrupt_timer_.reset_count();
// Post the next mode.
crtc_bus_handler_.set_next_mode(*cycle.value & 3);
break;
case 3:
// Perform RAM paging, if 128kb is permitted.
if(has_128k_) {
bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0];
bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3];
#define RAM_BANK(x) &ram_[x * 16384]
#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d);
switch(*cycle.value & 7) {
case 0: RAM_CONFIG(0, 1, 2, 3); break;
case 1: RAM_CONFIG(0, 1, 2, 7); break;
case 2: RAM_CONFIG(4, 5, 6, 7); break;
case 3: RAM_CONFIG(0, 3, 2, 7); break;
case 4: RAM_CONFIG(0, 4, 2, 3); break;
case 5: RAM_CONFIG(0, 5, 2, 3); break;
case 6: RAM_CONFIG(0, 6, 2, 3); break;
case 7: RAM_CONFIG(0, 7, 2, 3); break;
}
#undef RAM_CONFIG
#undef RAM_BANK
if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0];
read_pointers_[1] = write_pointers_[1];
read_pointers_[2] = write_pointers_[2];
if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3];
}
break;
}
} }
// Check for an upper ROM selection // Check for an upper ROM selection
@ -634,7 +606,7 @@ class ConcreteMachine:
switch((address >> 8) & 3) { switch((address >> 8) & 3) {
case 0: crtc_.select_register(*cycle.value); break; case 0: crtc_.select_register(*cycle.value); break;
case 1: crtc_.set_register(*cycle.value); break; case 1: crtc_.set_register(*cycle.value); break;
case 2: case 3: printf("Illegal CRTC write?\n"); break; default: break;
} }
} }
@ -657,23 +629,31 @@ class ConcreteMachine:
// Default to nothing answering // Default to nothing answering
*cycle.value = 0xff; *cycle.value = 0xff;
// Check for a CRTC access
if(!(address & 0x4000)) {
switch((address >> 8) & 3) {
case 0: case 1: printf("Illegal CRTC read?\n"); break;
case 2: *cycle.value = crtc_.get_status(); break;
case 3: *cycle.value = crtc_.get_register(); break;
}
}
// Check for a PIO access // Check for a PIO access
if(!(address & 0x800)) { if(!(address & 0x800)) {
*cycle.value = i8255_.get_register((address >> 8) & 3); *cycle.value &= i8255_.get_register((address >> 8) & 3);
} }
// Check for an FDC access // Check for an FDC access
if(has_fdc_ && (address & 0x580) == 0x100) { if(has_fdc_ && (address & 0x580) == 0x100) {
*cycle.value = fdc_.get_register(address & 1); *cycle.value &= fdc_.get_register(address & 1);
}
// Check for a CRTC access; the below is not a typo — the CRTC can be selected
// for writing via an input, and will sample whatever happens to be available
if(!(address & 0x4000)) {
switch((address >> 8) & 3) {
case 0: crtc_.select_register(*cycle.value); break;
case 1: crtc_.set_register(*cycle.value); break;
case 2: *cycle.value &= crtc_.get_status(); break;
case 3: *cycle.value &= crtc_.get_register(); break;
}
}
// As with the CRTC, the gate array will sample the bus if the address decoding
// implies that it should, unaware of data direction
if((address & 0xc000) == 0x4000) {
write_to_gate_array(*cycle.value);
} }
break; break;
@ -774,6 +754,11 @@ class ConcreteMachine:
c++; c++;
if(c == 4) break; if(c == 4) break;
} }
// Type whatever is required.
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
} }
// See header; provides the system ROMs. // See header; provides the system ROMs.
@ -781,6 +766,21 @@ class ConcreteMachine:
roms_[(int)type] = data; roms_[(int)type] = data;
} }
#pragma mark - Keyboard
void set_typer_for_string(const char *string) {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
HalfCycles get_typer_delay() {
return Cycles(4000000); // Wait 1 second before typing.
}
HalfCycles get_typer_frequency() {
return Cycles(80000); // Type one character per frame.
}
// See header; sets a key as either pressed or released. // See header; sets a key as either pressed or released.
void set_key_state(uint16_t key, bool isPressed) { void set_key_state(uint16_t key, bool isPressed) {
int line = key >> 4; int line = key >> 4;
@ -794,6 +794,51 @@ class ConcreteMachine:
} }
private: private:
inline void write_to_gate_array(uint8_t value) {
switch(value >> 6) {
case 0: crtc_bus_handler_.select_pen(value & 0x1f); break;
case 1: crtc_bus_handler_.set_colour(value & 0x1f); break;
case 2:
// Perform ROM paging.
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
upper_rom_is_paged_ = !(value & 8);
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
// Reset the interrupt timer if requested.
if(value & 0x10) interrupt_timer_.reset_count();
// Post the next mode.
crtc_bus_handler_.set_next_mode(value & 3);
break;
case 3:
// Perform RAM paging, if 128kb is permitted.
if(has_128k_) {
bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0];
bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3];
#define RAM_BANK(x) &ram_[x * 16384]
#define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d);
switch(value & 7) {
case 0: RAM_CONFIG(0, 1, 2, 3); break;
case 1: RAM_CONFIG(0, 1, 2, 7); break;
case 2: RAM_CONFIG(4, 5, 6, 7); break;
case 3: RAM_CONFIG(0, 3, 2, 7); break;
case 4: RAM_CONFIG(0, 4, 2, 3); break;
case 5: RAM_CONFIG(0, 5, 2, 3); break;
case 6: RAM_CONFIG(0, 6, 2, 3); break;
case 7: RAM_CONFIG(0, 7, 2, 3); break;
}
#undef RAM_CONFIG
#undef RAM_BANK
if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0];
read_pointers_[1] = write_pointers_[1];
read_pointers_[2] = write_pointers_[2];
if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3];
}
break;
}
}
CPU::Z80::Processor<ConcreteMachine> z80_; CPU::Z80::Processor<ConcreteMachine> z80_;
CRTCBusHandler crtc_bus_handler_; CRTCBusHandler crtc_bus_handler_;
@ -836,3 +881,5 @@ using namespace AmstradCPC;
Machine *Machine::AmstradCPC() { Machine *Machine::AmstradCPC() {
return new AmstradCPC::ConcreteMachine; return new AmstradCPC::ConcreteMachine;
} }
Machine::~Machine() {}

View File

@ -52,6 +52,8 @@ class Machine:
public ConfigurationTarget::Machine, public ConfigurationTarget::Machine,
public KeyboardMachine::Machine { public KeyboardMachine::Machine {
public: public:
virtual ~Machine();
/// Creates an returns an Amstrad CPC on the heap. /// Creates an returns an Amstrad CPC on the heap.
static Machine *AmstradCPC(); static Machine *AmstradCPC();

View File

@ -0,0 +1,89 @@
//
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 11/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "AmstradCPC.hpp"
using namespace AmstradCPC;
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
/* ACK */ X, /* BEL */ X,
/* BS */ KEYS(KeyDelete), /* HT */ X,
/* LF */ KEYS(KeyReturn), /* VT */ X,
/* FF */ X, /* CR */ X,
/* SO */ X, /* SI */ X,
/* DLE */ X, /* DC1 */ X,
/* DC2 */ X, /* DC3 */ X,
/* DC4 */ X, /* NAK */ X,
/* SYN */ X, /* ETB */ X,
/* CAN */ X, /* EM */ X,
/* SUB */ X, /* ESC */ X,
/* FS */ X, /* GS */ X,
/* RS */ X, /* US */ X,
/* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
/* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
/* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
/* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
/* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
/* * */ SHIFT(KeyColon), /* + */ SHIFT(KeySemicolon),
/* , */ KEYS(KeyComma), /* - */ KEYS(KeyMinus),
/* . */ KEYS(KeyFullStop), /* / */ KEYS(KeyForwardSlash),
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
/* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
/* < */ SHIFT(KeyComma), /* = */ SHIFT(KeyMinus),
/* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeyForwardSlash),
/* @ */ SHIFT(KeyAt), /* A */ SHIFT(KeyA),
/* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC),
/* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE),
/* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG),
/* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI),
/* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK),
/* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM),
/* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO),
/* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ),
/* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS),
/* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU),
/* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW),
/* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY),
/* Z */ SHIFT(KeyZ), /* [ */ KEYS(KeyLeftSquareBracket),
/* \ */ KEYS(KeyBackSlash), /* ] */ KEYS(KeyRightSquareBracket),
/* ^ */ SHIFT(KeyCaret), /* _ */ SHIFT(Key0),
/* ` */ X, /* a */ KEYS(KeyA),
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
/* z */ KEYS(KeyZ), /* { */ X,
/* | */ SHIFT(KeyAt), /* } */ X,
/* ~ */ X
};
#undef KEYS
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
}

View File

@ -0,0 +1,23 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_AmstradCPC_CharacterMapper_hpp
#define Machines_AmstradCPC_CharacterMapper_hpp
#include "../Typer.hpp"
namespace AmstradCPC {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
#endif /* CharacterMapper_hpp */

View File

@ -51,7 +51,7 @@ bool Typer::try_type_next_character() {
if(!phase_) delegate_->clear_all_keys(); if(!phase_) delegate_->clear_all_keys();
else { else {
delegate_->set_key_state(sequence[phase_ - 1], true); delegate_->set_key_state(sequence[phase_ - 1], true);
return sequence[phase_] == CharacterMapper::EndSequence; return sequence[phase_] != CharacterMapper::EndSequence;
} }
return true; return true;

View File

@ -6,8 +6,8 @@
// Copyright © 2017 Thomas Harte. All rights reserved. // Copyright © 2017 Thomas Harte. All rights reserved.
// //
#ifndef CharacterMapper_hpp #ifndef Machines_ZX8081_CharacterMapper_hpp
#define CharacterMapper_hpp #define Machines_ZX8081_CharacterMapper_hpp
#include "../Typer.hpp" #include "../Typer.hpp"

View File

@ -384,3 +384,5 @@ using namespace ZX8081;
Machine *Machine::ZX8081() { Machine *Machine::ZX8081() {
return new ZX8081::ConcreteMachine; return new ZX8081::ConcreteMachine;
} }
Machine::~Machine() {}

View File

@ -39,6 +39,7 @@ class Machine:
public KeyboardMachine::Machine { public KeyboardMachine::Machine {
public: public:
static Machine *ZX8081(); static Machine *ZX8081();
virtual ~Machine();
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0; virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;

View File

@ -58,6 +58,7 @@
4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; }; 4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; };
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; }; 4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; };
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; }; 4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; }; 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; }; 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; };
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
@ -119,6 +120,7 @@
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; };
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; };
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; }; 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; };
4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; };
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; }; 4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; }; 4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; 4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
@ -562,6 +564,8 @@
4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; }; 4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; };
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; }; 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; }; 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; };
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; }; 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
@ -667,6 +671,8 @@
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; }; 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; };
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; }; 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; }; 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; };
4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = AmstradCPC/CharacterMapper.cpp; sourceTree = "<group>"; };
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = "<group>"; };
4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; }; 4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
@ -1294,6 +1300,8 @@
children = ( children = (
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */, 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */,
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */, 4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */,
4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */,
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */,
); );
name = AmstradCPC; name = AmstradCPC;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1330,6 +1338,15 @@
path = Bridges; path = Bridges;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4B3FE75F1F3CF6BA00448EE4 /* Parsers */ = {
isa = PBXGroup;
children = (
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */,
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */,
);
name = Parsers;
sourceTree = "<group>";
};
4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1446,12 +1463,12 @@
4B69FB3A1C4D908A00B5F0AA /* Tape */ = { 4B69FB3A1C4D908A00B5F0AA /* Tape */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */,
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */,
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
4B69FB411C4D941400B5F0AA /* Formats */, 4B69FB411C4D941400B5F0AA /* Formats */,
4B8805F11DCFC9A2003085B1 /* Parsers */, 4B8805F11DCFC9A2003085B1 /* Parsers */,
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */,
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */,
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
); );
path = Tape; path = Tape;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1545,6 +1562,7 @@
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */, 4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */, 4BAB62B21D327F7E00DF5BA0 /* Formats */,
4B3FE75F1F3CF6BA00448EE4 /* Parsers */,
); );
path = Disk; path = Disk;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2700,6 +2718,7 @@
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
@ -2710,6 +2729,7 @@
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */,
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */,
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,

View File

@ -116,6 +116,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (void)setupOutputWithAspectRatio:(float)aspectRatio { - (void)setupOutputWithAspectRatio:(float)aspectRatio {
self.machine->setup_output(aspectRatio); self.machine->setup_output(aspectRatio);
// Since OS X v10.6, Macs have had a gamma of 2.2.
self.machine->get_crt()->set_output_gamma(2.2f);
} }
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty {

View File

@ -56,10 +56,12 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display
switch(displayType) { switch(displayType) {
case DisplayType::PAL50: case DisplayType::PAL50:
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync
set_input_gamma(2.8f);
break; break;
case DisplayType::NTSC60: case DisplayType::NTSC60:
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync
set_input_gamma(2.2f);
break; break;
} }
} }
@ -72,6 +74,21 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_
} }
} }
void CRT::set_input_gamma(float gamma) {
input_gamma_ = gamma;
update_gamma();
}
void CRT::set_output_gamma(float gamma) {
output_gamma_ = gamma;
update_gamma();
}
void CRT::update_gamma() {
float gamma_ratio = input_gamma_ / output_gamma_;
openGL_output_builder_.set_gamma(gamma_ratio);
}
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
is_receiving_sync_(false), is_receiving_sync_(false),
common_output_divisor_(common_output_divisor), common_output_divisor_(common_output_divisor),

View File

@ -97,6 +97,9 @@ class CRT {
unsigned int cycles_per_line_; unsigned int cycles_per_line_;
float input_gamma_, output_gamma_;
void update_gamma();
public: public:
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
The requested number of buffers, each with the requested number of bytes per pixel, The requested number of buffers, each with the requested number of bytes per pixel,
@ -238,6 +241,12 @@ class CRT {
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty); openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
} }
/*! Sets the gamma exponent for the simulated screen. */
void set_input_gamma(float gamma);
/*! Sets the gamma exponent for the real, tangible screen on which content will be drawn. */
void set_output_gamma(float gamma);
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
the previous. the previous.

View File

@ -89,6 +89,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
set_timing_uniforms(); set_timing_uniforms();
set_colour_space_uniforms(); set_colour_space_uniforms();
set_gamma();
} }
if(fence_ != nullptr) { if(fence_ != nullptr) {
@ -375,6 +376,10 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() {
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
} }
void OpenGLOutputBuilder::set_gamma() {
if(output_shader_program_) output_shader_program_->set_gamma_ratio(gamma_);
}
float OpenGLOutputBuilder::get_composite_output_width() const { float OpenGLOutputBuilder::get_composite_output_width() const {
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth); return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
} }

View File

@ -34,6 +34,7 @@ class OpenGLOutputBuilder {
unsigned int colour_cycle_numerator_; unsigned int colour_cycle_numerator_;
unsigned int colour_cycle_denominator_; unsigned int colour_cycle_denominator_;
OutputDevice output_device_; OutputDevice output_device_;
float gamma_;
// timing information to allow reasoning about input information // timing information to allow reasoning about input information
unsigned int input_frequency_; unsigned int input_frequency_;
@ -89,6 +90,7 @@ class OpenGLOutputBuilder {
void set_timing_uniforms(); void set_timing_uniforms();
void set_colour_space_uniforms(); void set_colour_space_uniforms();
void set_gamma();
void establish_OpenGL_state(); void establish_OpenGL_state();
void reset_all_OpenGL_state(); void reset_all_OpenGL_state();
@ -118,6 +120,11 @@ class OpenGLOutputBuilder {
visible_area_ = visible_area; visible_area_ = visible_area;
} }
inline void set_gamma(float gamma) {
gamma_ = gamma;
set_gamma();
}
inline std::unique_lock<std::mutex> get_output_lock() { inline std::unique_lock<std::mutex> get_output_lock() {
return std::unique_lock<std::mutex>(output_mutex_); return std::unique_lock<std::mutex>(output_mutex_);
} }

View File

@ -74,12 +74,13 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
"out vec4 fragColour;" "out vec4 fragColour;"
"uniform %s texID;" "uniform %s texID;"
"uniform float gamma;"
"\n%s\n" "\n%s\n"
"void main(void)" "void main(void)"
"{" "{"
"fragColour = vec4(%s, 0.5);"//*cos(lateralVarying) "fragColour = vec4(pow(%s, vec3(gamma)), 0.5);"//*cos(lateralVarying)
"}", "}",
sampler_type, fragment_methods, colour_expression); sampler_type, fragment_methods, colour_expression);
@ -116,6 +117,10 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider); set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
} }
void OutputShader::set_gamma_ratio(float ratio) {
set_uniform("gamma", ratio);
}
void OutputShader::set_input_width_scaler(float input_scaler) { void OutputShader::set_input_width_scaler(float input_scaler) {
set_uniform("inputScaler", input_scaler); set_uniform("inputScaler", input_scaler);
} }

View File

@ -63,6 +63,8 @@ public:
*/ */
void set_origin_is_double_height(bool is_double_height); void set_origin_is_double_height(bool is_double_height);
void set_gamma_ratio(float ratio);
/*! /*!
Sets the proportion of the input area that should be considered the whole width 1.0 means use all available Sets the proportion of the input area that should be considered the whole width 1.0 means use all available
space, 0.5 means use half, etc. space, 0.5 means use half, etc.

View File

@ -9,9 +9,10 @@ So its aims are:
It currently contains emulations of the: It currently contains emulations of the:
* Acorn Electron; * Acorn Electron;
* Amstrad CPC;
* Atari 2600; * Atari 2600;
* Oric 1/Atmos; * Commodore Vic-20 (and Commodore 1540/1);
* Commodore Vic-20 (and Commodore 1540/1); and * Oric 1/Atmos; and
* Sinclair ZX80/81. * Sinclair ZX80/81.
## Single-click Loading ## Single-click Loading

View File

@ -19,8 +19,8 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
std::unique_ptr<Catalogue> catalogue(new Catalogue); std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(false, disk); Storage::Encodings::MFM::Parser parser(false, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0); std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0, 0);
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 1); std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr; if(!names || !details) return nullptr;
if(names->data.size() != 256 || details->data.size() != 256) return nullptr; if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
@ -61,7 +61,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
uint8_t track = (uint8_t)(start_sector / 10); uint8_t track = (uint8_t)(start_sector / 10);
start_sector++; start_sector++;
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(track, sector); std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(0, track, sector);
if(!next_sector) break; if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l); long length_from_sector = std::min(data_length, 256l);
@ -77,13 +77,13 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
std::unique_ptr<Catalogue> catalogue(new Catalogue); std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(true, disk); Storage::Encodings::MFM::Parser parser(true, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1); std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 0, 1);
if(!free_space_map_second_half) return nullptr; if(!free_space_map_second_half) return nullptr;
std::vector<uint8_t> root_directory; std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256); root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) { for(uint8_t c = 2; c < 7; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c); std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr; if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
} }

View File

@ -7,6 +7,50 @@
// //
#include "StaticAnalyser.hpp" #include "StaticAnalyser.hpp"
#include "../../Storage/Disk/Parsers/CPM.hpp"
static void InspectDataCatalogue(
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &data_catalogue,
StaticAnalyser::Target &target) {
// If there's just one file, run that.
if(data_catalogue->files.size() == 1) {
target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n";
return;
}
// If only one file is [potentially] BASIC, run that one; otherwise if only one has no suffix,
// pick that one.
int basic_files = 0;
int nonsuffixed_files = 0;
size_t last_basic_file = 0;
size_t last_nonsuffixed_file = 0;
for(size_t c = 0; c < data_catalogue->files.size(); c++) {
// Check for whether this is [potentially] BASIC.
if(!((data_catalogue->files[c].data[18] >> 1) & 7)) {
basic_files++;
last_basic_file = c;
}
// Check suffix for emptiness.
if(data_catalogue->files[c].type == " ") {
nonsuffixed_files++;
last_nonsuffixed_file = c;
}
}
if(basic_files == 1 || nonsuffixed_files == 1) {
size_t selected_file = (basic_files == 1) ? last_basic_file : last_nonsuffixed_file;
target.loadingCommand = "run\"" + data_catalogue->files[selected_file].name + "\n";
return;
}
// Desperation.
target.loadingCommand = "cat\n";
}
static void InspectSystemCatalogue(
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &data_catalogue,
StaticAnalyser::Target &target) {
}
void StaticAnalyser::AmstradCPC::AddTargets( void StaticAnalyser::AmstradCPC::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks, const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
@ -20,7 +64,42 @@ void StaticAnalyser::AmstradCPC::AddTargets(
target.tapes = tapes; target.tapes = tapes;
target.cartridges = cartridges; target.cartridges = cartridges;
target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC6128; target.amstradcpc.model = AmstradCPCModel::CPC6128;
if(!target.tapes.empty()) {
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target.loadingCommand = "|tape\nrun\"\n1234567890";
}
if(!target.disks.empty()) {
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
data_format.block_size = 1024;
data_format.first_sector = 0xc1;
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), data_format);
if(data_catalogue) {
InspectDataCatalogue(data_catalogue, target);
} else {
Storage::Disk::CPM::ParameterBlock system_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
data_format.block_size = 1024;
data_format.first_sector = 0x41;
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 2;
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), system_format);
if(system_catalogue) {
InspectSystemCatalogue(data_catalogue, target);
}
}
}
destination.push_back(target); destination.push_back(target);
} }

View File

@ -46,6 +46,11 @@ bool Drive::get_is_read_only() {
return false; return false;
} }
bool Drive::get_is_ready() {
// TODO: a real test for this.
return disk_ != nullptr;
}
std::shared_ptr<Track> Drive::get_track() { std::shared_ptr<Track> Drive::get_track() {
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
if(track_) return track_; if(track_) return track_;

View File

@ -65,6 +65,11 @@ class Drive {
*/ */
void set_track(const std::shared_ptr<Track> &track); void set_track(const std::shared_ptr<Track> &track);
/*!
@returns @c true if the drive is ready; @c false otherwise.
*/
bool get_is_ready();
private: private:
std::shared_ptr<Track> track_; std::shared_ptr<Track> track_;
std::shared_ptr<Disk> disk_; std::shared_ptr<Disk> disk_;

View File

@ -114,22 +114,12 @@ class FMEncoder: public Encoder {
} }
}; };
static uint8_t logarithmic_size_for_size(size_t size) {
switch(size) {
default: return 0;
case 256: return 1;
case 512: return 2;
case 1024: return 3;
case 2048: return 4;
case 4196: return 5;
}
}
template<class T> std::shared_ptr<Storage::Disk::Track> template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors( GetTrackWithSectors(
const std::vector<Sector> &sectors, const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes, size_t post_address_mark_bytes, size_t pre_address_mark_bytes,
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
size_t pre_data_mark_bytes, size_t post_data_bytes, size_t pre_data_mark_bytes, size_t post_data_bytes,
size_t inter_sector_gap, size_t inter_sector_gap,
size_t expected_track_bytes) { size_t expected_track_bytes) {
@ -153,21 +143,30 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
shifter.add_byte(sector.track); shifter.add_byte(sector.track);
shifter.add_byte(sector.side); shifter.add_byte(sector.side);
shifter.add_byte(sector.sector); shifter.add_byte(sector.sector);
uint8_t size = logarithmic_size_for_size(sector.data.size()); shifter.add_byte(sector.size);
shifter.add_byte(size); shifter.add_crc(sector.has_header_crc_error);
shifter.add_crc();
// gap // gap
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data // data, if attached
shifter.add_data_address_mark(); if(!sector.data.empty()) {
for(size_t c = 0; c < sector.data.size(); c++) if(sector.is_deleted)
{ shifter.add_deleted_data_address_mark();
shifter.add_byte(sector.data[c]); else
shifter.add_data_address_mark();
size_t c = 0;
size_t declared_length = (size_t)(128 << sector.size);
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
shifter.add_byte(sector.data[c]);
}
for(; c < declared_length; c++) {
shifter.add_byte(0x00);
}
shifter.add_crc(sector.has_data_crc_error);
} }
shifter.add_crc();
// gap // gap
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00);
@ -189,28 +188,32 @@ void Encoder::output_short(uint16_t value) {
target_.push_back(value & 0xff); target_.push_back(value & 0xff);
} }
void Encoder::add_crc() { void Encoder::add_crc(bool incorrectly) {
uint16_t crc_value = crc_generator_.get_value(); uint16_t crc_value = crc_generator_.get_value();
add_byte(crc_value >> 8); add_byte(crc_value >> 8);
add_byte(crc_value & 0xff); add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
} }
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors) { const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>( return GetTrackWithSectors<FMEncoder>(
sectors, sectors,
16, 0x00, 16, 0x00,
6, 0, 6,
17, 14, (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 0, sector_gap_filler_byte,
(sector_gap_length != DefaultSectorGapLength) ? 0 : 17, 14,
0, 0,
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation 6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
} }
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors) { std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>( return GetTrackWithSectors<MFMEncoder>(
sectors, sectors,
50, 0x4e, 50, 0x4e,
12, 22, 12,
12, 18, (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 22, sector_gap_filler_byte,
(sector_gap_length != DefaultSectorGapLength) ? 0 : 12, 18,
32, 32,
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm) 12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
} }
@ -228,25 +231,26 @@ std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8
Parser::Parser(bool is_mfm) : Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300), Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff), crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm) { shift_register_(0), is_mfm_(is_mfm),
track_(0), head_(0) {
Storage::Time bit_length; Storage::Time bit_length;
bit_length.length = 1; bit_length.length = 1;
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
set_expected_bit_length(bit_length); set_expected_bit_length(bit_length);
drive.reset(new Storage::Disk::Drive); drive_.reset(new Storage::Disk::Drive);
set_drive(drive); set_drive(drive_);
set_motor_on(true); set_motor_on(true);
} }
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) : Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser(is_mfm) { Parser(is_mfm) {
drive->set_disk(disk); drive_->set_disk(disk);
} }
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) : Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
Parser(is_mfm) { Parser(is_mfm) {
drive->set_disk_with_track(track); drive_->set_disk_with_track(track);
} }
void Parser::seek_to_track(uint8_t track) { void Parser::seek_to_track(uint8_t track) {
@ -261,7 +265,20 @@ void Parser::seek_to_track(uint8_t track) {
} }
} }
std::shared_ptr<Sector> Parser::get_sector(uint8_t track, uint8_t sector) { std::shared_ptr<Sector> Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) {
// Check cache for sector.
int index = get_index(head, track, sector);
auto cached_sector = sectors_by_index_.find(index);
if(cached_sector != sectors_by_index_.end()) {
return cached_sector->second;
}
// Failing that, set the proper head and track, and search for the sector. get_sector automatically
// inserts everything found into sectors_by_index_.
if(head_ != head) {
drive_->set_head(head);
invalidate_track();
}
seek_to_track(track); seek_to_track(track);
return get_sector(sector); return get_sector(sector);
} }
@ -384,8 +401,7 @@ std::vector<uint8_t> Parser::get_track() {
} }
std::shared_ptr<Sector> Parser::get_next_sector() std::shared_ptr<Sector> Parser::get_next_sector() {
{
std::shared_ptr<Sector> sector(new Sector); std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0; index_count_ = 0;
@ -455,6 +471,10 @@ std::shared_ptr<Sector> Parser::get_next_sector()
if((data_crc >> 8) != get_next_byte()) continue; if((data_crc >> 8) != get_next_byte()) continue;
if((data_crc & 0xff) != get_next_byte()) continue; if((data_crc & 0xff) != get_next_byte()) continue;
// Put this sector into the cache.
int index = get_index(head_, track_, sector->sector);
sectors_by_index_[index] = sector;
return sector; return sector;
} }
@ -465,7 +485,7 @@ std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
std::shared_ptr<Sector> first_sector; std::shared_ptr<Sector> first_sector;
index_count_ = 0; index_count_ = 0;
while(!first_sector && index_count_ < 2) first_sector = get_next_sector(); while(!first_sector && index_count_ < 2) first_sector = get_next_sector();
if(!first_sector) return first_sector; if(!first_sector) return nullptr;
if(first_sector->sector == sector) return first_sector; if(first_sector->sector == sector) return first_sector;
while(1) { while(1) {
@ -475,3 +495,7 @@ std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
if(next_sector->sector == sector) return next_sector; if(next_sector->sector == sector) return next_sector;
} }
} }
int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) {
return head | (track << 8) | (sector << 16);
}

View File

@ -36,13 +36,39 @@ const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator sho
const uint8_t MFMIndexSyncByteValue = 0xc2; const uint8_t MFMIndexSyncByteValue = 0xc2;
const uint8_t MFMSyncByteValue = 0xa1; const uint8_t MFMSyncByteValue = 0xa1;
/*!
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
and a few extra flags of metadata.
*/
struct Sector { struct Sector {
uint8_t track, side, sector; uint8_t track, side, sector, size;
std::vector<uint8_t> data; std::vector<uint8_t> data;
bool has_data_crc_error;
bool has_header_crc_error;
bool is_deleted;
Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {}
}; };
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors); extern const size_t DefaultSectorGapLength;
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors); /*!
Converts a vector of sectors into a properly-encoded MFM track.
@param sectors The sectors to write.
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
*/
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
/*!
Converts a vector of sectors into a properly-encoded FM track.
@param sectors The sectors to write.
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
*/
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
class Encoder { class Encoder {
public: public:
@ -53,7 +79,9 @@ class Encoder {
virtual void add_data_address_mark() = 0; virtual void add_data_address_mark() = 0;
virtual void add_deleted_data_address_mark() = 0; virtual void add_deleted_data_address_mark() = 0;
virtual void output_short(uint16_t value); virtual void output_short(uint16_t value);
void add_crc();
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
void add_crc(bool incorrectly);
protected: protected:
NumberTheory::CRC16 crc_generator_; NumberTheory::CRC16 crc_generator_;
@ -75,7 +103,7 @@ class Parser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise. @returns a sector if one was found; @c nullptr otherwise.
*/ */
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t track, uint8_t sector); std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t head, uint8_t track, uint8_t sector);
/*! /*!
Attempts to read the track at @c track, starting from the index hole. Attempts to read the track at @c track, starting from the index hole.
@ -92,10 +120,10 @@ class Parser: public Storage::Disk::Controller {
private: private:
Parser(bool is_mfm); Parser(bool is_mfm);
std::shared_ptr<Storage::Disk::Drive> drive; std::shared_ptr<Storage::Disk::Drive> drive_;
unsigned int shift_register_; unsigned int shift_register_;
int index_count_; int index_count_;
uint8_t track_; uint8_t track_, head_;
int bit_count_; int bit_count_;
NumberTheory::CRC16 crc_generator_; NumberTheory::CRC16 crc_generator_;
bool is_mfm_; bool is_mfm_;
@ -110,6 +138,9 @@ class Parser: public Storage::Disk::Controller {
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector(); std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector();
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector); std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
std::vector<uint8_t> get_track(); std::vector<uint8_t> get_track();
std::map<int, std::shared_ptr<Storage::Encodings::MFM::Sector>> sectors_by_index_;
int get_index(uint8_t head, uint8_t track, uint8_t sector);
}; };

View File

@ -14,6 +14,7 @@
namespace { namespace {
static const unsigned int sectors_per_track = 16; static const unsigned int sectors_per_track = 16;
static const unsigned int bytes_per_sector = 256; static const unsigned int bytes_per_sector = 256;
static const unsigned int sector_size = 1;
} }
using namespace Storage::Disk; using namespace Storage::Disk;
@ -69,6 +70,7 @@ std::shared_ptr<Track> AcornADF::get_uncached_track_at_position(unsigned int hea
new_sector.track = (uint8_t)position; new_sector.track = (uint8_t)position;
new_sector.side = (uint8_t)head; new_sector.side = (uint8_t)head;
new_sector.sector = (uint8_t)sector; new_sector.sector = (uint8_t)sector;
new_sector.size = sector_size;
new_sector.data.resize(bytes_per_sector); new_sector.data.resize(bytes_per_sector);
fread(&new_sector.data[0], 1, bytes_per_sector, file_); fread(&new_sector.data[0], 1, bytes_per_sector, file_);
@ -87,7 +89,7 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p
std::vector<uint8_t> parsed_track; std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(true, track); Storage::Encodings::MFM::Parser parser(true, track);
for(unsigned int c = 0; c < sectors_per_track; c++) { for(unsigned int c = 0; c < sectors_per_track; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c); std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
if(sector) { if(sector) {
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
} else { } else {

View File

@ -75,8 +75,8 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
// Grab the track information. // Grab the track information.
fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector
int number_of_sectors = fgetc(file_); int number_of_sectors = fgetc(file_);
__unused uint8_t gap3_length = (uint8_t)fgetc(file_); uint8_t gap3_length = (uint8_t)fgetc(file_);
__unused uint8_t filler_byte = (uint8_t)fgetc(file_); uint8_t filler_byte = (uint8_t)fgetc(file_);
// Grab the sector information // Grab the sector information
struct SectorInfo { struct SectorInfo {
@ -111,6 +111,7 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
new_sector.track = sector_info.track; new_sector.track = sector_info.track;
new_sector.side = sector_info.side; new_sector.side = sector_info.side;
new_sector.sector = sector_info.sector; new_sector.sector = sector_info.sector;
new_sector.size = sector_info.length;
size_t data_size; size_t data_size;
if(is_extended_) { if(is_extended_) {
@ -122,32 +123,34 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
new_sector.data.resize(data_size); new_sector.data.resize(data_size);
fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_); fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_);
// TODO: obey the status bytes, somehow (?) if(sector_info.status1 & 0x08) {
if(sector_info.status1 || sector_info.status2) { // The CRC failed in the ID field.
if(sector_info.status1 & 0x08) { new_sector.has_header_crc_error = true;
// The CRC failed in the ID field. }
}
if(sector_info.status2 & 0x20) { if(sector_info.status2 & 0x20) {
// The CRC failed in the data field. // The CRC failed in the data field.
} new_sector.has_data_crc_error = true;
}
if(sector_info.status2 & 0x40) { if(sector_info.status2 & 0x40) {
// This sector is marked as deleted. // This sector is marked as deleted.
} new_sector.is_deleted = true;
}
if(sector_info.status2 & 0x01) { if(sector_info.status2 & 0x01) {
// Data field wasn't found. // Data field wasn't found.
} new_sector.data.clear();
printf("Unhandled: status errors\n");
} }
sectors.push_back(std::move(new_sector)); sectors.push_back(std::move(new_sector));
} }
// TODO: supply gay 3 length and filler byte // TODO: extensions to the extended format; John Elliot's addition of single-density support,
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors); // and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential
// FM/MFM encoding that can produce specified weak values.
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte);
return nullptr; return nullptr;
} }

View File

@ -60,6 +60,7 @@ std::shared_ptr<Track> SSD::get_uncached_track_at_position(unsigned int head, un
new_sector.track = (uint8_t)position; new_sector.track = (uint8_t)position;
new_sector.side = 0; new_sector.side = 0;
new_sector.sector = (uint8_t)sector; new_sector.sector = (uint8_t)sector;
new_sector.size = 1;
new_sector.data.resize(256); new_sector.data.resize(256);
fread(new_sector.data.data(), 1, 256, file_); fread(new_sector.data.data(), 1, 256, file_);
@ -81,7 +82,7 @@ void SSD::store_updated_track_at_position(unsigned int head, unsigned int positi
std::vector<uint8_t> parsed_track; std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(false, track); Storage::Encodings::MFM::Parser parser(false, track);
for(unsigned int c = 0; c < 10; c++) { for(unsigned int c = 0; c < 10; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c); std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
if(sector) { if(sector) {
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
} else { } else {

View File

@ -0,0 +1,120 @@
//
// CPM.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CPM.hpp"
#include "../Encodings/MFM.hpp"
using namespace Storage::Disk::CPM;
std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock &parameters) {
Storage::Encodings::MFM::Parser parser(true, disk);
// Assemble the actual bytes of the catalogue.
std::vector<uint8_t> catalogue;
size_t sector_size = 1;
uint16_t catalogue_allocation_bitmap = parameters.catalogue_allocation_bitmap;
if(!catalogue_allocation_bitmap) return nullptr;
int sector = 0;
int track = parameters.reserved_tracks;
while(catalogue_allocation_bitmap) {
if(catalogue_allocation_bitmap & 0x8000) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
if(!sector_contents) {
return nullptr;
}
catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end());
sector_size = sector_contents->data.size();
}
catalogue_allocation_bitmap <<= 1;
sector++;
if(sector == parameters.sectors_per_track) {
sector = 0;
track++;
}
}
std::unique_ptr<Catalogue> result(new Catalogue);
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
// From the catalogue, create files.
std::map<std::vector<uint8_t>, size_t> indices_by_name;
File empty_file;
for(size_t c = 0; c < catalogue.size(); c += 32) {
// Skip this file if it's deleted; this is marked by it having 0xe5 as its user number
if(catalogue[c] == 0xe5) continue;
// Check whether this file has yet been seen; if not then add it to the list
std::vector<uint8_t> descriptor;
size_t index;
descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]);
auto iterator = indices_by_name.find(descriptor);
if(iterator != indices_by_name.end()) {
index = iterator->second;
} else {
File new_file;
new_file.user_number = catalogue[c];
for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]);
for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f);
new_file.read_only = catalogue[c + 9] & 0x80;
new_file.system = catalogue[c + 10] & 0x80;
index = result->files.size();
result->files.push_back(new_file);
indices_by_name[descriptor] = index;
}
// figure out where this data needs to be pasted in
size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
int number_of_records = catalogue[c + 15];
size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128;
if(result->files[index].data.size() < required_size) {
result->files[index].data.resize(required_size);
}
int sectors_per_block = parameters.block_size / (int)sector_size;
int records_per_sector = (int)sector_size / 128;
int record = 0;
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) {
int block_number;
if(has_long_allocation_units) {
block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8);
} else {
block_number = catalogue[c + 16 + block];
}
if(!block_number) {
record += parameters.block_size / 128;
continue;
}
int first_sector = block_number * sectors_per_block;
sector = first_sector % parameters.sectors_per_track;
track = first_sector / parameters.sectors_per_track;
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
if(!sector_contents) break;
sector++;
if(sector == parameters.sectors_per_track) {
sector = 0;
track++;
}
int records_to_copy = std::min(number_of_records - record, records_per_sector);
memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
record += records_to_copy;
}
}
}
return result;
}

View File

@ -0,0 +1,51 @@
//
// CPM.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Storage_Disk_Parsers_CPM_hpp
#define Storage_Disk_Parsers_CPM_hpp
#include "../Disk.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace Storage {
namespace Disk {
namespace CPM {
struct ParameterBlock {
int sectors_per_track;
int tracks;
int block_size;
int first_sector;
uint16_t catalogue_allocation_bitmap;
int reserved_tracks;
};
struct File {
uint8_t user_number;
std::string name;
std::string type;
bool read_only;
bool system;
std::vector<uint8_t> data;
};
struct Catalogue {
std::vector<File> files;
};
std::unique_ptr<Catalogue> GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock &parameters);
}
}
}
#endif /* Storage_Disk_Parsers_CPM_hpp */