175 lines
7.0 KiB
GDScript
175 lines
7.0 KiB
GDScript
extends Node
|
|
#warning-ignore-all:return_value_discarded
|
|
signal loading_stage_updated(stage) # String
|
|
signal rom_loaded
|
|
|
|
var THREADED_LOADING := OS.can_use_threads()
|
|
|
|
const STRUCT := preload('res://scripts/struct.gd')
|
|
const STRUCT_SNES := preload('res://scripts/loaders/snes/structs.gd')
|
|
var structdefs := {}
|
|
const loader_cd_image := preload('res://scripts/loaders/cd/image.gd')
|
|
var psx_productcode_regex := RegEx.new()
|
|
const psx_ff5_productcodes = [
|
|
'SLUS_008.79', # US Anthology, both 1.0 and 1.1
|
|
'SCES_138.40', # EU/Aus Anthology
|
|
'SLPM_860.81', # JP
|
|
'SCPS_452.14', # JP original, untested
|
|
]
|
|
|
|
var rom_snes := File.new()
|
|
var snes_data := {}
|
|
var snes_bytes: PoolByteArray
|
|
var snes_buffer: StreamPeerBuffer
|
|
var threads: Array # Thread
|
|
|
|
func load_snes_structs(buffer: StreamPeerBuffer) -> Dictionary:
|
|
var data := {}
|
|
var rom_size := len(buffer.data_array)
|
|
for key in Common.SNES_PSX_addresses:
|
|
var d = Common.SNES_PSX_addresses[key]
|
|
if d.format and (d.SNES < rom_size): # Don't try to grab RPGe bank E0-E7 stuff from a smaller JP ROM
|
|
var s: STRUCT.StructType
|
|
if d.format in structdefs:
|
|
s = structdefs[d.format]
|
|
else:
|
|
s = STRUCT.get_structarraytype(d.format, structdefs)
|
|
structdefs[d.format] = s
|
|
if not s:
|
|
assert(false, 'Invalid StructType: "%s"' % d.format)
|
|
buffer.seek(d.SNES)
|
|
if '.' in key:
|
|
var keysplit: PoolStringArray = key.split('.', 1)
|
|
var k0 := keysplit[0]
|
|
var k1 = keysplit[1]
|
|
if k1.is_valid_integer():
|
|
k1 = int(k1)
|
|
if not (k0 in data):
|
|
data[k0] = {k1: s.get_value(buffer, [0, 0])}
|
|
else:
|
|
data[k0][k1] = s.get_value(buffer, [0, 0])
|
|
else:
|
|
data[key] = s.get_value(buffer, [0, 0])
|
|
data['job_levels'] = []
|
|
for job_id in 21:
|
|
var ability_list_ptr: int = data.ptrs_job_ability_lists[job_id]
|
|
var num_abilities: int = data.job_ability_counts[job_id]
|
|
var ability_list := []
|
|
buffer.seek(0x110000 + ability_list_ptr)
|
|
for i in num_abilities:
|
|
var abp_requirement: int = buffer.get_u16()
|
|
var ability_learned: int = buffer.get_u8()
|
|
ability_list.append({'ABP': abp_requirement, 'ability': ability_learned})
|
|
data.job_levels.append(ability_list)
|
|
return data
|
|
|
|
var load_start_tick := 0
|
|
func _on_loader_loading_stage_updated(stage: String, loader: String, threaded: bool = false) -> void:
|
|
var output := '%s: %s' % [loader, stage]
|
|
if threaded:
|
|
output += ' (background thread)'
|
|
#print(Time.get_time_string_from_system() + ' ' + output)
|
|
print('@%dms - %s' % [Time.get_ticks_msec() - load_start_tick, output])
|
|
emit_signal('loading_stage_updated', output)
|
|
var scenetree := get_tree()
|
|
if scenetree:
|
|
yield(scenetree, 'idle_frame')
|
|
|
|
func _unpack_threaded_args(obj_method_args: Array) -> void:
|
|
# Thread.start() only passes one argument, totally insane API deficiency
|
|
var obj: Object = obj_method_args[0]
|
|
var method: String = obj_method_args[1]
|
|
var args: Array = obj_method_args[2]
|
|
obj.callv(method, args)
|
|
|
|
func _load_stage(stage: String, loader: String, obj: Object, method: String, args: Array, threaded_loading: bool = false) -> void:
|
|
yield(_on_loader_loading_stage_updated(stage, loader, threaded_loading), 'completed')
|
|
if threaded_loading:
|
|
var thread := Thread.new()
|
|
match thread.start(self, '_unpack_threaded_args', [obj, method, args]):
|
|
ERR_CANT_CREATE:
|
|
print_debug('Failed to create thread for loading %s: %s'%[loader, stage])
|
|
OS.quit(1)
|
|
OK:
|
|
self.threads.append(thread)
|
|
else:
|
|
obj.callv(method, args)
|
|
|
|
func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void:
|
|
load_start_tick = Time.get_ticks_msec()
|
|
if (len(bytes) % 1024) == 512: # Naive header strip
|
|
bytes = bytes.subarray(512, -1)
|
|
self.snes_bytes = bytes
|
|
self.snes_buffer = StreamPeerBuffer.new()
|
|
self.snes_buffer.data_array = bytes
|
|
|
|
# Thread anything we don't need structs loaded for
|
|
# Give threads their own buffer, avoid file pointer conflicts
|
|
self._load_stage('Loading strings', 'StringLoader', StringLoader, 'load_snes_rom', [self.snes_buffer.duplicate()], THREADED_LOADING)
|
|
|
|
# Most loaders depend on Structs being loaded, this is a bottleneck
|
|
yield(_on_loader_loading_stage_updated('Loading struct definitions', 'StructLoader'), 'completed')
|
|
self.snes_data = load_snes_structs(self.snes_buffer.duplicate())
|
|
|
|
self._load_stage('Loading sounds', 'SoundLoader', SoundLoader, 'parse_rom', [self.snes_data, self.snes_buffer.duplicate()], THREADED_LOADING)
|
|
self._load_stage('Parsing music data', 'MusicManager', MusicManager, 'load_snes_rom', [self.snes_buffer.duplicate()], THREADED_LOADING)
|
|
|
|
|
|
SpriteLoader.reset()
|
|
self._load_stage('Loading sprites', 'SpriteLoader', SpriteLoader, 'load_from_structs', [self.snes_data])
|
|
self._load_stage('Loading enemy battle sprites', 'SpriteLoader', SpriteLoader, 'load_enemy_battle_sprites', [self.snes_data, self.snes_buffer])
|
|
self._load_stage('Loading battle backgrounds', 'SpriteLoader', SpriteLoader, 'load_battle_bgs', [self.snes_data, self.snes_buffer])
|
|
|
|
self._load_stage('Loading map data', 'MapLoader', MapLoader, 'load_snes_rom', [self.snes_data, self.snes_buffer])
|
|
|
|
for thread in self.threads:
|
|
thread.wait_to_finish()
|
|
self.threads.clear()
|
|
yield(_on_loader_loading_stage_updated('Finished loading!', 'RomLoader'), 'completed')
|
|
emit_signal('rom_loaded')
|
|
# var music = load('res://scripts/loaders/snes/music_ff5.gd').new()
|
|
# music.disassemble_sfx(self.snes_buffer)
|
|
|
|
func load_snes_rom(filename: String) -> void:
|
|
var error := rom_snes.open(filename, File.READ)
|
|
if error == OK:
|
|
# Copy entire SNES ROM to a buffer for StreamPeerBuffer usage.
|
|
# Unfortunately, the File API is different and slightly worse than the StreamPeer API.
|
|
var rom_size := rom_snes.get_len()
|
|
var bytes := rom_snes.get_buffer(rom_size)
|
|
load_snes_rom_from_bytes(bytes)
|
|
|
|
func load_psx_folder(_dirname: String):
|
|
pass
|
|
|
|
func load_psx_image(filename: String):
|
|
# While it would technically be possible to load everything with no temporary files,
|
|
# It is more convenient to unpack the small files we care about to the user:// directory
|
|
var rom_psx := File.new()
|
|
var error := rom_psx.open(filename, File.READ)
|
|
if error == OK:
|
|
var cd := loader_cd_image.new(rom_psx)
|
|
for key in cd.directory:
|
|
var s = key.trim_prefix('./')
|
|
var re_match := psx_productcode_regex.search(s)
|
|
if re_match:
|
|
print(re_match.get_string(0))
|
|
print(cd.directory)
|
|
|
|
|
|
func _ready():
|
|
structdefs.merge(STRUCT.get_base_structarraytypes())
|
|
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/structs_SNES_stubs.tsv', structdefs)
|
|
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/5/structs/SNES_stubs.tsv', structdefs)
|
|
structdefs.merge(STRUCT_SNES.get_structtypes(), true) # Overwrite the stubs!
|
|
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/5/structs/SNES_save.tsv', structdefs)
|
|
STRUCT.parse_struct_definitions_from_tsv_filename('res://data/5/structs/SNES.tsv', structdefs)
|
|
var _error := psx_productcode_regex.compile('(S[A-Z]{3}_\\d{3}\\.\\d{2});(\\d)')
|
|
StringLoader.connect('loading_stage_updated', self, '_on_loader_loading_stage_updated', ['StringLoader'])
|
|
# Debugging breakpoint
|
|
pass
|
|
|
|
func _exit_tree() -> void:
|
|
for thread in self.threads:
|
|
thread.wait_to_finish()
|