Kjetil's Information Center: A Blog About My Projects

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


Topic: Scripts and Code, by Kjetil @ 12/05-2016, Article Link