D64 PRG Dumper
Maybe such a program already exists, but I do not know of any, so I made my own. This program scans a D64 file, which is a disk image typically used by Commodore 64 emulators, and attempts to extract all programs (PRG files) from it.
Take a look:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>
#define SECTOR_SIZE 256
#define FIRST_DIR_TRACK 18
#define FIRST_DIR_SECTOR 1
#define DIR_ENTRIES_PER_SECTOR 8
#define FILE_NAME_SIZE 16
#pragma pack(1)
typedef struct dir_entry_s {
uint8_t next_dir_track;
uint8_t next_dir_sector;
uint8_t file_type;
uint8_t first_file_track;
uint8_t first_file_sector;
uint8_t petscii_file_name[FILE_NAME_SIZE];
uint8_t extra[11]; /* Unused by this program. */
} dir_entry_t;
#pragma pack()
static int byte_offset(int track_no, int sector_no)
{
int offset = 0;
while (track_no-- > 0) {
if (track_no >= 31) {
offset += 17;
} else if (track_no >= 25) {
offset += 18;
} else if (track_no >= 18) {
offset += 19;
} else if (track_no >= 1) {
offset += 21;
}
}
return (offset + sector_no) * SECTOR_SIZE;
}
static void dump_program(FILE *d64, char *name, int first_track, int first_sector)
{
uint8_t file_track, file_sector;
uint8_t data[254];
int size, saved = 0;
char path[PATH_MAX];
FILE *prg;
snprintf(path, PATH_MAX, "%s.prg", name);
prg = fopen(path, "wbx");
if (prg == NULL) {
fprintf(stderr, "Warning: fopen(%s) failed: %s\n", path, strerror(errno));
return;
}
file_track = first_track;
file_sector = first_sector;
do {
if (fseek(d64, byte_offset(file_track, file_sector), SEEK_SET) == -1) {
fprintf(stderr, "Warning: fseek(%d,%d) failed: %s\n", file_track, file_sector, strerror(errno));
fclose(prg);
return;
}
size = fread(&file_track, sizeof(uint8_t), 1, d64);
size += fread(&file_sector, sizeof(uint8_t), 1, d64);
size += fread(&data, sizeof(uint8_t), 254, d64);
if (size != SECTOR_SIZE) {
fprintf(stderr, "Warning: fread() read less than a sector for program: \"%s\"\n", name);
fclose(prg);
return;
}
saved += fwrite(&data, sizeof(uint8_t), 254, prg);
/* NOTE: No protection against circular references. */
} while (file_track != 0);
printf("Extracted: \"%s\" (%d bytes)\n", name, saved);
fclose(prg);
}
int main(int argc, char *argv[])
{
FILE *d64;
int i, n, size;
uint8_t dir_track, dir_sector, petscii;
char ascii_name[FILE_NAME_SIZE + 1];
dir_entry_t de[DIR_ENTRIES_PER_SECTOR];
if (argc != 2) {
fprintf(stderr, "Usage: %s <d64 image>\n", argv[0]);
return 1;
}
d64 = fopen(argv[1], "rb");
if (d64 == NULL) {
fprintf(stderr, "Error: fopen(%s) failed: %s\n", argv[1], strerror(errno));
return 1;
}
/* Traverse directory entries: */
dir_track = FIRST_DIR_TRACK;
dir_sector = FIRST_DIR_SECTOR;
do {
if (fseek(d64, byte_offset(dir_track, dir_sector), SEEK_SET) == -1) {
fprintf(stderr, "Error: fseek(%d,%d) failed: %s\n", dir_track, dir_sector, strerror(errno));
return 1;
}
size = fread(&de[0], sizeof(dir_entry_t), DIR_ENTRIES_PER_SECTOR, d64);
if (size != DIR_ENTRIES_PER_SECTOR) {
fprintf(stderr, "Error: fread() read less than a sector for directory entry\n");
return 1;
}
for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++) {
/* Only extract if PRG type, meaning bit 2 is set: */
if ((de[i].file_type & 0x2) == 0)
continue;
/* Convert file name from PETSCII to ASCII: */
memset(ascii_name, '\0', FILE_NAME_SIZE + 1);
for (n = 0; n < FILE_NAME_SIZE; n++) {
petscii = de[i].petscii_file_name[n];
if (petscii == 0xA0) /* Padding, end of name. */
break;
if (isalnum(petscii)) {
ascii_name[n] = tolower(petscii);
} else if (petscii == ' ') {
ascii_name[n] = '_';
} else {
ascii_name[n] = '.';
}
}
/* Dump the program unless 0: */
if (de[i].first_file_track == 0)
continue;
dump_program(d64, ascii_name, de[i].first_file_track, de[i].first_file_sector);
}
dir_track = de[0].next_dir_track;
dir_sector = de[0].next_dir_sector;
/* NOTE: No protection against circular references. */
} while (dir_track != 0);
fclose(d64);
return 0;
}