172 lines
6.1 KiB
GDScript
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]
|