joeylib2/tools/rawCd/rawCd.c

197 lines
6.7 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// rawCd.c - read data out of a raw CD image (2352-byte sectors, as
// CloneCD's .img and a .bin keep them).
//
// rawCd track IMAGE FIRST COUNT OUT
// COUNT Mode 1 sectors from FIRST (a track's start, as the image's
// .ccd or .cue gives it), their 2048 data bytes each, into OUT: the
// track as a plain image (an ISO 9660 or a BFS volume, say)
// rawCd eltorito ISO OUT
// the boot image an El Torito ISO boots with floppy emulation,
// 1.44 MB, into OUT; the ISO is 2048-byte sectors (such as a track
// `track` made), or a raw image whose first track is the ISO
//
// Read-only on its inputs.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "toolFile.h"
#define TOOL_NAME "rawCd"
#define USAGE_EXIT 2
#define RAW_SECTOR 2352u
#define RAW_SYNC_BYTES 12u
// 12 sync bytes and a 4-byte header before a Mode 1 sector's data.
#define MODE1_DATA_AT 16u
#define DATA_SECTOR 2048u
// ISO 9660's El Torito boot record volume descriptor: at sector 17, its
// "CD001" at 1, "EL TORITO" in bytes 7-38, the boot catalog's sector at
// 0x47; the catalog's default entry after its 32-byte validation entry:
// bootable 0x88, media type 2 (a 1.44 MB floppy), its load sector at 8.
#define BOOT_RECORD_SECTOR 17u
#define CD001_AT 1u
#define CD001_BYTES 5u
#define SYSTEM_ID_AT 7u
#define SYSTEM_ID_BYTES 32u
#define CATALOG_POINTER_AT 0x47u
#define DEFAULT_ENTRY_AT 32u
#define ENTRY_BYTES 32u
#define BOOTABLE 0x88u
#define MEDIA_FLOPPY_144 2u
#define LOAD_RBA_AT 8u
#define FLOPPY_144_BYTES 1474560u
static void eltorito(const char *iso, const char *out);
static uint32_t rd32(const uint8_t *p);
static bool readData(FILE *f, bool raw, uint32_t sector, size_t count, uint8_t *data, size_t *got);
static void track(const char *image, long first, long count, const char *out);
static void usage(void);
// The boot image an El Torito ISO boots with 1.44 MB floppy emulation.
static void eltorito(const char *iso, const char *out) {
static const uint8_t sync[RAW_SYNC_BYTES] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
uint8_t head[RAW_SYNC_BYTES];
uint8_t record[DATA_SECTOR];
uint8_t catalog[DATA_SECTOR];
uint8_t *image;
const uint8_t *entry;
size_t got;
bool raw;
bool torito;
uint32_t i;
FILE *f;
f = fopen(iso, "rb");
if (f == NULL) {
toolFail("cannot read %s", iso);
}
raw = fread(head, 1u, sizeof(head), f) == sizeof(head) && memcmp(head, sync, sizeof(sync)) == 0;
readData(f, raw, BOOT_RECORD_SECTOR, DATA_SECTOR, record, &got);
torito = false;
for (i = SYSTEM_ID_AT; got == DATA_SECTOR && i + strlen("EL TORITO") <= SYSTEM_ID_AT + SYSTEM_ID_BYTES; i++) {
if (memcmp(record + i, "EL TORITO", strlen("EL TORITO")) == 0) {
torito = true;
}
}
if (!torito || memcmp(record + CD001_AT, "CD001", CD001_BYTES) != 0) {
toolFail("%s has no El Torito boot record", iso);
}
if (!readData(f, raw, rd32(record + CATALOG_POINTER_AT), DATA_SECTOR, catalog, &got) || got < DEFAULT_ENTRY_AT + ENTRY_BYTES) {
toolFail("%s ends before its boot catalog", iso);
}
entry = catalog + DEFAULT_ENTRY_AT;
if (entry[0] != BOOTABLE || entry[1] != MEDIA_FLOPPY_144) {
toolFail("%s does not boot a 1.44 MB floppy image", iso);
}
image = (uint8_t *)malloc(FLOPPY_144_BYTES);
if (image == NULL) {
toolFail("out of memory");
}
readData(f, raw, rd32(entry + LOAD_RBA_AT), FLOPPY_144_BYTES, image, &got);
fclose(f);
if (!toolWriteFile(out, image, got)) {
toolFail("cannot write %s", out);
}
free(image);
}
static uint32_t rd32(const uint8_t *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
// count bytes of data from sector on: the data parts of raw sectors, or a
// plain image's bytes. got is how many there were (fewer at the image's
// end); false when there were none.
static bool readData(FILE *f, bool raw, uint32_t sector, size_t count, uint8_t *data, size_t *got) {
size_t n;
size_t want;
*got = 0u;
if (!raw) {
if (fseek(f, (long)sector * (long)DATA_SECTOR, SEEK_SET) != 0) {
return false;
}
*got = fread(data, 1u, count, f);
return *got > 0u;
}
while (*got < count) {
if (fseek(f, (long)sector * (long)RAW_SECTOR + (long)MODE1_DATA_AT, SEEK_SET) != 0) {
break;
}
want = count - *got < DATA_SECTOR ? count - *got : DATA_SECTOR;
n = fread(data + *got, 1u, want, f);
*got += n;
if (n < want) {
break;
}
sector++;
}
return *got > 0u;
}
// count Mode 1 sectors from first, their data, into out.
static void track(const char *image, long first, long count, const char *out) {
uint8_t raw[RAW_SECTOR];
FILE *in;
FILE *o;
long s;
in = fopen(image, "rb");
if (in == NULL) {
toolFail("cannot read %s", image);
}
o = fopen(out, "wb");
if (o == NULL) {
toolFail("cannot write %s", out);
}
if (first < 0 || fseek(in, first * (long)RAW_SECTOR, SEEK_SET) != 0) {
toolFail("%s has no sector %ld", image, first);
}
for (s = 0; s < count; s++) {
if (fread(raw, 1u, RAW_SECTOR, in) != RAW_SECTOR) {
toolFail("%s ends before sector %ld", image, first + count);
}
if (fwrite(raw + MODE1_DATA_AT, 1u, DATA_SECTOR, o) != DATA_SECTOR) {
toolFail("cannot write %s", out);
}
}
fclose(in);
if (fclose(o) != 0) {
toolFail("cannot write %s", out);
}
}
static void usage(void) {
fprintf(stderr, "usage: %s track IMAGE FIRST COUNT OUT\n", TOOL_NAME);
fprintf(stderr, " %s eltorito ISO OUT\n", TOOL_NAME);
exit(USAGE_EXIT);
}
int main(int argc, char **argv) {
long first;
long count;
toolFileName(TOOL_NAME);
if (argc == 6 && strcmp(argv[1], "track") == 0) {
if (!toolParseInt(argv[3], strlen(argv[3]), &first) || !toolParseInt(argv[4], strlen(argv[4]), &count)) {
toolFail("FIRST and COUNT are sector numbers");
}
track(argv[2], first, count, argv[5]);
} else if (argc == 4 && strcmp(argv[1], "eltorito") == 0) {
eltorito(argv[2], argv[3]);
} else {
usage();
}
return 0;
}