Introduces DOS 3.3 conversion.

This commit is contained in:
Thomas Harte 2018-06-05 19:40:13 -04:00
parent 08a2283cec
commit b0577eb459
2 changed files with 370 additions and 1 deletions

View File

@ -1,2 +1,13 @@
# dsk2woz
A tool to convert Apple II DSK images to WOZ format
A command-line tool to convert Apple II DSK images to WOZ format.
Usage:
dsk2woz input.dsk output.woz
Reads the contents of the DOS 3.3 disk input.dsk and outputs the WOZ-format file output.woz.
## Building
There are no dependencies beyond the C standard library. So e.g.
cc dsk2woz.c -o dsk2woz

358
dsk2woz.c Normal file
View File

@ -0,0 +1,358 @@
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
static uint32_t crc32(const uint8_t *buf, size_t size);
static void serialise_track(uint8_t *dest, const uint8_t *src, uint8_t track_number);
int main(int argc, char *argv[]) {
// Announce failure if there are anything other than three arguments.
if(argc != 3) {
printf("USAGE: dsk2woz input.dsk output.woz\n");
return -1;
}
// Attempt to read the standard DSK number of bytes into a buffer.
FILE *const dsk_file = fopen(argv[1], "rb");
if(!dsk_file) {
printf("ERROR: could not open %s for reading\n", argv[1]);
return -2;
}
const size_t dsk_image_size = 35 * 16 * 256;
uint8_t dsk[dsk_image_size];
const size_t bytes_read = fread(dsk, 1, dsk_image_size, dsk_file);
fclose(dsk_file);
// If the DSK image was too short, announce failure. Some DSK files
// seem empirically to be too long, but it's unclear that the extra
// bytes actually mean anything — they're usually not many.
if(bytes_read != dsk_image_size) {
printf("ERROR: DSK image too small\n");
return -3;
}
// Create a buffer for the portion of the WOZ image that comes after
// the 12-byte header. The header will house the CRC, which will be
// calculated later.
const size_t woz_image_size = 256 + 35*6656;
uint8_t woz[woz_image_size];
#define set_int32(location, value) \
woz[location] = (value) & 0xff; \
woz[location+1] = ((value) >> 8) & 0xff; \
woz[location+2] = ((value) >> 16) & 0xff; \
woz[location+3] = (value) >> 24;
/*
WOZ image item 1: an INFO chunk.
*/
strcpy((char *)&woz[0], "INFO"); // Chunk ID.
set_int32(4, 60); // Chunk size.
woz[8] = 1; // INFO version: 1.
woz[9] = 1; // Disk type: 5.25".
woz[10] = 0; // Write protection: disabled.
woz[11] = 0; // Cross-track synchronised image: no.
woz[12] = 1; // MC3470 fake bits have been removed: yes.
// (or, rather, were never inserted)
// Append creator, which needs to be padded out to 32
// bytes with space characters.
const char creator[] = "dsk2woz 1.0";
const size_t creator_length = strlen(creator);
assert(creator_length < 32);
strcpy((char *)&woz[13], creator);
memset(&woz[13 + strlen(creator)], 32 - strlen(creator), ' ');
// Chunk should be padded with 0s to reach 60 bytes in length.
memset(&woz[13 + 32], (8+60) - (13 + 32), 0);
/*
WOZ image item 2: a TMAP chunk.
*/
strcpy((char *)&woz[68], "TMAP"); // Chunk ID.
set_int32(72, 160); // Chunk size.
// This is a DSK conversion, so the TMAP table is just the next
// track in every fourth slot, with 255s in between.
for(int c = 0; c < 35; ++c) {
woz[76 + (c << 2)] = c;
woz[77 + (c << 2)] = woz[78 + (c << 2)] = woz[79 + (c << 2)] = 255;
}
// So there are 20 track slots that a DSK doesn't reach; set them
// to no-track-mapped.
memset(&woz[76 + (35 << 2)], 20, 0xff);
/*
WOZ image item 3: a TRKS chunk.
*/
strcpy((char *)&woz[236], "TRKS"); // Chunk ID.
set_int32(240, 35*65536); // Chunk size.
// The output pointer holds a **bit** position into the WOZ buffer.
size_t output_pointer = 244;
// Write out all 35 tracks.
for(int c = 0; c < 35; ++c) {
serialise_track(&woz[output_pointer], &dsk[c * 16 * 256], c);
output_pointer += 6656;
}
#undef set_int32
/*
WOZ image output.
*/
FILE *const woz_file = fopen(argv[2], "wb");
if(!woz_file) {
printf("ERROR: Could not open %s for writing\n", argv[2]);
return -5;
}
fputs("WOZ1", woz_file);
fputc(0xff, woz_file);
fputs("\n\r\n", woz_file);
const uint32_t crc = crc32(woz, sizeof(woz));
fputc(crc & 0xff, woz_file);
fputc((crc >> 8) & 0xff, woz_file);
fputc((crc >> 16) & 0xff, woz_file);
fputc(crc >> 24, woz_file);
const size_t length_written = fwrite(woz, 1, sizeof(woz), woz_file);
fclose(woz_file);
if(length_written != sizeof(woz)) {
printf("ERROR: Could not write full WOZ image\n");
return -6;
}
return 0;
}
/*
CRC generator. Essentially that of Gary S. Brown from 1986, but I've
fixed the initial value. This is exactly the code advocated by the
WOZ file specifications (with some extra consts).
*/
static const uint32_t crc32_tab[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
uint32_t crc32(const uint8_t *buf, size_t size) {
uint32_t crc = ~0;
size_t byte = 0;
while (size--) {
crc = crc32_tab[(crc ^ buf[byte]) & 0xFF] ^ (crc >> 8);
++byte;
}
return ~crc;
}
/*
DSK sector serialiser. Constructs the 6-and-2 DOS 3.3-style on-disk
representation of a DOS logical-order sector dump.
*/
static size_t write_bit(uint8_t *buffer, size_t position, int value) {
buffer[position >> 3] |= (value ? 0x80 : 0x00) >> (position & 7);
return position + 1;
}
static size_t write_byte(uint8_t *buffer, size_t position, int value) {
int mask = 0x80;
while(mask) {
position = write_bit(buffer, position, value & mask);
mask >>= 1;
}
return position;
}
static size_t write_4_and_4(uint8_t *buffer, size_t position, int value) {
position = write_byte(buffer, position, (value >> 1) | 0xaa);
position = write_byte(buffer, position, value | 0xaa);
return position;
}
static size_t write_sync(uint8_t *buffer, size_t position) {
position = write_byte(buffer, position, 0xff);
position = write_bit(buffer, position, 0);
position = write_bit(buffer, position, 0);
return position;
}
static void encode_6_and_2(uint8_t *dest, const uint8_t *src) {
const uint8_t six_and_two_mapping[] = {
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
// Fill in byte values: the first 86 bytes contain shuffled
// and combined copies of the bottom two bits of the sector
// contents; the 256 bytes afterwards are the remaining
// six bits.
const uint8_t bit_reverse[] = {0, 2, 1, 3};
for(size_t c = 0; c < 84; ++c) {
dest[c] =
bit_reverse[src[c]&3] |
(bit_reverse[src[c + 86]&3] << 2) |
(bit_reverse[src[c + 172]&3] << 4);
}
dest[84] =
(bit_reverse[src[84]&3] << 0) |
(bit_reverse[src[170]&3] << 2);
dest[85] =
(bit_reverse[src[85]&3] << 0) |
(bit_reverse[src[171]&3] << 2);
for(size_t c = 0; c < 256; ++c) {
dest[86 + c] = src[c] >> 2;
}
// Exclusive OR each byte with the one before it.
dest[342] = dest[341];
size_t location = 342;
while(location) {
--location;
dest[location] ^= dest[location-1];
}
// Map six-bit values up to full bytes.
for(size_t c = 0; c < 343; ++c) {
dest[c] = six_and_two_mapping[dest[c]];
}
}
void serialise_track(uint8_t *dest, const uint8_t *src, uint8_t track_number) {
size_t track_position = 0; // This is the track position **in bits**.
memset(dest, 0, 6646);
// Step through the physical sector.
for(int sector = 0; sector < 16; ++sector) {
/*
Write the sector header.
*/
// Lead-in.
for(int c = 0; c < 10; ++c) {
track_position = write_sync(dest, track_position);
}
// Prologue.
track_position = write_byte(dest, track_position, 0xd5);
track_position = write_byte(dest, track_position, 0xaa);
track_position = write_byte(dest, track_position, 0x96);
// Volume, track, setor and checksum, all in 4-and-4 format.
track_position = write_4_and_4(dest, track_position, 254);
track_position = write_4_and_4(dest, track_position, track_number);
track_position = write_4_and_4(dest, track_position, sector);
track_position = write_4_and_4(dest, track_position, 254 ^ track_number ^ sector);
// Epilogue.
track_position = write_byte(dest, track_position, 0xde);
track_position = write_byte(dest, track_position, 0xaa);
track_position = write_byte(dest, track_position, 0xeb);
/*
Write the sector body.
*/
// Lead-in.
for(int c = 0; c < 10; ++c) {
track_position = write_sync(dest, track_position);
}
// Prologue.
track_position = write_byte(dest, track_position, 0xd5);
track_position = write_byte(dest, track_position, 0xaa);
track_position = write_byte(dest, track_position, 0xad);
// Map from this physical sector to a logical sector.
const int logical_sector = (sector == 15) ? 15 : ((sector * 7) % 15);
// Sector contents
uint8_t contents[343];
encode_6_and_2(contents, &src[logical_sector * 256]);
for(size_t c = 0; c < sizeof(contents); ++c) {
track_position = write_byte(dest, track_position, contents[c]);
}
// Epilogue.
track_position = write_byte(dest, track_position, 0xde);
track_position = write_byte(dest, track_position, 0xaa);
track_position = write_byte(dest, track_position, 0xeb);
}
// Pad out to roughly 50,000 bits.
while(track_position < 50000) {
track_position = write_sync(dest, track_position);
}
// Add the track suffix.
dest[6646] = (track_position >> 3) & 0xff;
dest[6647] = (track_position >> 11) & 0xff; // Byte count.
dest[6648] = track_position & 0xff;
dest[6649] = (track_position >> 8) & 0xff; // Bit count.
dest[6650] = dest[6651] = 0xff; // Splice information: none.
}