ChocolateBird/scripts/loaders/cd/image.gd

172 lines
6.1 KiB
GDScript

extends Node
#warning-ignore-all:shadowed_variable
#warning-ignore-all:return_value_discarded
const PVD_STRING := PoolByteArray([1, 0x43, 0x44, 0x30, 0x30, 0x31]) # b'\x01CD001'
const SECTOR_SIZE_ISO := 0x800
const SECTOR_SIZE_RAW := 0x930
static func decode_filename(filename: PoolByteArray) -> String:
match filename[0]:
0:
return '.'
1:
return '..'
_:
return filename.get_string_from_ascii()
static func parse_primary_volume_descriptor(rom: File, offset: int) -> Dictionary:
var output := {}
rom.seek(offset + 8)
output['system_identifier'] = rom.get_buffer(32).get_string_from_ascii()
output['volume_identifier'] = rom.get_buffer(32).get_string_from_ascii()
rom.seek(offset + 80)
output['volume_sector_count'] = rom.get_32()
rom.seek(offset + 120)
output['volume_set_size'] = rom.get_16()
# rom.seek(offset + 124)
# output['volume_seq_number'] = rom.get_16()
rom.seek(offset + 128)
output['logical_block_size'] = rom.get_16()
rom.seek(offset + 132)
output['path_table_size'] = rom.get_32()
rom.seek(offset + 140)
output['path_table_lba'] = rom.get_32()
return output
static func find_primary_volume_descriptor(rom: File):
# Assume either ISO image with 0x800 bytes per sector, or raw .bin with 0x940 bytes per sector
for offset in [SECTOR_SIZE_ISO*16, SECTOR_SIZE_RAW*16, SECTOR_SIZE_RAW*16+24]:
rom.seek(offset)
if rom.get_buffer(6) == PVD_STRING:
return offset
print_debug('Primary Volume Descriptor not found')
return -1
static func parse_path_table(rom: File, remaining_bytes: int) -> Array:
var entry := {}
var l = rom.get_8()
entry['ea_l'] = rom.get_8()
entry['lba'] = rom.get_32()
entry['dir_num'] = rom.get_16()
entry['name'] = decode_filename(rom.get_buffer(l))
if (l % 2) > 0:
rom.get_8()
l += 1
remaining_bytes -= l + 8
if remaining_bytes > 8:
return [entry] + parse_path_table(rom, remaining_bytes)
else:
return [entry]
static func parse_directory_table(rom: File, offset: int):
var entry := {}
rom.seek(offset)
var l = rom.get_8()
if l == 0:
return null
entry['ea_l'] = rom.get_8()
entry['lba'] = rom.get_32()
rom.seek(offset+10)
entry['data_length'] = rom.get_32()
rom.seek(offset + 25)
entry['flags'] = rom.get_8()
entry['interleaved_size'] = rom.get_8()
entry['interleaved_gap'] = rom.get_8()
rom.seek(offset + 32)
var filename_length := rom.get_8()
entry['name'] = decode_filename(rom.get_buffer(filename_length))
if (l % 2) > 0:
l += 1
var tail = parse_directory_table(rom, offset + l)
if tail != null:
return [entry] + tail
else:
return [entry]
static func read_sector(rom: File, sector: int, pvd_offset: int, sector_size: int = SECTOR_SIZE_ISO, amount: int = SECTOR_SIZE_ISO) -> PoolByteArray:
var d_sector = sector - 16
rom.seek(pvd_offset + (d_sector*sector_size))
return rom.read_buffer(amount)
# Non-static
var directory: Dictionary
var pvd_offset: int
var pvd: Dictionary
var rom: File
var sector_count: int
var sector_size: int # The sector size of the File. The actual data will always be in 0x800 byte sectors, but raw image files will have 0x18 bytes of header and 0x118 bytes of footer in every sector
func lba2offset(lba: int) -> int:
# Our offset seek strategy is to start from the PVD (guaranteed to be the start of the data region of Sector 16)
return self.pvd_offset + ((lba-16) * self.sector_size)
func lba2bytes(lba: int, amount: int = SECTOR_SIZE_ISO) -> PoolByteArray:
self.rom.seek(self.lba2offset(lba))
return self.rom.get_buffer(amount)
func get_file(filename: String) -> PoolByteArray:
# Note that actual files end in ';1'
# On a multi-session CD there may be subsequent versions like ';2' but that is not of interest to this program
# Remember to ask for your filename with ';1' on the end
if not (filename in directory):
print_debug('No such file in directory: "%s"' % filename)
return PoolByteArray()
if not filename.ends_with(';1'):
print_debug('Filename must end with ";1", is this a directory? "%s"' % filename)
return PoolByteArray()
var f = directory[filename]
if (typeof(f) != typeof([])) or len(f) < 2:
print_debug('Directory entry does not have a filesize: "%s"' % filename)
return PoolByteArray()
var lba_start: int = f[0]
var size: int = f[1]
if self.sector_size == SECTOR_SIZE_ISO:
# There are no sector headers or footers interspersed, so we can just read the whole thing at once
return self.lba2bytes(lba_start, size)
else:
# This reads in one sector at a time to be compatible with RAW images, which have sector headers and footers interspersed
var buffer := StreamPeerBuffer.new()
var lba := lba_start # Extraneous vars for debugging purposes
var bytes_remaining := size # ^
while bytes_remaining > 0:
var amount = bytes_remaining
if bytes_remaining > SECTOR_SIZE_ISO:
amount = SECTOR_SIZE_ISO
buffer.put_data(self.lba2bytes(lba, amount))
bytes_remaining -= amount
lba += 1
return buffer.data_array
func _init(rom: File):
# super()
self.rom = rom
var rom_len := self.rom.get_len()
self.pvd_offset = find_primary_volume_descriptor(self.rom)
if self.pvd_offset < 0:
print_debug('Primary Volume Descriptor not found in ROM, aborting')
return
self.pvd = parse_primary_volume_descriptor(self.rom, self.pvd_offset)
# Determine the sector size of this File
self.sector_count = self.pvd.volume_sector_count
# Use >= comparisons to allow for preamble in the image file
var f := rom_len/float(self.sector_count)
if f >= SECTOR_SIZE_RAW:
self.sector_size = SECTOR_SIZE_RAW
elif f >= SECTOR_SIZE_ISO:
self.sector_size = SECTOR_SIZE_ISO
else:
print_debug('Filesize divided by Sector Count is too low for known formats, the image file may be truncated or lying about sector count')
return
self.rom.seek(self.lba2offset(self.pvd.path_table_lba))
var path_table := parse_path_table(self.rom, self.pvd.path_table_size)
for path_entry in path_table:
var path_name: String = '%s/' % path_entry.name
self.directory[path_name] = [path_entry.lba]
var dir_table: Array = parse_directory_table(self.rom, self.lba2offset(path_entry.lba))
for dir_entry in dir_table:
var filename := '%s%s' % [path_name, dir_entry.name]
self.directory[filename] = [dir_entry.lba, dir_entry.data_length]