88 lines
3.2 KiB
Python
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))
|