Skip to content

Instantly share code, notes, and snippets.

@johngirvin
Created January 1, 2024 13:50
Show Gist options
  • Save johngirvin/4058fa8b4442e42abe74176dc97b8f19 to your computer and use it in GitHub Desktop.
Save johngirvin/4058fa8b4442e42abe74176dc97b8f19 to your computer and use it in GitHub Desktop.
Create Amiga CD32/CTDV ISO image (Python/JSON)
{
"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"
}
},
]
}
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