joeylib2/tools/rawCd.py
2026-10-13 14:01:46 -05:00

88 lines
3.2 KiB
Python

#!/usr/bin/env python3
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
"""rawCd.py - read data out of a raw CD image (2352-byte sectors, as CloneCD's
.img and a .bin keep them).
rawCd.py 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.py 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.
"""
import struct
import sys
RAW_SECTOR = 2352
RAW_SYNC = b"\x00" + b"\xff" * 10 + b"\x00" # how every raw sector starts
MODE1_DATA_AT = 16 # 12 sync bytes, 4 header bytes
DATA_SECTOR = 2048
BOOT_RECORD_SECTOR = 17 # ISO 9660's El Torito boot record volume descriptor
CATALOG_POINTER_AT = 0x47 # in the boot record: the boot catalog's sector
DEFAULT_ENTRY_AT = 32 # in the catalog: after the validation entry
BOOTABLE = 0x88
MEDIA_FLOPPY_144 = 2
LOAD_RBA_AT = 8
FLOPPY_144_BYTES = 1474560
def eltorito(iso, out):
with open(iso, "rb") as f:
raw = f.read(len(RAW_SYNC)) == RAW_SYNC
record = readData(f, raw, BOOT_RECORD_SECTOR, DATA_SECTOR)
if record[1:6] != b"CD001" or b"EL TORITO" not in record[7:39]:
sys.exit("rawCd: %s has no El Torito boot record" % iso)
catalog = struct.unpack_from("<I", record, CATALOG_POINTER_AT)[0]
entry = readData(f, raw, catalog, DATA_SECTOR)[DEFAULT_ENTRY_AT:DEFAULT_ENTRY_AT + 32]
if entry[0] != BOOTABLE or entry[1] != MEDIA_FLOPPY_144:
sys.exit("rawCd: %s does not boot a 1.44 MB floppy image" % iso)
image = readData(f, raw, struct.unpack_from("<I", entry, LOAD_RBA_AT)[0], FLOPPY_144_BYTES)
with open(out, "wb") as o:
o.write(image)
# readData F RAW SECTOR BYTES -- BYTES of data from SECTOR on: the data
# parts of raw sectors, or a plain image's bytes.
def readData(f, raw, sector, count):
if not raw:
f.seek(sector * DATA_SECTOR)
return f.read(count)
data = b""
while len(data) < count:
f.seek(sector * RAW_SECTOR + MODE1_DATA_AT)
data += f.read(DATA_SECTOR)
sector += 1
return data[:count]
def track(image, first, count, out):
with open(image, "rb") as f, open(out, "wb") as o:
f.seek(first * RAW_SECTOR)
for _ in range(count):
raw = f.read(RAW_SECTOR)
if len(raw) != RAW_SECTOR:
sys.exit("rawCd: %s ends before sector %d" % (image, first + count))
o.write(raw[MODE1_DATA_AT:MODE1_DATA_AT + DATA_SECTOR])
def main(argv):
if len(argv) == 6 and argv[1] == "track":
track(argv[2], int(argv[3]), int(argv[4]), argv[5])
elif len(argv) == 4 and argv[1] == "eltorito":
eltorito(argv[2], argv[3])
else:
sys.stderr.write(__doc__)
return 2
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))