mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
Merge pull request #51 from TomHarte/WD1770
Adds FM and MFM encoding logic, SSD, DSD, ADF file support, and an emulated WD1770
This commit is contained in:
commit
6c449e7936
460
Components/1770/1770.cpp
Normal file
460
Components/1770/1770.cpp
Normal file
@ -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()
|
||||
}
|
92
Components/1770/1770.hpp
Normal file
92
Components/1770/1770.hpp
Normal file
@ -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 */
|
@ -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<Storage::Disk::Disk> disk)
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
|
||||
drive->set_disk(disk);
|
||||
set_drive(drive);
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
CPU6502::Processor<Machine>::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
|
||||
|
@ -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<Machine>,
|
||||
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<Storage::Disk::Disk> disk);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
@ -9,7 +9,7 @@
|
||||
#ifndef SerialBus_hpp
|
||||
#define SerialBus_hpp
|
||||
|
||||
#import <vector>
|
||||
#include <vector>
|
||||
|
||||
namespace Commodore {
|
||||
namespace Serial {
|
||||
|
@ -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<uint8_t>(_adfs.begin() + 16384, _adfs.end()), true);
|
||||
}
|
||||
|
||||
_plus3->set_disk(target.disks.front(), 0);
|
||||
}
|
||||
|
||||
ROMSlot slot = ROMSlot12;
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> 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<uint8_t> 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)
|
||||
|
@ -15,8 +15,10 @@
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../Typer.hpp"
|
||||
#include "Plus3.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t> 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<uint8_t> _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> _plus3;
|
||||
bool is_holding_shift_;
|
||||
|
||||
// Outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> _crt;
|
||||
std::shared_ptr<Speaker> _speaker;
|
||||
|
35
Machines/Electron/Plus3.cpp
Normal file
35
Machines/Electron/Plus3.cpp
Normal file
@ -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<Storage::Disk::Disk> 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));
|
||||
}
|
28
Machines/Electron/Plus3.hpp
Normal file
28
Machines/Electron/Plus3.hpp
Normal file
@ -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<Storage::Disk::Disk> disk, int drive);
|
||||
void set_control_register(uint8_t control);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Storage::Disk::Drive> _drives[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Plus3_hpp */
|
||||
|
11
NumberTheory/CRC.cpp
Normal file
11
NumberTheory/CRC.cpp
Normal file
@ -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;
|
40
NumberTheory/CRC.hpp
Normal file
40
NumberTheory/CRC.hpp
Normal file
@ -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 <cstdint>
|
||||
|
||||
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 */
|
@ -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 = "<group>"; };
|
||||
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
|
||||
4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = "<group>"; };
|
||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
|
||||
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
|
||||
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
|
||||
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
|
||||
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
|
||||
@ -449,8 +463,8 @@
|
||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
|
||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = "<group>"; };
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = "<group>"; };
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
||||
4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
@ -458,6 +472,8 @@
|
||||
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
|
||||
4BA22B051D8817CE0008C640 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Commodore/Disk.cpp; sourceTree = "<group>"; };
|
||||
4BA22B061D8817CE0008C640 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Commodore/Disk.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
|
||||
@ -789,8 +805,12 @@
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = "<group>"; };
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
|
||||
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
|
||||
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
@ -802,6 +822,16 @@
|
||||
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
|
||||
4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
|
||||
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
|
||||
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
|
||||
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
|
||||
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
@ -936,6 +968,8 @@
|
||||
children = (
|
||||
4B2E2D9B1C3A070400138695 /* Electron.cpp */,
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
|
||||
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */,
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
|
||||
);
|
||||
name = Electron;
|
||||
sourceTree = "<group>";
|
||||
@ -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 = "<group>";
|
||||
@ -1388,6 +1428,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
sourceTree = "<group>";
|
||||
@ -1397,6 +1439,8 @@
|
||||
children = (
|
||||
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
|
||||
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */,
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */,
|
||||
);
|
||||
name = Encodings;
|
||||
sourceTree = "<group>";
|
||||
@ -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 = "<group>";
|
||||
@ -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 = "<group>";
|
||||
};
|
||||
4BD468F81D8DF4290084958B /* 1770 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */,
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */,
|
||||
);
|
||||
name = 1770;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
@ -21,6 +21,6 @@ class DocumentController: NSDocumentController {
|
||||
}
|
||||
}
|
||||
|
||||
return try! super.makeDocument(withContentsOf: url, ofType: typeName)
|
||||
return try super.makeDocument(withContentsOf: url, ofType: typeName)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -130,6 +130,22 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>ssd</string>
|
||||
<string>dsd</string>
|
||||
<string>adf</string>
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ElectronDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
18
OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h
Normal file
18
OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h
Normal file
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@interface NSData (StdVector)
|
||||
|
||||
- (std::vector<uint8_t>)stdVector8;
|
||||
|
||||
@end
|
19
OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm
Normal file
19
OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm
Normal file
@ -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<uint8_t>)stdVector8
|
||||
{
|
||||
uint8_t *bytes8 = (uint8_t *)self.bytes;
|
||||
return std::vector<uint8_t>(bytes8, bytes8 + self.length);
|
||||
}
|
||||
|
||||
@end
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
280
StaticAnalyser/Acorn/Disk.cpp
Normal file
280
StaticAnalyser/Acorn/Disk.cpp
Normal file
@ -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 <algorithm>
|
||||
|
||||
using namespace StaticAnalyser::Acorn;
|
||||
|
||||
class FMParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
std::shared_ptr<Storage::Disk::Drive> 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<Storage::Encodings::MFM::Sector> 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<Storage::Encodings::MFM::Sector> 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<Storage::Encodings::MFM::Sector> get_next_sector()
|
||||
{
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Storage::Encodings::MFM::Sector> 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<Storage::Encodings::MFM::Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1)
|
||||
{
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
|
||||
{
|
||||
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
FMParser parser(false);
|
||||
parser.drive->set_disk(disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Storage::Encodings::MFM::Sector> 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<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
|
||||
{
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
FMParser parser(true);
|
||||
parser.drive->set_disk(disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++)
|
||||
{
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> 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;
|
||||
}
|
35
StaticAnalyser/Acorn/Disk.hpp
Normal file
35
StaticAnalyser/Acorn/Disk.hpp
Normal file
@ -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<File> files;
|
||||
enum class BootOption {
|
||||
None,
|
||||
LoadBOOT,
|
||||
RunBOOT,
|
||||
ExecBOOT
|
||||
} bootOption;
|
||||
};
|
||||
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
9
StaticAnalyser/Acorn/File.cpp
Normal file
9
StaticAnalyser/Acorn/File.cpp
Normal file
@ -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"
|
47
StaticAnalyser/Acorn/File.hpp
Normal file
47
StaticAnalyser/Acorn/File.hpp
Normal file
@ -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 <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
uint32_t load_address;
|
||||
uint32_t execution_address;
|
||||
bool is_protected;
|
||||
std::vector<uint8_t> 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<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<Chunk> chunks;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
@ -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<Storage::Disk::Disk> disk = disks.front();
|
||||
std::unique_ptr<Catalogue> 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);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include "../TapeParser.hpp"
|
||||
#include "../../NumberTheory/CRC.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Acorn;
|
||||
|
||||
@ -23,7 +24,9 @@ enum class SymbolType {
|
||||
|
||||
class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, SymbolType> {
|
||||
public:
|
||||
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) : TapeParser(tape) {}
|
||||
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) :
|
||||
TapeParser(tape),
|
||||
_crc(0x1021, 0x0000) {}
|
||||
|
||||
int get_next_bit()
|
||||
{
|
||||
@ -49,7 +52,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
|
||||
set_error_flag();
|
||||
return -1;
|
||||
}
|
||||
add_to_crc((uint8_t)value);
|
||||
_crc.add((uint8_t)value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -67,8 +70,8 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
|
||||
return result;
|
||||
}
|
||||
|
||||
void reset_crc() { _crc = 0; }
|
||||
uint16_t get_crc() { return _crc; }
|
||||
void reset_crc() { _crc.reset(); }
|
||||
uint16_t get_crc() { return _crc.get_value(); }
|
||||
|
||||
private:
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
@ -111,17 +114,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
|
||||
remove_waves(1);
|
||||
}
|
||||
|
||||
void add_to_crc(uint8_t value)
|
||||
{
|
||||
_crc ^= (uint16_t)value << 8;
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
uint16_t exclusive_or = (_crc&0x8000) ? 0x1021 : 0x0000;
|
||||
_crc = (uint16_t)(_crc << 1) ^ exclusive_or;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t _crc;
|
||||
NumberTheory::CRC16 _crc;
|
||||
};
|
||||
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(Acorn1200BaudTapeParser &parser)
|
||||
|
@ -9,40 +9,14 @@
|
||||
#ifndef StaticAnalyser_Acorn_Tape_hpp
|
||||
#define StaticAnalyser_Acorn_Tape_hpp
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t> 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<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<Chunk> chunks;
|
||||
};
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
|
@ -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<Storage::Disk::Drive> 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<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
{
|
||||
std::list<File> files;
|
||||
CommodoreGCRParser parser;
|
||||
parser.set_disk(disk);
|
||||
parser.drive->set_disk(disk);
|
||||
|
||||
// find any sector whatsoever to establish the current track
|
||||
std::shared_ptr<CommodoreGCRParser::Sector> sector;
|
||||
|
@ -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<Target> 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<Target> 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)
|
||||
|
||||
|
@ -46,6 +46,7 @@ struct Target {
|
||||
struct {
|
||||
bool has_adfs;
|
||||
bool has_dfs;
|
||||
bool should_hold_shift;
|
||||
} acorn;
|
||||
};
|
||||
|
||||
|
@ -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<Track> get_track_at_position(unsigned int position) = 0;
|
||||
virtual std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 = 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 = drive;
|
||||
invalidate_track();
|
||||
}
|
||||
|
||||
void Controller::invalidate_track()
|
||||
{
|
||||
_track = nullptr;
|
||||
}
|
@ -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> 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> 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<DigitalPhaseLockedLoop> _pll;
|
||||
std::shared_ptr<Disk> _disk;
|
||||
std::shared_ptr<Drive> _drive;
|
||||
std::shared_ptr<Track> _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();
|
||||
};
|
||||
|
||||
}
|
46
Storage/Disk/Drive.cpp
Normal file
46
Storage/Disk/Drive.cpp
Normal file
@ -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 <algorithm>
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Drive::Drive()
|
||||
: _head_position(0), _head(0) {}
|
||||
|
||||
void Drive::set_disk(std::shared_ptr<Disk> 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<Track> Drive::get_track()
|
||||
{
|
||||
if(_disk) return _disk->get_track_at_position(_head, (unsigned int)_head_position);
|
||||
return nullptr;
|
||||
}
|
59
Storage/Disk/Drive.hpp
Normal file
59
Storage/Disk/Drive.hpp
Normal file
@ -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 <memory>
|
||||
#include "Disk.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
class Drive {
|
||||
public:
|
||||
Drive();
|
||||
|
||||
/*!
|
||||
Inserts @c disk into the drive.
|
||||
*/
|
||||
void set_disk(std::shared_ptr<Disk> 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<Track> get_track();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Disk> _disk;
|
||||
int _head_position;
|
||||
unsigned int _head;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Drive_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 <cstdint>
|
||||
|
217
Storage/Disk/Encodings/MFM.cpp
Normal file
217
Storage/Disk/Encodings/MFM.cpp
Normal file
@ -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 T> 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 T> class MFMShifter: public Shifter<T> {
|
||||
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<T *>(this)->output_short(output_);
|
||||
}
|
||||
|
||||
void add_index_address_mark() {
|
||||
static_cast<T *>(this)->output_short(output_ = MFMIndexAddressMark);
|
||||
add_byte(MFMIndexAddressByte);
|
||||
}
|
||||
|
||||
void add_ID_address_mark() {
|
||||
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
|
||||
add_byte(MFMIDAddressByte);
|
||||
}
|
||||
|
||||
void add_data_address_mark() {
|
||||
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
|
||||
add_byte(MFMDataAddressByte);
|
||||
}
|
||||
|
||||
void add_deleted_data_address_mark() {
|
||||
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
|
||||
add_byte(MFMDeletedDataAddressByte);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t output_;
|
||||
};
|
||||
|
||||
template <class T> class FMShifter: public Shifter<T> {
|
||||
// encodes each 16-bit part as clock, data, clock, data [...]
|
||||
public:
|
||||
void add_byte(uint8_t input) {
|
||||
static_cast<T *>(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<T *>(this)->output_short(FMIndexAddressMark); }
|
||||
void add_ID_address_mark() { static_cast<T *>(this)->output_short(FMIDAddressMark); }
|
||||
void add_data_address_mark() { static_cast<T *>(this)->output_short(FMDataAddressMark); }
|
||||
void add_deleted_data_address_mark() { static_cast<T *>(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<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
GetTrackWithSectors(
|
||||
const std::vector<Sector> §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<Storage::Disk::Track>(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::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors)
|
||||
{
|
||||
struct VectorShifter: public FMShifter<VectorShifter>, VectorReceiver {
|
||||
using VectorReceiver::output_short;
|
||||
};
|
||||
return GetTrackWithSectors<VectorShifter>(
|
||||
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::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> §ors)
|
||||
{
|
||||
struct VectorShifter: public MFMShifter<VectorShifter>, VectorReceiver {
|
||||
using VectorReceiver::output_short;
|
||||
};
|
||||
return GetTrackWithSectors<VectorShifter>(
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
12, 22,
|
||||
12, 18,
|
||||
32,
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
|
||||
}
|
45
Storage/Disk/Encodings/MFM.hpp
Normal file
45
Storage/Disk/Encodings/MFM.hpp
Normal file
@ -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 <cstdint>
|
||||
#include <vector>
|
||||
#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<uint8_t> data;
|
||||
};
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> §ors);
|
||||
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> §ors);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MFM_hpp */
|
87
Storage/Disk/Formats/AcornADF.cpp
Normal file
87
Storage/Disk/Formats/AcornADF.cpp
Normal file
@ -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 <sys/stat.h>
|
||||
#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<Track> AcornADF::get_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
std::shared_ptr<Track> 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<Storage::Encodings::MFM::Sector> 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;
|
||||
}
|
48
Storage/Disk/Formats/AcornADF.hpp
Normal file
48
Storage/Disk/Formats/AcornADF.hpp
Normal file
@ -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<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AcornADF_hpp */
|
@ -53,10 +53,10 @@ unsigned int D64::get_head_position_count()
|
||||
return _number_of_tracks*2;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> D64::get_track_at_position(unsigned int position)
|
||||
std::shared_ptr<Track> 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<Track>();
|
||||
|
||||
// figure out where this track starts on the disk
|
||||
@ -102,8 +102,8 @@ std::shared_ptr<Track> 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);
|
||||
|
||||
|
@ -35,7 +35,7 @@ class D64: public Disk {
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
unsigned int get_head_position_count();
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int position);
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
|
@ -54,13 +54,14 @@ unsigned int G64::get_head_position_count()
|
||||
return _number_of_tracks > 84 ? _number_of_tracks : 84;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
|
||||
std::shared_ptr<Track> G64::get_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
std::shared_ptr<Track> 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<Track> 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<uint8_t> track_contents(new uint8_t[track_length]);
|
||||
fread(track_contents.get(), 1, track_length, _file);
|
||||
std::vector<uint8_t> 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<Track> 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;
|
||||
|
@ -37,7 +37,7 @@ class G64: public Disk {
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
unsigned int get_head_position_count();
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int position);
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
|
81
Storage/Disk/Formats/SSD.cpp
Normal file
81
Storage/Disk/Formats/SSD.cpp
Normal file
@ -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 <sys/stat.h>
|
||||
#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<Track> SSD::get_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
std::shared_ptr<Track> track;
|
||||
|
||||
if(head >= _head_count) return track;
|
||||
long file_offset = (position * _head_count + head) * 256 * 10;
|
||||
fseek(_file, file_offset, SEEK_SET);
|
||||
|
||||
std::vector<Storage::Encodings::MFM::Sector> 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;
|
||||
}
|
50
Storage/Disk/Formats/SSD.hpp
Normal file
50
Storage/Disk/Formats/SSD.hpp
Normal file
@ -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<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
unsigned int _head_count;
|
||||
unsigned int _track_count;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SSD_hpp */
|
@ -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
|
||||
|
@ -23,7 +23,7 @@ namespace Disk {
|
||||
struct PCMSegment {
|
||||
Time length_of_a_bit;
|
||||
unsigned int number_of_bits;
|
||||
std::unique_ptr<uint8_t> data;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
Loading…
Reference in New Issue
Block a user