regs/2mg.c

345 lines
8.4 KiB
C

/**
* @copyright 2018 Sean Kasun
* Extracts 2mg and other prodos disk images into a directory structure
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <argp.h>
#include <stdbool.h>
#include <locale.h>
#include "handle.h"
#include "prodos_types.h"
static void doDirectory(uint16_t key, uint8_t *disk, uint32_t disklen,
int depth);
static void doEntry(uint8_t *entry, uint8_t *disk, uint32_t disklen,
int depth);
static void doFile(uint16_t key, uint32_t len, char *name, uint8_t *disk,
uint32_t disklen, int type);
static void doGSOS(uint16_t key, char *name, uint8_t filetype, uint8_t *disk,
uint32_t disklen, int depth);
const char *argp_program_version = "2mg 0.2";
const char *argp_program_bug_address = "sean@seancode.com";
static char doc[] = "Extract ProDOS disk images";
static char args_doc[] = "DISKIMAGE";
static struct argp_option options[] = {
{"list", 'l', 0, 0, "List files"},
{ 0 }
};
struct arguments {
char *diskimage;
bool list;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'l':
arguments->list = true;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
}
arguments->diskimage = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
int main(int argc, char **argv) {
struct arguments arguments;
arguments.list = false;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
setlocale(LC_NUMERIC, "");
FILE *f = fopen(arguments.diskimage, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", arguments.diskimage);
return -1;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 64) {
fprintf(stderr, "%s is not a valid disk image\n", argv[1]);
fclose(f);
return -1;
}
uint8_t *header = malloc(64);
fread(header, 64, 1, f);
uint32_t disklen = len;
uint32_t diskofs = 0;
if (r4(header) == fourcc("2IMG")) {
if (r32(header + 0xc) != 1) {
fprintf(stderr, "Not a ProDOS disk image\n");
fclose(f);
return -1;
}
disklen = r32(header + 0x14) * 512;
diskofs = r32(header + 0x18);
}
free(header);
fseek(f, diskofs, SEEK_SET);
uint8_t *disk = malloc(disklen);
fread(disk, disklen, 1, f);
fclose(f);
doDirectory(2, disk, disklen, arguments.list ? 0 : -1);
free(disk);
return 0;
}
static void readFilename(uint8_t *filename, uint8_t length, char *outname) {
for (int i = 0; i < length; i++) {
char ch = filename[i];
if (isalnum(ch) || ch == '_' || ch == '.' || ch == ' ') {
*outname++ = ch;
} else {
*outname++ = 'x';
char hi = ch >> 4;
char lo = ch & 0xf;
if (hi > 9) {
*outname++ = 'a' + (hi - 10);
} else {
*outname++ = '0' + hi;
}
if (lo > 9) {
*outname++ = '0' + (lo - 10);
} else {
*outname++ = '0' + lo;
}
}
}
*outname = 0;
}
static void indent(int depth) {
for (int i = 0; i < depth; i++) {
printf(" ");
}
}
static void doDirectory(uint16_t key, uint8_t *disk, uint32_t disklen,
int depth) {
uint8_t *block = disk + key * 512;
if ((block[4] & 0xf0) != 0xf0 && (block[4] & 0xf0) != 0xe0) {
fprintf(stderr, "Invalid ProDOS disk\n");
return;
}
char dirname[50];
readFilename(block + 5, block[4] & 0xf, dirname);
if (depth < 0) {
mkdir(dirname, 0777);
chdir(dirname);
} else {
indent(depth);
printf("%s <dir>\n", dirname);
}
uint8_t entryLength = block[0x23];
uint8_t entriesPerBlock = block[0x24];
uint16_t fileCount = r16(block + 0x25);
uint8_t *entry = block + entryLength + 4;
uint8_t curEntry = 1;
uint16_t curFile = 0;
while (curFile < fileCount) {
if (entry[0] != 0) {
doEntry(entry, disk, disklen, depth < 0 ? -1 : depth + 1);
curFile++;
}
curEntry++;
entry += entryLength;
if (curEntry == entriesPerBlock) {
curEntry = 0;
block = disk + r16(block + 2) * 512;
entry = block + 4;
}
}
if (depth < 0) {
chdir("..");
}
}
static void printDateTime(uint16_t date, uint16_t time) {
printf("%02d-%02d-%02d %02d:%02d",
(date >> 9) & 0x7f, (date >> 5) & 0xf, date & 0x1f,
(time >> 8) & 0x1f, time & 0x3f);
}
static void doEntry(uint8_t *entry, uint8_t *disk, uint32_t disklen,
int depth) {
uint8_t filetype = entry[0x10];
uint16_t key = r16(entry + 0x11);
uint32_t eof = r24(entry + 0x15);
char filename[50];
readFilename(entry + 1, entry[0] & 0xf, filename);
switch (entry[0] & 0xf0) {
case 0x10: // seedling
case 0x20: // sapling
case 0x30: // tree
if (depth < 0) {
doFile(key, eof, filename, disk, disklen, entry[0] >> 4);
} else {
uint16_t createDate = r16(entry + 0x18);
uint16_t createTime = r16(entry + 0x1a);
uint16_t aux = r16(entry + 0x1f);
uint16_t modDate = r16(entry + 0x21);
uint16_t modTime = r16(entry + 0x23);
indent(depth);
printf("%s\n", filename);
indent(depth);
printf(" Size: %'d", eof);
printf(" Created: ");
printDateTime(createDate, createTime);
printf(" Modified: ");
printDateTime(modDate, modTime);
printf("\n");
indent(depth);
printf(" Type: $%02x Aux: $%04x ", filetype, aux);
uint32_t typeaux = filetype | (aux << 8);
for (int i = 0; i < numTypes; i++) {
if ((typeaux & fileTypes[i].mask) == fileTypes[i].id) {
printf("%s/%s\n", fileTypes[i].ext, fileTypes[i].desc);
break;
}
}
}
break;
case 0x50:
doGSOS(key, filename, filetype, disk, disklen, depth);
break;
case 0xd0:
doDirectory(key, disk, disklen, depth < 0 ? -1 : depth + 1);
break;
default:
fprintf(stderr, "Unknown file type: %x\n", entry[0] & 0xf0);
return;
}
}
static void dumpSeedling(uint8_t *block, uint32_t len, FILE *f) {
if (block == NULL) {
fseek(f, len, SEEK_CUR);
} else {
fwrite(block, len, 1, f);
}
}
static void dumpSapling(uint8_t *index, uint32_t len, FILE *f, uint8_t *disk,
uint32_t disklen) {
if (index == NULL) {
fseek(f, len, SEEK_CUR);
return;
}
while (len > 0) {
uint16_t blockid = index[0] | (index[256] << 8);
uint8_t *block = NULL;
if (blockid && (blockid + 1) * 512 <= disklen) {
block = disk + blockid * 512;
}
uint32_t blen = len > 512 ? 512 : len;
dumpSeedling(block, blen, f);
len -= blen;
index++;
}
}
static void dumpTree(uint8_t *index, uint32_t len, FILE *f, uint8_t *disk,
uint32_t disklen) {
if (index == NULL) {
fseek(f, len, SEEK_CUR);
return;
}
while (len > 0) {
uint16_t blockid = index[0] | (index[256] << 8);
uint8_t *block = NULL;
if (blockid && (blockid + 1) * 512 <= disklen) {
block = disk + blockid * 512;
}
uint32_t blen = len > 256 * 512 ? 256 * 512 : len;
dumpSapling(block, blen, f, disk, disklen);
len -= blen;
index++;
}
}
static void doGSOS(uint16_t key, char *name, uint8_t filetype, uint8_t *disk,
uint32_t disklen, int depth) {
uint8_t *block = disk + key * 512;
int type = *block;
uint16_t subkey = r16(block + 1);
uint32_t eof = r24(block + 5);
if (depth < 0) {
doFile(subkey, eof, name, disk, disklen, type);
}
char resname[50];
strncpy(resname, name, 50);
strncat(resname, ".res", 50);
block = disk + key * 512 + 0x100;
type = *block;
subkey = r16(block + 1);
uint32_t reof = r24(block + 5);
if (depth < 0) {
doFile(subkey, reof, resname, disk, disklen, type);
} else {
indent(depth);
printf("%s %d bytes resource: %d bytes\n", name, eof, reof);
}
}
static void doFile(uint16_t key, uint32_t len, char *name, uint8_t *disk,
uint32_t disklen, int type) {
uint8_t *block = disk + key * 512;
FILE *f = fopen(name, "wb");
if (!f) {
fprintf(stderr, "Failed to create '%s'\n", name);
return;
}
switch (type) {
case 1:
dumpSeedling(block, len, f);
break;
case 2:
dumpSapling(block, len, f, disk, disklen);
break;
case 3:
dumpTree(block, len, f, disk, disklen);
break;
}
fclose(f);
}