// 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 #include #include #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; }