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; }