Created
January 1, 2024 13:50
-
-
Save johngirvin/4058fa8b4442e42abe74176dc97b8f19 to your computer and use it in GitHub Desktop.
Create Amiga CD32/CTDV ISO image (Python/JSON)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"iso": { | |
"volume": "SUPERGAME", | |
"application": "Super Game", | |
"publisher": "Supersoft", | |
"preparer": "Supersoft", | |
"copyright": "(C) 2023 Supersoft" | |
}, | |
"files": [ | |
{ | |
"path": "CDTV.TM", | |
"iso": { | |
"path": "/CDTV.TM" | |
} | |
}, | |
{ | |
"path": "content/S/Startup-Sequence", | |
"iso": { | |
"path": "/S/Startup-Sequence" | |
} | |
}, | |
{ | |
"path": "content/C/RMTM", | |
"iso": { | |
"path": "/C/RMTM" | |
} | |
}, | |
{ | |
"path": "SuperGame", | |
"iso": { | |
"path": "/SUPERGAM.EXE", | |
"rr_name": "SuperGame" | |
} | |
}, | |
{ | |
"path": "DATA.BIN", | |
"iso": { | |
"path": "/DATA.BIN" | |
} | |
}, | |
{ | |
"path": "content/Disk.info", | |
"iso": { | |
"path": "/DISK.INF", | |
"rr_name": "Disk.info" | |
} | |
}, | |
{ | |
"path": "content/SuperGame.info", | |
"iso": { | |
"path": "/SUPERGAM.INF", | |
"rr_name": "SuperGame.info" | |
} | |
}, | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import json | |
import os | |
from io import BytesIO | |
import pycdlib | |
MODE_CDTV = 1 | |
MODE_CD32 = 32 | |
MODE = MODE_CDTV | |
# List of CDFS options normally set by ISOCD. | |
# Stored one byte into the Application Data field of the ISO9660 Primary Volume Descriptor. | |
# 0x4653 FS = Fast Search option enabled | |
# 0x0000 = Size of Fast Search option data | |
# 0x544d TM = TradeMark option | |
# 0x0014 = Size of TradeMark option data | |
# 0x00000000 = Size of trademark file | |
# 0x00000012 = Sector number (2048 byte sectors) of start of trademark file | |
# 0x00000000 | |
# 0x00000000 | |
# 0x00000000 | |
# NB: CD32.TM file always has zero length and offset 0x12 in the TM option field | |
# F S T M TMOSIZE TMSIZE TMSEC1 TMSEC2 TMSEC3 TMSEC4 | |
# 9 13 | |
CDFS_APPDATA = bytearray(b'\x00\x46\x53\x00\x00\x54\x4d\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') | |
CDFS_APPDATA = CDFS_APPDATA.ljust(512, b'\x00') | |
CDFS_APPDATA_TMSIZE = 9 | |
CDFS_APPDATA_TMSEC1 = 13 | |
def tolong(i, signed=False): | |
try: | |
return int(i).to_bytes(4, 'big', signed=signed) | |
except OverflowError: | |
print('tolong OverflowError', i) | |
def poke_long(l, i, v, signed=False): | |
vl = tolong(v, signed) | |
l[i + 0] = vl[0] | |
l[i + 1] = vl[1] | |
l[i + 2] = vl[2] | |
l[i + 3] = vl[3] | |
def rr_dir(file_desc_iso): | |
if 'rr_dir' in file_desc_iso: | |
return file_desc_iso['rr_dir'] | |
return os.path.basename(os.path.split(file_desc_iso['path'])[0]) | |
def rr_name(file_desc_iso): | |
if 'rr_name' in file_desc_iso: | |
return file_desc_iso['rr_name'] | |
return os.path.basename(file_desc_iso['path']) | |
# parse args | |
if len(sys.argv) != 4: | |
print('Usage: {} <workpath> <out.iso> <manifest.json> '.format(sys.argv[0])) | |
sys.exit(20) | |
FN_WORK = sys.argv[1] | |
FN_DISK = sys.argv[2].upper() | |
FN_JSON = sys.argv[3] | |
# load json disk description | |
with open(FN_JSON, 'r') as f: | |
cd_desc = json.load(f) | |
# generate iso file | |
iso = pycdlib.PyCdlib() | |
iso.new( | |
rock_ridge='1.09', | |
interchange_level=3, | |
sys_ident='CDTV', | |
copyright_file=cd_desc['iso']['copyright'], | |
vol_ident=cd_desc['iso']['volume'], | |
vol_set_ident=cd_desc['iso']['volume'], | |
set_size=1, | |
seqnum=1, | |
app_ident_str=cd_desc['iso']['application'], | |
pub_ident_str=cd_desc['iso']['publisher'], | |
preparer_ident_str=cd_desc['iso']['preparer'], | |
) | |
# add files from manifest to the image | |
iso_dirs = [] | |
for file_desc in cd_desc['files']: | |
# load file | |
with open(os.path.join(FN_WORK, file_desc['path']), 'rb') as f: | |
file_data = bytearray(f.read()) | |
# add dir first if the file has a path | |
iso_path = os.path.split(file_desc['iso']['path']) | |
iso_dir = iso_path[0] | |
if iso_dir != '/' and iso_dir not in iso_dirs: | |
iso_dirs.append(iso_dir) | |
# print(rr_dir(file_desc['iso'])) | |
iso.add_directory(iso_dir, rr_name=rr_dir(file_desc['iso'])) | |
# add file | |
iso.add_fp( | |
BytesIO(file_data), | |
len(file_data), | |
iso_path=file_desc['iso']['path'], | |
rr_name=rr_name(file_desc['iso']) | |
) | |
iso.write(FN_DISK) | |
iso.close() | |
# patch iso file with appropriate cd copyright parts | |
with open(FN_DISK, 'rb') as f: | |
iso_data = bytearray(f.read()) | |
if MODE == MODE_CDTV: | |
# CDTV | |
with open(os.path.join(FN_WORK, 'CDTV.TM'), 'rb') as f: | |
tm_data = bytearray(f.read()) | |
iso.open(FN_DISK) | |
tm_rec = iso.get_record(iso_path='/CDTV.TM') | |
tm_ofs = tm_rec.extent_location() | |
iso.close() | |
poke_long(CDFS_APPDATA, CDFS_APPDATA_TMSIZE, len(tm_data)) | |
poke_long(CDFS_APPDATA, CDFS_APPDATA_TMSEC1, tm_ofs) | |
else: | |
# CD32 | |
with open(os.path.join(FN_WORK, 'CD32.TM'), 'rb') as f: | |
tm_data = bytearray(f.read()) | |
poke_long(CDFS_APPDATA, CDFS_APPDATA_TMSIZE, 0) | |
poke_long(CDFS_APPDATA, CDFS_APPDATA_TMSEC1, 0x12) | |
iso_data[0x9000:0x9000+len(tm_data)] = tm_data | |
iso_data[0x8373:0x8373+len(CDFS_APPDATA)] = CDFS_APPDATA | |
with open(FN_DISK, 'wb') as f: | |
f.write(iso_data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment