mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-10 23:31:24 +00:00
Merge pull request #42 from TomHarte/D64
Adds support for the .d64 file format
This commit is contained in:
commit
95a3c42407
@ -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
|
||||
{
|
||||
|
@ -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 = "<group>"; };
|
||||
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
|
||||
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
|
||||
4B4C836E1D4F623200CD541F /* D64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = D64.cpp; sourceTree = "<group>"; };
|
||||
4B4C836F1D4F623200CD541F /* D64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = D64.hpp; sourceTree = "<group>"; };
|
||||
4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = "<group>"; };
|
||||
4B4DC8201D2C2425003C5BF8 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Vic20.hpp; sourceTree = "<group>"; };
|
||||
4B4DC8261D2C2470003C5BF8 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = C1540.cpp; sourceTree = "<group>"; };
|
||||
@ -1027,6 +1030,8 @@
|
||||
children = (
|
||||
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
|
||||
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
|
||||
4B4C836E1D4F623200CD541F /* D64.cpp */,
|
||||
4B4C836F1D4F623200CD541F /* D64.hpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -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)
|
||||
|
@ -116,6 +116,20 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
@ -44,7 +44,7 @@
|
||||
std::shared_ptr<Storage::UEF> tape(new Storage::UEF([URL fileSystemRepresentation]));
|
||||
_electron.set_tape(tape);
|
||||
return YES;
|
||||
} catch(int exception) {
|
||||
} catch(...) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<Storage::CommodoreTAP> 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<Storage::Disk>{
|
||||
return std::shared_ptr<Storage::Disk>(new Storage::G64([URL fileSystemRepresentation]));
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)openD64AtURL:(NSURL *)URL {
|
||||
return [self openDisk:^std::shared_ptr<Storage::Disk>{
|
||||
return std::shared_ptr<Storage::Disk>(new Storage::D64([URL fileSystemRepresentation]));
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)openDisk:(std::shared_ptr<Storage::Disk> (^)())opener {
|
||||
@synchronized(self) {
|
||||
try {
|
||||
std::shared_ptr<Storage::G64> disk(new Storage::G64([URL fileSystemRepresentation]));
|
||||
std::shared_ptr<Storage::Disk> disk = opener();
|
||||
_vic20.set_disk(disk);
|
||||
return YES;
|
||||
} catch(int exception) {
|
||||
} catch(...) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
167
Storage/Disk/Formats/D64.cpp
Normal file
167
Storage/Disk/Formats/D64.cpp
Normal file
@ -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 <sys/stat.h>
|
||||
#include <algorithm>
|
||||
#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<Track> D64::get_track_at_position(unsigned int position)
|
||||
{
|
||||
// every other track is missing
|
||||
if(position&1)
|
||||
return std::shared_ptr<Track>();
|
||||
|
||||
// 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<Track>(new PCMTrack(std::move(track)));
|
||||
}
|
46
Storage/Disk/Formats/D64.hpp
Normal file
46
Storage/Disk/Formats/D64.hpp
Normal file
@ -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<Track> get_track_at_position(unsigned int position);
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
unsigned int _number_of_tracks;
|
||||
uint16_t _disk_id;
|
||||
};
|
||||
|
||||
};
|
||||
#endif /* D64_hpp */
|
@ -19,6 +19,8 @@ PCMTrack::PCMTrack(std::vector<PCMSegment> 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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user