197 lines
6.7 KiB
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;
|
|
}
|