iso.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. #!/usr/bin/env python3
  2. """
  3. Tool for creating ISO 9660 CD images.
  4. """
  5. import array
  6. import struct
  7. class Structure(object):
  8. assert_size = -1
  9. def __init__(self):
  10. self.data = {}
  11. for field in self.fields:
  12. if len(field) > 2:
  13. f, s, d = field
  14. self.data[s] = d
  15. else:
  16. f, s = field
  17. if f.endswith('s'):
  18. self.data[s] = b""
  19. else:
  20. self.data[s] = 0
  21. if self.assert_size != -1:
  22. assert(len(self) == self.assert_size)
  23. def __len__(self):
  24. return sum([struct.calcsize(f[0]) for f in self.fields])
  25. def read(self, data, offset):
  26. def read_struct(fmt,buf,offset):
  27. out, = struct.unpack_from(fmt,buf,offset)
  28. return out, offset + struct.calcsize(fmt)
  29. o = offset
  30. for field in self.fields:
  31. if len(field) > 2:
  32. f, s, _ = field
  33. else:
  34. f, s = field
  35. self.data[s], o = read_struct(f, data, o)
  36. return o
  37. def write(self, data, offset):
  38. def write_struct(fmt, buf, offset, value):
  39. struct.pack_into(fmt, buf, offset, value)
  40. return offset + struct.calcsize(fmt)
  41. o = offset
  42. for field in self.fields:
  43. if len(field) > 2:
  44. f, s, _ = field
  45. else:
  46. f, s = field
  47. o = write_struct(f,data,o,self.data[s])
  48. return o
  49. def read_struct(fmt,buf,offset):
  50. out, = struct.unpack_from(fmt,buf,offset)
  51. return out, offset + struct.calcsize(fmt)
  52. class FAT(object):
  53. def __init__(self, iso, offset):
  54. self.iso = iso
  55. self.offset = offset
  56. self.bytespersector, _ = read_struct('H', self.iso.data, offset + 11)
  57. self.sectorspercluster, _ = read_struct('B', self.iso.data, offset + 13)
  58. self.reservedsectors, _ = read_struct('H', self.iso.data, offset + 14)
  59. self.numberoffats, _ = read_struct('B', self.iso.data, offset + 16)
  60. self.numberofdirs, _ = read_struct('H', self.iso.data, offset + 17)
  61. self.fatsize, _ = read_struct('H', self.iso.data, offset + 22)
  62. self.root_dir_sectors = (self.numberofdirs * 32 + (self.bytespersector - 1)) // self.bytespersector
  63. self.first_data_sector = self.reservedsectors + (self.numberoffats * self.fatsize) + self.root_dir_sectors
  64. self.root_sector= self.first_data_sector - self.root_dir_sectors
  65. self.root = FATDirectory(self, self.offset + self.root_sector * self.bytespersector)
  66. def get_offset(self, cluster):
  67. return self.offset + ((cluster - 2) * self.sectorspercluster + self.first_data_sector) * self.bytespersector
  68. def get_file(self, path):
  69. units = path.split('/')
  70. units = units[1:]
  71. me = self.root
  72. out = None
  73. for i in units:
  74. for fatfile in me.list():
  75. if fatfile.readable_name() == i:
  76. me = fatfile.to_dir()
  77. out = fatfile
  78. break
  79. else:
  80. return None
  81. return out
  82. class FATDirectory(object):
  83. def __init__(self, fat, offset):
  84. self.fat = fat
  85. self.offset = offset
  86. def list(self):
  87. o = self.offset
  88. while 1:
  89. out = FATFile(self.fat, o)
  90. if out.name != '\0\0\0\0\0\0\0\0':
  91. yield out
  92. else:
  93. break
  94. o += out.size
  95. class FATFile(object):
  96. def __init__(self, fat, offset):
  97. self.fat = fat
  98. self.offset = offset
  99. self.magic_long = None
  100. self.size = 0
  101. self.long_name = ''
  102. o = self.offset
  103. self.actual_offset = o
  104. self.attrib, _ = read_struct('B',self.fat.iso.data,o+11)
  105. while (self.attrib & 0x0F) == 0x0F:
  106. # Long file name entry
  107. tmp = read_struct('10s',self.fat.iso.data,o+1)[0]
  108. tmp += read_struct('12s',self.fat.iso.data,o+14)[0]
  109. tmp += read_struct('4s',self.fat.iso.data,o+28)[0]
  110. tmp = "".join([chr(x) for x in tmp[::2] if x != '\xFF']).strip('\x00')
  111. self.long_name = tmp + self.long_name
  112. self.size += 32
  113. o = self.offset + self.size
  114. self.actual_offset = o
  115. self.attrib, _ = read_struct('B',self.fat.iso.data,o+11)
  116. o = self.offset + self.size
  117. self.name, o = read_struct('8s',self.fat.iso.data,o)
  118. self.ext, o = read_struct('3s',self.fat.iso.data,o)
  119. self.attrib, o = read_struct('B',self.fat.iso.data,o)
  120. self.userattrib, o = read_struct('B',self.fat.iso.data,o)
  121. self.undelete, o = read_struct('b',self.fat.iso.data,o)
  122. self.createtime, o = read_struct('H',self.fat.iso.data,o)
  123. self.createdate, o = read_struct('H',self.fat.iso.data,o)
  124. self.accessdate, o = read_struct('H',self.fat.iso.data,o)
  125. self.clusterhi, o = read_struct('H',self.fat.iso.data,o)
  126. self.modifiedti, o = read_struct('H',self.fat.iso.data,o)
  127. self.modifiedda, o = read_struct('H',self.fat.iso.data,o)
  128. self.clusterlow, o = read_struct('H',self.fat.iso.data,o)
  129. self.filesize, o = read_struct('I',self.fat.iso.data,o)
  130. self.name = self.name.decode('ascii')
  131. self.ext = self.ext.decode('ascii')
  132. self.size += 32
  133. self.cluster = (self.clusterhi << 16) + self.clusterlow
  134. def is_dir(self):
  135. return bool(self.attrib & 0x10)
  136. def is_long(self):
  137. return bool((self.attrib & 0x0F) == 0x0F)
  138. def to_dir(self):
  139. return FATDirectory(self.fat, self.fat.get_offset(self.cluster))
  140. def get_offset(self):
  141. return self.fat.get_offset(self.cluster)
  142. def readable_name(self):
  143. if self.long_name:
  144. return self.long_name
  145. if self.ext.strip():
  146. return (self.name.strip() + '.' + self.ext.strip()).lower()
  147. else:
  148. return self.name.strip().lower()
  149. def make_time():
  150. data = array.array('b',b'\0'*17)
  151. struct.pack_into(
  152. '4s2s2s2s2s2s2sb',
  153. data, 0,
  154. b'2018', b'11', b'14', # Year, Month, Day
  155. b'12', b'00', b'00', # Hour, Minute, Second
  156. b'00', # Hundreths
  157. 0, # Offset
  158. )
  159. return bytes(data)
  160. def make_date():
  161. data = array.array('b',b'\0'*7)
  162. struct.pack_into(
  163. 'BBBBBBb',
  164. data, 0,
  165. 118, 11, 14,
  166. 12, 0, 0,
  167. 0,
  168. )
  169. return bytes(data)
  170. class ISOBootRecord(Structure):
  171. assert_size = 2048
  172. fields = (
  173. ('B', 'type_code', 0),
  174. ('5s', 'cd001', b'CD001'),
  175. ('B', 'version', 1),
  176. ('32s', 'boot_system_identifier'),
  177. ('32s', 'boot_identifier'),
  178. ('1977s', 'boot_record_data'),
  179. )
  180. class ISOElToritoBootRecord(ISOBootRecord):
  181. assert_size = 2048
  182. fields = (
  183. ('B', 'type_code', 0),
  184. ('5s', 'cd001', b'CD001'),
  185. ('B', 'version', 1),
  186. ('32s', 'boot_system_identifier',b'EL TORITO SPECIFICATION'),
  187. ('32s', 'boot_identifier'),
  188. ('<I', 'catalog_lba'),
  189. ('1973s', 'boot_record_data'),
  190. )
  191. def set_catalog(self, catalog_lba):
  192. self.data['catalog_lba'] = catalog_lba
  193. class ISOPrimaryVolumeDescriptor(Structure):
  194. assert_size = 2048
  195. fields = (
  196. ('B', 'type_code', 1),
  197. ('5s', 'cd001', b'CD001'),
  198. ('B', 'version', 1),
  199. ('B', 'unused_0', 0),
  200. ('32s', 'system_id', b' '*32),
  201. ('32s', 'volume_id', b'ToaruOS Boot CD'.ljust(32)),
  202. ('8s', 'unused_1', b'\0'*8),
  203. ('<I', 'volume_space_lsb'),
  204. ('>I', 'volume_space_msb'),
  205. ('32s', 'unused_2', b'\0'*32),
  206. ('<H', 'volume_set_size_lsb', 1),
  207. ('>H', 'volume_set_size_msb', 1),
  208. ('<H', 'volume_sequence_lsb', 1),
  209. ('>H', 'volume_sequence_msb', 1),
  210. ('<H', 'logical_block_size_lsb', 2048),
  211. ('>H', 'logical_block_size_msb', 2048),
  212. ('<I', 'path_table_size_lsb'),
  213. ('>I', 'path_table_size_msb'),
  214. ('<I', 'type_l_table_lsb'),
  215. ('<I', 'optional_type_l_table_lsb'),
  216. ('>I', 'type_m_table_msb'),
  217. ('>I', 'optional_type_m_table_msb'),
  218. ('34s', 'root_entry_data'),
  219. ('128s', 'volume_set_identifier', b' '*128),
  220. ('128s', 'publisher_identifier', b' '*128),
  221. ('128s', 'data_preparer_identifier', b' '*128),
  222. ('128s', 'application_identifier',b' '*128),
  223. ('38s', 'copyright_file_identifier',b' '*38),
  224. ('36s', 'abstract_file_identifier',b' '*36),
  225. ('37s', 'bibliographic_file_identifier',b' '*37),
  226. ('17s', 'volume_creation_time',make_time()),
  227. ('17s', 'volume_modification_time',make_time()),
  228. ('17s', 'volume_expiration_time',make_time()),
  229. ('17s', 'volume_effective_time',make_time()),
  230. ('B', 'file_structure_version'),
  231. ('B', 'unused_3', 0),
  232. ('512s', 'application_data'),
  233. ('653s', 'reserved', b'\0'*653),
  234. )
  235. class ISOVolumeDescriptorSetTerminator(Structure):
  236. assert_size = 2048
  237. fields = (
  238. ('B', 'type_code', 0xFF),
  239. ('5s', 'cd001', b'CD001'),
  240. ('B', 'version', 1),
  241. ('2041s', 'unused', b'\0'*2041)
  242. )
  243. class ISODirectoryEntry(Structure):
  244. assert_size = 33
  245. fields = (
  246. ('B', 'length'),
  247. ('B', 'ext_length'),
  248. ('<I', 'extent_start_lsb'),
  249. ('>I', 'extent_start_msb'),
  250. ('<I', 'extent_length_lsb'),
  251. ('>I', 'extent_length_msb'),
  252. ('7s', 'record_date', make_date()),
  253. ('B', 'flags'),
  254. ('B', 'interleave_units'),
  255. ('B', 'interleave_gap'),
  256. ('<H', 'volume_seq_lsb'),
  257. ('>H', 'volume_seq_msb'),
  258. ('B', 'name_len'),
  259. )
  260. def set_name(self, name):
  261. self.data['name_len'] = len(name)
  262. self.name = name
  263. self.data['length'] = self.assert_size + len(self.name)
  264. if self.data['length'] % 2:
  265. self.data['length'] += 1
  266. def set_extent(self, start, length):
  267. self.data['extent_start_lsb'] = start
  268. self.data['extent_start_msb'] = start
  269. self.data['extent_length_lsb'] = length
  270. self.data['extent_length_msb'] = length
  271. def write(self, data, offset):
  272. o = super(ISODirectoryEntry,self).write(data,offset)
  273. struct.pack_into(str(len(self.name))+'s', data, o, self.name.encode('utf-8'))
  274. return offset + self.data['length']
  275. class ArbitraryData(object):
  276. def __init__(self, path=None, size=None):
  277. if path:
  278. with open(path,'rb') as f:
  279. tmp = f.read()
  280. self.data = array.array('b',tmp)
  281. elif size:
  282. self.data = array.array('b',b'\0'*size)
  283. else:
  284. raise ValueError("Expected one of path or size to be set.")
  285. self.size = len(self.data.tobytes())
  286. self.actual_size = self.size
  287. while (self.size % 2048):
  288. self.size += 1
  289. def write(self, data, offset):
  290. struct.pack_into(str(self.size) + 's', data, offset, self.data.tobytes())
  291. return offset + self.size
  292. def make_entry():
  293. return b'\0'*34
  294. class ISO9660(object):
  295. def __init__(self, from_file=None):
  296. self.primary_volume_descriptor = ISOPrimaryVolumeDescriptor()
  297. self.boot_record = ISOElToritoBootRecord()
  298. self.volume_descriptor_set_terminator = ISOVolumeDescriptorSetTerminator()
  299. self.el_torito_catalog = ElToritoCatalog()
  300. self.allocate = 0x13
  301. if from_file:
  302. # Only for a file we produced.
  303. with open(from_file, 'rb') as f:
  304. tmp = f.read()
  305. data = array.array('b', tmp)
  306. self.primary_volume_descriptor.read(data, 0x10 * 2048)
  307. self.boot_record.read(data, 0x11 * 2048)
  308. self.volume_descriptor_set_terminator.read(data, 0x12 * 2048)
  309. self.el_torito_catalog.read(data, self.boot_record.data['catalog_lba'] * 2048)
  310. else:
  311. # Root directory
  312. self.root = ISODirectoryEntry()
  313. self.root.data['flags'] = 0x02 # Directory
  314. self.root.set_name(' ')
  315. self.root_data = ArbitraryData(size=2048)
  316. self.root_data.sector_offset = self.allocate_space(1)
  317. self.root.set_extent(self.root_data.sector_offset,self.root_data.size)
  318. # Dummy entries
  319. t = ISODirectoryEntry()
  320. t.set_name('')
  321. o = t.write(self.root_data.data, 0)
  322. t = ISODirectoryEntry()
  323. t.set_name('\1')
  324. o = t.write(self.root_data.data, o)
  325. # Kernel
  326. self.kernel_data = ArbitraryData(path='fatbase/kernel')
  327. self.kernel_data.sector_offset = self.allocate_space(self.kernel_data.size // 2048)
  328. self.kernel_entry = ISODirectoryEntry()
  329. self.kernel_entry.set_name('KERNEL.')
  330. self.kernel_entry.set_extent(self.kernel_data.sector_offset, self.kernel_data.actual_size)
  331. o = self.kernel_entry.write(self.root_data.data, o)
  332. # Ramdisk
  333. self.ramdisk_data = ArbitraryData(path='fatbase/ramdisk.img')
  334. self.ramdisk_data.sector_offset = self.allocate_space(self.ramdisk_data.size // 2048)
  335. self.ramdisk_entry = ISODirectoryEntry()
  336. self.ramdisk_entry.set_name('RAMDISK.IMG')
  337. self.ramdisk_entry.set_extent(self.ramdisk_data.sector_offset, self.ramdisk_data.actual_size)
  338. o = self.ramdisk_entry.write(self.root_data.data, o)
  339. # Modules directory
  340. self.mods_data = ArbitraryData(size=(2048*2)) # Just in case
  341. self.mods_data.sector_offset = self.allocate_space(self.mods_data.size // 2048)
  342. self.mods_entry = ISODirectoryEntry()
  343. self.mods_entry.data['flags'] = 0x02
  344. self.mods_entry.set_name('MOD')
  345. self.mods_entry.set_extent(self.mods_data.sector_offset, self.mods_data.actual_size)
  346. o = self.mods_entry.write(self.root_data.data, o)
  347. self.payloads = []
  348. # Modules themselves
  349. t = ISODirectoryEntry()
  350. t.set_name('')
  351. o = t.write(self.mods_data.data, 0)
  352. t = ISODirectoryEntry()
  353. t.set_name('\1')
  354. o = t.write(self.mods_data.data, o)
  355. for mod_file in [
  356. 'fatbase/mod/ac97.ko',
  357. 'fatbase/mod/ata.ko',
  358. 'fatbase/mod/ataold.ko',
  359. 'fatbase/mod/debug_sh.ko',
  360. 'fatbase/mod/dospart.ko',
  361. 'fatbase/mod/e1000.ko',
  362. 'fatbase/mod/ext2.ko',
  363. 'fatbase/mod/hda.ko',
  364. 'fatbase/mod/iso9660.ko',
  365. 'fatbase/mod/lfbvideo.ko',
  366. 'fatbase/mod/net.ko',
  367. 'fatbase/mod/packetfs.ko',
  368. 'fatbase/mod/pcnet.ko',
  369. 'fatbase/mod/pcspkr.ko',
  370. 'fatbase/mod/portio.ko',
  371. 'fatbase/mod/procfs.ko',
  372. 'fatbase/mod/ps2kbd.ko',
  373. 'fatbase/mod/ps2mouse.ko',
  374. 'fatbase/mod/random.ko',
  375. 'fatbase/mod/rtl.ko',
  376. 'fatbase/mod/serial.ko',
  377. 'fatbase/mod/snd.ko',
  378. 'fatbase/mod/tmpfs.ko',
  379. 'fatbase/mod/usbuhci.ko',
  380. 'fatbase/mod/vbox.ko',
  381. 'fatbase/mod/vgadbg.ko',
  382. 'fatbase/mod/vgalog.ko',
  383. 'fatbase/mod/vidset.ko',
  384. 'fatbase/mod/vmware.ko',
  385. 'fatbase/mod/xtest.ko',
  386. 'fatbase/mod/zero.ko',
  387. 'fatbase/mod/tarfs.ko',
  388. ]:
  389. payload = ArbitraryData(path=mod_file)
  390. payload.sector_offset = self.allocate_space(payload.size // 2048)
  391. entry = ISODirectoryEntry()
  392. entry.set_name(mod_file.replace('fatbase/mod/','').upper())
  393. entry.set_extent(payload.sector_offset, payload.actual_size)
  394. o = entry.write(self.mods_data.data, o)
  395. self.payloads.append(payload)
  396. # Set up the boot catalog and records
  397. self.el_torito_catalog.sector_offset = self.allocate_space(1)
  398. self.boot_record.set_catalog(self.el_torito_catalog.sector_offset)
  399. self.boot_payload = ArbitraryData(path='cdrom/boot.sys')
  400. self.boot_payload.sector_offset = self.allocate_space(self.boot_payload.size // 2048)
  401. self.el_torito_catalog.initial_entry.data['sector_count'] = self.boot_payload.size // 512
  402. self.el_torito_catalog.initial_entry.data['load_rba'] = self.boot_payload.sector_offset
  403. #self.el_torito_catalog.section.data['sector_count'] = 0 # Expected to be 0 or 1 for "until end of CD"
  404. #self.el_torito_catalog.section.data['load_rba'] = self.fat_payload.sector_offset
  405. self.primary_volume_descriptor.data['root_entry_data'] = make_entry()
  406. def allocate_space(self, sectors):
  407. out = self.allocate
  408. self.allocate += sectors
  409. return out
  410. def write(self, file_name):
  411. with open(file_name, 'wb') as f:
  412. data = array.array('b',b'\0'*(2048*self.allocate))
  413. self.primary_volume_descriptor.write(data,0x10 * 2048)
  414. self.root.write(data,0x10*2048 + 156)
  415. self.boot_record.write(data,0x11 * 2048)
  416. self.mods_data.write(data, self.mods_data.sector_offset * 2048)
  417. self.root_data.write(data,self.root_data.sector_offset * 2048)
  418. self.volume_descriptor_set_terminator.write(data,0x12 * 2048)
  419. self.el_torito_catalog.write(data,self.el_torito_catalog.sector_offset * 2048)
  420. self.boot_payload.write(data,self.boot_payload.sector_offset * 2048)
  421. self.kernel_data.write(data,self.kernel_data.sector_offset * 2048)
  422. self.ramdisk_data.write(data,self.ramdisk_data.sector_offset * 2048)
  423. #self.fat_payload.write(data,self.fat_payload.sector_offset * 2048)
  424. for payload in self.payloads:
  425. payload.write(data,payload.sector_offset * 2048)
  426. data.tofile(f)
  427. class ElToritoValidationEntry(Structure):
  428. assert_size = 0x20
  429. fields = (
  430. ('B','header_id',1),
  431. ('B','platform_id',0),
  432. ('<H','reserved_0'),
  433. ('24s','id_str',b'\0'*24),
  434. ('<H','checksum',0x55aa),
  435. ('B','key_55',0x55),
  436. ('B','key_aa',0xaa),
  437. )
  438. class ElToritoInitialEntry(Structure):
  439. assert_size = 0x20
  440. fields = (
  441. ('B','bootable',0x88),
  442. ('B','media_type'),
  443. ('<H','load_segment'),
  444. ('B','system_type'),
  445. ('B','unused_0'),
  446. ('<H','sector_count'),
  447. ('<I','load_rba'),
  448. ('20s','unused_1',b'\0'*20),
  449. )
  450. class ElToritoSectionHeader(Structure):
  451. assert_size = 0x20
  452. fields = (
  453. ('B','header_id',0x91),
  454. ('B','platform_id',0xEF),
  455. ('<H','sections',1),
  456. ('28s','id_str',b'\0'*28)
  457. )
  458. class ElToritoSectionEntry(Structure):
  459. assert_size = 0x20
  460. fields = (
  461. ('B','bootable',0x88),
  462. ('B','media_type'),
  463. ('<H','load_segment'),
  464. ('B','system_type'),
  465. ('B','unused_0'),
  466. ('<H','sector_count'),
  467. ('<I','load_rba'),
  468. ('B','selection_criteria'),
  469. ('19s','vendor'),
  470. )
  471. class ElToritoCatalog(object):
  472. def __init__(self):
  473. self.validation_entry = ElToritoValidationEntry()
  474. self.initial_entry = ElToritoInitialEntry()
  475. self.section_header = ElToritoSectionHeader()
  476. self.section = ElToritoSectionEntry()
  477. def read(self, data, offset):
  478. o = offset
  479. o = self.validation_entry.read(data, o)
  480. o = self.initial_entry.read(data, o)
  481. o = self.section_header.read(data, o)
  482. o = self.section.read(data, o)
  483. def write(self, data, offset):
  484. o = offset
  485. o = self.validation_entry.write(data, o)
  486. o = self.initial_entry.write(data, o)
  487. o = self.section_header.write(data, o)
  488. o = self.section.write(data, o)
  489. iso = ISO9660()
  490. #print(iso.el_torito_catalog.validation_entry.data)
  491. #print(iso.el_torito_catalog.initial_entry.data)
  492. #print(iso.el_torito_catalog.section_header.data)
  493. #print(iso.el_torito_catalog.section.data)
  494. iso.write('test.iso')