diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index 0e0e3455b..ef207a8ff 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -125,7 +125,7 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) if((_shift_register & 0x3ff) == 0x3ff) { _driveVIA.set_sync_detected(true); - _bit_window_offset = -1; + _bit_window_offset = -1; // i.e. this bit isn't the first within a data window, but the next might be } else { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 124da7db1..9a9652a66 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; }; 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; }; 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; }; + 4B4C83701D4F623200CD541F /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C836E1D4F623200CD541F /* D64.cpp */; }; 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; @@ -408,6 +409,8 @@ 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = ""; }; 4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = ""; }; + 4B4C836E1D4F623200CD541F /* D64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = D64.cpp; sourceTree = ""; }; + 4B4C836F1D4F623200CD541F /* D64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = D64.hpp; sourceTree = ""; }; 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = ""; }; 4B4DC8201D2C2425003C5BF8 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Vic20.hpp; sourceTree = ""; }; 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = C1540.cpp; sourceTree = ""; }; @@ -1027,6 +1030,8 @@ children = ( 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */, 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */, + 4B4C836E1D4F623200CD541F /* D64.cpp */, + 4B4C836F1D4F623200CD541F /* D64.hpp */, ); path = Formats; sourceTree = ""; @@ -1938,6 +1943,7 @@ 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, + 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 0c446427c..3afb2fd20 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -50,6 +50,7 @@ class Vic20Document: MachineDocument { switch pathExtension.lowercaseString { case "tap": vic20.openTAPAtURL(url) case "g64": vic20.openG64AtURL(url) + case "d64": vic20.openD64AtURL(url) default: let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) try self.readFromFileWrapper(fileWrapper, ofType: typeName) diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index a8ce924f6..ec505d1aa 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -116,6 +116,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).Vic20Document + + CFBundleTypeExtensions + + d64 + + CFBundleTypeName + Commodore 1540/1 Disk + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).Vic20Document + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index 2785ce6d1..d2a6f48ce 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -44,7 +44,7 @@ std::shared_ptr tape(new Storage::UEF([URL fileSystemRepresentation])); _electron.set_tape(tape); return YES; - } catch(int exception) { + } catch(...) { return NO; } } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index ae0dfc86c..90bd349ec 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -20,6 +20,7 @@ - (void)setPRG:(nonnull NSData *)prg; - (BOOL)openTAPAtURL:(nonnull NSURL *)URL; - (BOOL)openG64AtURL:(nonnull NSURL *)URL; +- (BOOL)openD64AtURL:(nonnull NSURL *)URL; @property (nonatomic, assign) BOOL useFastLoadingHack; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index 67ec6ace9..f05fa6494 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -11,6 +11,7 @@ #include "Vic20.hpp" #include "CommodoreTAP.hpp" #include "G64.hpp" +#include "D64.hpp" using namespace Commodore::Vic20; @@ -51,19 +52,31 @@ using namespace Commodore::Vic20; std::shared_ptr tape(new Storage::CommodoreTAP([URL fileSystemRepresentation])); _vic20.set_tape(tape); return YES; - } catch(int exception) { + } catch(...) { return NO; } } } - (BOOL)openG64AtURL:(NSURL *)URL { + return [self openDisk:^std::shared_ptr{ + return std::shared_ptr(new Storage::G64([URL fileSystemRepresentation])); + }]; +} + +- (BOOL)openD64AtURL:(NSURL *)URL { + return [self openDisk:^std::shared_ptr{ + return std::shared_ptr(new Storage::D64([URL fileSystemRepresentation])); + }]; +} + +- (BOOL)openDisk:(std::shared_ptr (^)())opener { @synchronized(self) { try { - std::shared_ptr disk(new Storage::G64([URL fileSystemRepresentation])); + std::shared_ptr disk = opener(); _vic20.set_disk(disk); return YES; - } catch(int exception) { + } catch(...) { return NO; } } diff --git a/Storage/Disk/Encodings/CommodoreGCR.cpp b/Storage/Disk/Encodings/CommodoreGCR.cpp index fb756a345..b86836673 100644 --- a/Storage/Disk/Encodings/CommodoreGCR.cpp +++ b/Storage/Disk/Encodings/CommodoreGCR.cpp @@ -31,6 +31,7 @@ unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibbl case 0x5: return 0x0f; case 0x6: return 0x16; case 0x7: return 0x17; + case 0x8: return 0x09; case 0x9: return 0x19; case 0xa: return 0x1a; @@ -49,3 +50,19 @@ unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte) { return encoding_for_nibble(byte) | (encoding_for_nibble(byte >> 4) << 5); } + +void Storage::Encodings::CommodoreGCR::encode_block(uint8_t *destination, uint8_t *source) +{ + unsigned int encoded_bytes[4] = { + encoding_for_byte(source[0]), + encoding_for_byte(source[1]), + encoding_for_byte(source[2]), + encoding_for_byte(source[3]), + }; + + destination[0] = (uint8_t)(encoded_bytes[0] >> 2); + destination[1] = (uint8_t)((encoded_bytes[0] << 6) | (encoded_bytes[1] >> 4)); + destination[2] = (uint8_t)((encoded_bytes[1] << 4) | (encoded_bytes[2] >> 6)); + destination[3] = (uint8_t)((encoded_bytes[2] << 2) | (encoded_bytes[3] >> 8)); + destination[4] = (uint8_t)(encoded_bytes[3]); +} diff --git a/Storage/Disk/Encodings/CommodoreGCR.hpp b/Storage/Disk/Encodings/CommodoreGCR.hpp index 38c32efe7..76504bb6f 100644 --- a/Storage/Disk/Encodings/CommodoreGCR.hpp +++ b/Storage/Disk/Encodings/CommodoreGCR.hpp @@ -31,6 +31,11 @@ namespace CommodoreGCR { @returns the ten-bit GCR encoding for @c byte. */ unsigned int encoding_for_byte(uint8_t byte); + + /*! + A block is defined to be four source bytes, which encodes to five GCR bytes. + */ + void encode_block(uint8_t *destination, uint8_t *source); } } } diff --git a/Storage/Disk/Formats/D64.cpp b/Storage/Disk/Formats/D64.cpp new file mode 100644 index 000000000..38765eb50 --- /dev/null +++ b/Storage/Disk/Formats/D64.cpp @@ -0,0 +1,167 @@ +// +// D64.cpp +// Clock Signal +// +// Created by Thomas Harte on 01/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "D64.hpp" + +#include +#include +#include "../PCMTrack.hpp" +#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp" + +using namespace Storage; + +D64::D64(const char *file_name) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // in D64, this is it for validation without imposing potential false-negative tests — check that + // the file size appears to be correct. Stone-age stuff. + if(file_stats.st_size != 174848 && file_stats.st_size != 196608) + throw ErrorNotD64; + + _number_of_tracks = (file_stats.st_size == 174848) ? 35 : 40; + + _file = fopen(file_name, "rb"); + + if(!_file) + throw ErrorNotD64; + + // then, ostensibly, this is a valid file. Hmmm. Pick a disk ID as a function of the file_name, + // being the most stable thing available + _disk_id = 0; + while(*file_name) + { + _disk_id ^= file_name[0]; + _disk_id = (uint16_t)((_disk_id << 2) ^ (_disk_id >> 13)); + file_name++; + } +} + +D64::~D64() +{ + if(_file) fclose(_file); +} + +unsigned int D64::get_head_position_count() +{ + return _number_of_tracks*2; +} + +std::shared_ptr D64::get_track_at_position(unsigned int position) +{ + // every other track is missing + if(position&1) + return std::shared_ptr(); + + // figure out where this track starts on the disk + int offset_to_track = 0; + int tracks_to_traverse = position >> 1; + + int zone_sizes[] = {17, 7, 6, 10}; + int sectors_by_zone[] = {21, 19, 18, 17}; + int zone = 0; + for(int current_zone = 0; current_zone < 4; current_zone++) + { + int tracks_in_this_zone = std::min(tracks_to_traverse, zone_sizes[current_zone]); + offset_to_track += tracks_in_this_zone * sectors_by_zone[current_zone]; + tracks_to_traverse -= tracks_in_this_zone; + if(tracks_in_this_zone == zone_sizes[current_zone]) zone++; + } + + // seek to start of data + fseek(_file, offset_to_track * 256, SEEK_SET); + + // build up a PCM sampling of the GCR version of this track + + // format per sector: + // + // syncronisation: three $FFs directly in GCR + // value $08 to announce a header + // a checksum made of XORing the following four bytes + // sector number (1 byte) + // track number (1 byte) + // disk ID (2 bytes) + // five GCR bytes of value $55 + // = [6 bytes -> 7.5 GCR bytes] + ... = 21 GCR bytes + // + // syncronisation: three $FFs directly in GCR + // value $07 to announce data + // 256 data bytes + // a checksum: the XOR of the previous 256 bytes + // two bytes of vaue $00 + // = [260 bytes -> 325 GCR bytes] + 3 GCR bytes = 328 GCR bytes + // + // = 349 GCR bytes per sector + + 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); + + memset(data, 0, track_bytes); + + for(int sector = 0; sector < sectors_by_zone[zone]; sector++) + { + uint8_t *sector_data = &data[sector * 349]; + sector_data[0] = sector_data[1] = sector_data[2] = 0xff; + + uint8_t sector_number = (uint8_t)(sector); // sectors count from 0 + uint8_t track_number = (uint8_t)((position >> 1) + 1); // tracks count from 1 + uint8_t checksum = (uint8_t)(sector_number ^ track_number ^ _disk_id ^ (_disk_id >> 8)); + uint8_t header_start[4] = { + 0x08, checksum, sector_number, track_number + }; + Encodings::CommodoreGCR::encode_block(§or_data[3], header_start); + + uint8_t header_end[4] = { + (uint8_t)(_disk_id & 0xff), (uint8_t)(_disk_id >> 8), 0, 0 + }; + Encodings::CommodoreGCR::encode_block(§or_data[8], header_end); + + // pad out post-header parts + uint8_t zeros[4] = {0, 0, 0, 0}; + Encodings::CommodoreGCR::encode_block(§or_data[13], zeros); + sector_data[18] = 0x52; + sector_data[19] = 0x94; + sector_data[20] = 0xaf; + + // get the actual contents + uint8_t source_data[256]; + fread(source_data, 1, 256, _file); + + // compute the latest checksum + checksum = 0; + for(int c = 0; c < 256; c++) + checksum ^= source_data[c]; + + // put in another sync + sector_data[21] = sector_data[22] = sector_data[23] = 0xff; + + // now start writing in the actual data + uint8_t start_of_data[4] = { + 0x07, source_data[0], source_data[1], source_data[2] + }; + Encodings::CommodoreGCR::encode_block(§or_data[24], start_of_data); + int source_data_offset = 3; + int target_data_offset = 29; + while((source_data_offset+4) < 256) + { + Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], &source_data[source_data_offset]); + target_data_offset += 5; + source_data_offset += 4; + } + uint8_t end_of_data[4] = { + source_data[255], checksum, 0, 0 + }; + Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], end_of_data); + } + + return std::shared_ptr(new PCMTrack(std::move(track))); +} diff --git a/Storage/Disk/Formats/D64.hpp b/Storage/Disk/Formats/D64.hpp new file mode 100644 index 000000000..eefc32f80 --- /dev/null +++ b/Storage/Disk/Formats/D64.hpp @@ -0,0 +1,46 @@ +// +// D64.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef D64_hpp +#define D64_hpp + +#include "../Disk.hpp" + +namespace Storage { + +/*! + Provies a @c Disk containing a D64 disk image — a decoded sector dump of a C1540-format disk. +*/ +class D64: public Disk { + public: + /*! + Construct a @c D64 containing content from the file with name @c file_name. + + @throws ErrorCantOpen if this file can't be opened. + @throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image. + */ + D64(const char *file_name); + ~D64(); + + enum { + ErrorCantOpen, + ErrorNotD64, + }; + + // implemented to satisfy @c Disk + unsigned int get_head_position_count(); + std::shared_ptr get_track_at_position(unsigned int position); + + private: + FILE *_file; + unsigned int _number_of_tracks; + uint16_t _disk_id; +}; + +}; +#endif /* D64_hpp */ diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index 32dc79a9d..f909c33a0 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -19,6 +19,8 @@ PCMTrack::PCMTrack(std::vector segments) PCMTrack::PCMTrack(PCMSegment segment) { + segment.length_of_a_bit.length = 1; + segment.length_of_a_bit.clock_rate = 1; _segments.push_back(std::move(segment)); fix_length(); } diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp index 758846926..f16c0ca7d 100644 --- a/Storage/Disk/PCMTrack.hpp +++ b/Storage/Disk/PCMTrack.hpp @@ -41,6 +41,7 @@ class PCMTrack: public Track { /*! Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate. + The segment's @c length_of_a_bit will be ignored and therefore need not be filled in. */ PCMTrack(PCMSegment segment);