CD Parsing and WIP ROM selector menu
This commit is contained in:
parent
9d36e2afb5
commit
5acc618852
|
@ -1,3 +1,5 @@
|
||||||
|
# Chocolate Bird
|
||||||
|
|
||||||
# What?
|
# What?
|
||||||
Interactive Final Fantasy V save editor
|
Interactive Final Fantasy V save editor
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
Label SNES PSX Comment
|
Label SNES PSX_file PSX_offset Comment
|
||||||
locations_bg_palettes 0x03BB00 /nar/ff5_binx.bin:0x03BF80
|
locations_bg_palettes 0x03BB00 /nar/ff5_binx.bin 0x03BF80
|
||||||
worldmap_blocks 0x0FF0C0 /nar/ff5_binx.bin:0x040300
|
worldmap_blocks 0x0FF0C0 /nar/ff5_binx.bin 0x040300
|
||||||
worldmap_tile_palettes 0x0FF9C0 /nar/ff5_bin3.bin:0x03FB00
|
worldmap_tile_palettes 0x0FF9C0 /nar/ff5_bin3.bin 0x03FB00
|
||||||
worldmap_palettes 0x0FFCC0 /nar/ff5_binx.bin:0x040000
|
worldmap_palettes 0x0FFCC0 /nar/ff5_binx.bin 0x040000
|
||||||
worldmap_tiles 0x1B8000 /nar/ff5_bin3.bin:0x039B00
|
worldmap_tiles 0x1B8000 /nar/ff5_bin3.bin 0x039B00
|
||||||
character_battle_sprite_tiles 0x120000 /mnu/men_bin.eng:0x010200
|
character_battle_sprite_tiles 0x120000 /mnu/men_bin.eng 0x010200
|
||||||
character_battle_sprite_palettes 0x14A3C0 /btl/ff5_btl.bin:0x0273C0 Also /mnu/men_bin.eng:0x03A5C0
|
character_battle_sprite_palettes 0x14A3C0 /btl/ff5_btl.bin 0x0273C0 Also /mnu/men_bin.eng:0x03A5C0
|
||||||
character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin:0x028997
|
character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin 0x028997
|
||||||
character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin:0x000034
|
character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin 0x000034
|
||||||
character_battle_sprite_stone_palette 0x00F807 N/A Also 0x199835
|
character_battle_sprite_stone_palette 0x00F807 N/A N/A Also 0x199835
|
||||||
tiles_fist 0x11D710 /btl/ff5_btl.bin:0x021D10 Also /mnu/men_bin.eng:0x00D910
|
tiles_fist 0x11D710 /btl/ff5_btl.bin 0x021D10 Also /mnu/men_bin.eng:0x00D910
|
||||||
|
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
@ -1,20 +1,22 @@
|
||||||
[gd_scene load_steps=6 format=2]
|
[gd_scene load_steps=7 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://box.tscn" type="PackedScene" id=1]
|
[ext_resource path="res://box.tscn" type="PackedScene" id=1]
|
||||||
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2]
|
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2]
|
||||||
[ext_resource path="res://widgets/ColorMenu.tscn" type="PackedScene" id=3]
|
[ext_resource path="res://widgets/ColorMenu.tscn" type="PackedScene" id=3]
|
||||||
[ext_resource path="res://theme/border_imagetexture.tres" type="Texture" id=4]
|
[ext_resource path="res://theme/border_imagetexture.tres" type="Texture" id=4]
|
||||||
[ext_resource path="res://party_menu.tscn" type="PackedScene" id=5]
|
[ext_resource path="res://party_menu.tscn" type="PackedScene" id=5]
|
||||||
|
[ext_resource path="res://widgets/RomSelect.tscn" type="PackedScene" id=6]
|
||||||
|
|
||||||
[node name="main_menu" type="Control"]
|
[node name="main_menu" type="Control"]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
margin_right = -854.0
|
margin_right = -640.0
|
||||||
margin_bottom = -480.0
|
margin_bottom = -360.0
|
||||||
rect_scale = Vector2( 3, 3 )
|
rect_scale = Vector2( 2, 2 )
|
||||||
theme = ExtResource( 2 )
|
theme = ExtResource( 2 )
|
||||||
|
|
||||||
[node name="party_menu" parent="." instance=ExtResource( 5 )]
|
[node name="party_menu" parent="." instance=ExtResource( 5 )]
|
||||||
|
visible = false
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
anchor_top = 1.0
|
anchor_top = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
@ -54,8 +56,10 @@ margin_bottom = 33.0
|
||||||
text = "Load Save File
|
text = "Load Save File
|
||||||
From here"
|
From here"
|
||||||
|
|
||||||
|
[node name="RomSelect" parent="." instance=ExtResource( 6 )]
|
||||||
|
|
||||||
[node name="ColorMenu" parent="." instance=ExtResource( 3 )]
|
[node name="ColorMenu" parent="." instance=ExtResource( 3 )]
|
||||||
margin_left = 44.0
|
margin_left = 548.0
|
||||||
margin_top = 64.0
|
margin_top = 288.0
|
||||||
margin_right = 129.0
|
margin_right = 633.0
|
||||||
margin_bottom = 128.0
|
margin_bottom = 352.0
|
||||||
|
|
|
@ -14,8 +14,9 @@ _global_script_class_icons={
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="FF"
|
config/name="ChocolateBird"
|
||||||
run/main_scene="res://main_menu.tscn"
|
run/main_scene="res://main_menu.tscn"
|
||||||
|
config/use_custom_user_dir=true
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
@ -44,6 +45,15 @@ window/dpi/allow_hidpi=true
|
||||||
|
|
||||||
theme/use_hidpi=true
|
theme/use_hidpi=true
|
||||||
|
|
||||||
|
[importer_defaults]
|
||||||
|
|
||||||
|
texture={
|
||||||
|
"detect_3d": false,
|
||||||
|
"flags/filter": false,
|
||||||
|
"flags/srgb": 0,
|
||||||
|
"process/fix_alpha_border": false
|
||||||
|
}
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
quality/driver/driver_name="GLES2"
|
quality/driver/driver_name="GLES2"
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
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]
|
|
@ -1,5 +1,14 @@
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
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_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan).sfc'
|
var ROM_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan).sfc'
|
||||||
var GBA_filename := '2564 - Final Fantasy V Advance (U)(Independent).gba'
|
var GBA_filename := '2564 - Final Fantasy V Advance (U)(Independent).gba'
|
||||||
|
|
||||||
|
@ -13,7 +22,26 @@ func load_snes_rom(filename: String):
|
||||||
MapLoader.load_snes_rom(rom_snes)
|
MapLoader.load_snes_rom(rom_snes)
|
||||||
var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes)
|
var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes)
|
||||||
|
|
||||||
|
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():
|
func _ready():
|
||||||
|
var _error := psx_productcode_regex.compile('(S[A-Z]{3}_\\d{3}\\.\\d{2});(\\d)')
|
||||||
load_snes_rom(ROM_filename)
|
load_snes_rom(ROM_filename)
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 816 B |
|
@ -0,0 +1,35 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/ThemeElements.png-9ba250373d48571fc9e80b17768f8afe.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://theme/ThemeElements.png"
|
||||||
|
dest_files=[ "res://.import/ThemeElements.png-9ba250373d48571fc9e80b17768f8afe.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=false
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=0
|
||||||
|
process/fix_alpha_border=false
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=false
|
||||||
|
svg/scale=1.0
|
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_resource type="AtlasTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
atlas = ExtResource( 1 )
|
||||||
|
region = Rect2( 53, 25, 10, 10 )
|
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_resource type="AtlasTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
atlas = ExtResource( 1 )
|
||||||
|
region = Rect2( 52, 13, 11, 11 )
|
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_resource type="AtlasTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
atlas = ExtResource( 1 )
|
||||||
|
region = Rect2( 53, 2, 10, 10 )
|
|
@ -1,9 +1,26 @@
|
||||||
[gd_resource type="Theme" load_steps=8 format=2]
|
[gd_resource type="Theme" load_steps=19 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://3rd_party/sysfont/dynamicfont.tres" type="DynamicFont" id=1]
|
[ext_resource path="res://3rd_party/sysfont/dynamicfont.tres" type="DynamicFont" id=1]
|
||||||
[ext_resource path="res://theme/border_stylebox.tres" type="StyleBox" id=2]
|
[ext_resource path="res://theme/border_stylebox.tres" type="StyleBox" id=2]
|
||||||
[ext_resource path="res://theme/slider8px_inner_stylebox.tres" type="StyleBox" id=3]
|
[ext_resource path="res://theme/slider8px_inner_stylebox.tres" type="StyleBox" id=3]
|
||||||
[ext_resource path="res://theme/slider8px_stylebox.tres" type="StyleBox" id=4]
|
[ext_resource path="res://theme/slider8px_stylebox.tres" type="StyleBox" id=4]
|
||||||
|
[ext_resource path="res://theme/vscroll10px_inner_stylebox.tres" type="StyleBox" id=5]
|
||||||
|
[ext_resource path="res://theme/vscroll10px_stylebox.tres" type="StyleBox" id=6]
|
||||||
|
[ext_resource path="res://theme/vslider10px_inner_stylebox.tres" type="StyleBox" id=7]
|
||||||
|
[ext_resource path="res://theme/vslider10px_stylebox.tres" type="StyleBox" id=8]
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=9]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id=10]
|
||||||
|
texture = ExtResource( 9 )
|
||||||
|
region_rect = Rect2( 24, 1, 4, 7 )
|
||||||
|
margin_left = 1.0
|
||||||
|
margin_right = 1.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id=9]
|
||||||
|
texture = ExtResource( 9 )
|
||||||
|
region_rect = Rect2( 12, 1, 8, 7 )
|
||||||
|
margin_left = 3.0
|
||||||
|
margin_right = 3.0
|
||||||
|
|
||||||
[sub_resource type="ImageTexture" id=4]
|
[sub_resource type="ImageTexture" id=4]
|
||||||
|
|
||||||
|
@ -11,16 +28,33 @@
|
||||||
|
|
||||||
[sub_resource type="ImageTexture" id=3]
|
[sub_resource type="ImageTexture" id=3]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id=5]
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id=6]
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id=7]
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id=8]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
default_font = ExtResource( 1 )
|
default_font = ExtResource( 1 )
|
||||||
Button/colors/font_color = Color( 1, 1, 1, 1 )
|
Button/colors/font_color = Color( 1, 1, 1, 1 )
|
||||||
|
Button/styles/disabled = ExtResource( 2 )
|
||||||
|
Button/styles/hover = ExtResource( 2 )
|
||||||
Button/styles/normal = ExtResource( 2 )
|
Button/styles/normal = ExtResource( 2 )
|
||||||
|
Button/styles/pressed = ExtResource( 2 )
|
||||||
|
HScrollBar/styles/grabber = SubResource( 10 )
|
||||||
|
HScrollBar/styles/grabber_highlight = SubResource( 10 )
|
||||||
|
HScrollBar/styles/grabber_pressed = SubResource( 10 )
|
||||||
|
HScrollBar/styles/scroll = SubResource( 9 )
|
||||||
|
HScrollBar/styles/scroll_focus = SubResource( 9 )
|
||||||
HSlider/icons/grabber = SubResource( 4 )
|
HSlider/icons/grabber = SubResource( 4 )
|
||||||
HSlider/icons/grabber_disabled = SubResource( 2 )
|
HSlider/icons/grabber_disabled = SubResource( 2 )
|
||||||
HSlider/icons/grabber_highlight = SubResource( 3 )
|
HSlider/icons/grabber_highlight = SubResource( 3 )
|
||||||
HSlider/styles/grabber_area = ExtResource( 3 )
|
HSlider/styles/grabber_area = ExtResource( 3 )
|
||||||
HSlider/styles/grabber_area_highlight = ExtResource( 3 )
|
HSlider/styles/grabber_area_highlight = ExtResource( 3 )
|
||||||
HSlider/styles/slider = ExtResource( 4 )
|
HSlider/styles/slider = ExtResource( 4 )
|
||||||
|
ItemList/styles/bg = SubResource( 5 )
|
||||||
Label/colors/font_color = Color( 1, 1, 1, 1 )
|
Label/colors/font_color = Color( 1, 1, 1, 1 )
|
||||||
Label/colors/font_color_shadow = Color( 0, 0, 0, 0.501961 )
|
Label/colors/font_color_shadow = Color( 0, 0, 0, 0.501961 )
|
||||||
Label/constants/line_spacing = 1
|
Label/constants/line_spacing = 1
|
||||||
|
@ -36,3 +70,13 @@ TabContainer/styles/tab_bg = ExtResource( 2 )
|
||||||
TabContainer/styles/tab_disabled = ExtResource( 2 )
|
TabContainer/styles/tab_disabled = ExtResource( 2 )
|
||||||
TabContainer/styles/tab_fg = ExtResource( 2 )
|
TabContainer/styles/tab_fg = ExtResource( 2 )
|
||||||
Tree/styles/bg = ExtResource( 2 )
|
Tree/styles/bg = ExtResource( 2 )
|
||||||
|
VScrollBar/styles/grabber = ExtResource( 5 )
|
||||||
|
VScrollBar/styles/grabber_highlight = ExtResource( 5 )
|
||||||
|
VScrollBar/styles/grabber_pressed = ExtResource( 5 )
|
||||||
|
VScrollBar/styles/scroll = ExtResource( 6 )
|
||||||
|
VSlider/icons/grabber = SubResource( 6 )
|
||||||
|
VSlider/icons/grabber_disabled = SubResource( 7 )
|
||||||
|
VSlider/icons/grabber_highlight = SubResource( 8 )
|
||||||
|
VSlider/styles/grabber_area = ExtResource( 7 )
|
||||||
|
VSlider/styles/grabber_area_highlight = ExtResource( 7 )
|
||||||
|
VSlider/styles/slider = ExtResource( 8 )
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[gd_resource type="StyleBoxTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
texture = ExtResource( 1 )
|
||||||
|
region_rect = Rect2( 28, 47, 10, 4 )
|
||||||
|
margin_top = 1.0
|
||||||
|
margin_bottom = 1.0
|
|
@ -0,0 +1,13 @@
|
||||||
|
[gd_resource type="StyleBoxTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
texture = ExtResource( 1 )
|
||||||
|
region_rect = Rect2( 28, 55, 10, 8 )
|
||||||
|
margin_left = 1.0
|
||||||
|
margin_right = 1.0
|
||||||
|
margin_top = 3.0
|
||||||
|
margin_bottom = 3.0
|
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_resource type="StyleBoxTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
texture = ExtResource( 1 )
|
||||||
|
region_rect = Rect2( 28, 52, 10, 2 )
|
|
@ -0,0 +1,13 @@
|
||||||
|
[gd_resource type="StyleBoxTexture" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
texture = ExtResource( 1 )
|
||||||
|
region_rect = Rect2( 28, 55, 10, 8 )
|
||||||
|
margin_left = 1.0
|
||||||
|
margin_right = 1.0
|
||||||
|
margin_top = 3.0
|
||||||
|
margin_bottom = 3.0
|
||||||
|
expand_margin_top = 4.0
|
||||||
|
expand_margin_bottom = 4.0
|
|
@ -0,0 +1,133 @@
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
const folder_icon := preload('res://theme/icon_folder.tres')
|
||||||
|
const allowed_exts := PoolStringArray(['bin', 'iso', 'sfc', 'srm', 'gba'])
|
||||||
|
var ext_icons := {
|
||||||
|
'bin': preload('res://theme/icon_disc.tres'),
|
||||||
|
'iso': preload('res://theme/icon_disc.tres'),
|
||||||
|
'sfc': preload('res://theme/icon_cart.tres'),
|
||||||
|
'gba': preload('res://theme/icon_cart.tres'),
|
||||||
|
}
|
||||||
|
var dir := Directory.new()
|
||||||
|
var home_path := ''
|
||||||
|
onready var itemlist: ItemList = $VBoxContainer/HBoxContainer/PanelContainer/ItemList
|
||||||
|
onready var vscroller: VScrollBar = itemlist.get_child(0)
|
||||||
|
onready var folder_buttons_scroller: ScrollContainer = $VBoxContainer/ScrollContainer
|
||||||
|
onready var folder_buttons: HBoxContainer = $VBoxContainer/ScrollContainer/folder_buttons
|
||||||
|
onready var btn_ok: Button = $VBoxContainer/HBoxContainer/VBoxContainer/btn_ok
|
||||||
|
onready var file_info: Label = $VBoxContainer/HBoxContainer/VBoxContainer/file_info
|
||||||
|
|
||||||
|
static func get_human_size(n: int) -> String:
|
||||||
|
if n > 0x100000000:
|
||||||
|
return '%.2f GiB' % (n/float(0x40000000))
|
||||||
|
if n > 0x400000:
|
||||||
|
return '%.1f MiB' % (n/float(0x100000))
|
||||||
|
if n > 0x1000:
|
||||||
|
return '%.1f KiB' % (n/float(0x400))
|
||||||
|
return '%d B' % n
|
||||||
|
|
||||||
|
func update_view():
|
||||||
|
var error = dir.list_dir_begin(true, true)
|
||||||
|
if error != OK:
|
||||||
|
print_debug(error)
|
||||||
|
return
|
||||||
|
|
||||||
|
for child in folder_buttons.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
var path = ProjectSettings.globalize_path(dir.get_current_dir())
|
||||||
|
var cur_path: Array = path.split('/')
|
||||||
|
var l := len(cur_path)
|
||||||
|
for i in l:
|
||||||
|
var subpath = '/'.join(cur_path.slice(0, i))
|
||||||
|
var btn := Button.new()
|
||||||
|
btn.connect('pressed', self, 'activate_entry', [subpath])
|
||||||
|
btn.text = cur_path[i]
|
||||||
|
folder_buttons.add_child(btn)
|
||||||
|
if i == 0:
|
||||||
|
btn.text += '/'
|
||||||
|
if i == l-1:
|
||||||
|
btn.icon = folder_icon
|
||||||
|
folder_buttons_scroller.set_h_scroll(9999)
|
||||||
|
folder_buttons_scroller.ensure_control_visible(folder_buttons_scroller.get_h_scrollbar())
|
||||||
|
folder_buttons_scroller.get_h_scrollbar().update()
|
||||||
|
|
||||||
|
itemlist.clear()
|
||||||
|
var directories := PoolStringArray()
|
||||||
|
var files := []
|
||||||
|
while true:
|
||||||
|
var entry := dir.get_next()
|
||||||
|
if len(entry) == 0:
|
||||||
|
break
|
||||||
|
if dir.current_is_dir():
|
||||||
|
directories.append(entry)
|
||||||
|
elif '.' in entry:
|
||||||
|
var extension = entry.rsplit('.', true, 1)[1]
|
||||||
|
if extension in allowed_exts:
|
||||||
|
files.append([entry, ext_icons.get(extension)])
|
||||||
|
directories.sort()
|
||||||
|
files.sort()
|
||||||
|
for entry in directories:
|
||||||
|
itemlist.add_item(entry, folder_icon)
|
||||||
|
for entry in files:
|
||||||
|
itemlist.add_item(entry[0], entry[1])
|
||||||
|
# itemlist.sort_items_by_text()
|
||||||
|
|
||||||
|
func activate_entry(entry: String):
|
||||||
|
if dir.dir_exists(entry):
|
||||||
|
var error := dir.change_dir(entry)
|
||||||
|
if error == OK:
|
||||||
|
update_view()
|
||||||
|
else:
|
||||||
|
print_debug(error)
|
||||||
|
elif dir.file_exists(entry):
|
||||||
|
pass # Load the file
|
||||||
|
|
||||||
|
func view_entry(entry: String):
|
||||||
|
if dir.dir_exists(entry):
|
||||||
|
file_info.text = 'Filename:\n %s/\nType: Folder' % entry
|
||||||
|
elif dir.file_exists(entry):
|
||||||
|
var file := File.new()
|
||||||
|
var error := file.open(dir.get_current_dir() + '/' + entry, File.READ)
|
||||||
|
if error != OK:
|
||||||
|
print_debug(error)
|
||||||
|
return
|
||||||
|
var size = file.get_len()
|
||||||
|
var human_size = get_human_size(size)
|
||||||
|
var ext = entry.rsplit('.', true, 1)[1].to_lower()
|
||||||
|
var type = {'iso': 'CD-ROM Image', 'bin': 'Binary', 'sfc': 'SNES ROM', 'gba': 'GBA ROM', 'srm': 'SNES Savefile'}.get(ext, 'Unknown')
|
||||||
|
var prodcode := ''
|
||||||
|
if ext in ['iso', 'bin']:
|
||||||
|
var cd := RomLoader.loader_cd_image.new(file)
|
||||||
|
for key in cd.directory:
|
||||||
|
var s = key.trim_prefix('./')
|
||||||
|
var re_match := RomLoader.psx_productcode_regex.search(s)
|
||||||
|
if re_match:
|
||||||
|
prodcode = '%s\n %s\n %s' % [cd.pvd.system_identifier.strip_edges(), cd.pvd.volume_identifier.strip_edges(), re_match.get_string(1)]
|
||||||
|
type = 'PSX CD-ROM Image'
|
||||||
|
break
|
||||||
|
var info = ('Info:\n %s' % prodcode) if prodcode else ''
|
||||||
|
file_info.text = 'Filename:\n %s\nType:\n %s\nSize:\n %s\n%s' % [entry, type, human_size, info]
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if OS.has_feature('windows'):
|
||||||
|
home_path = OS.get_environment('USERPROFILE')
|
||||||
|
else:
|
||||||
|
home_path = OS.get_environment('HOME')
|
||||||
|
# dir.open(OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS))
|
||||||
|
var error = dir.open(home_path)
|
||||||
|
if error == OK:
|
||||||
|
update_view()
|
||||||
|
print(ProjectSettings.globalize_path('user://'))
|
||||||
|
print(ProjectSettings.globalize_path(dir.get_current_dir()))
|
||||||
|
|
||||||
|
#func _process(_delta):
|
||||||
|
#vscroller.set_margin(MARGIN_TOP, 4)
|
||||||
|
#vscroller.set_margin(MARGIN_BOTTOM, -4)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ItemList_item_activated(index: int) -> void:
|
||||||
|
activate_entry(itemlist.get_item_text(index))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ItemList_item_selected(index: int) -> void:
|
||||||
|
view_entry(itemlist.get_item_text(index))
|
|
@ -0,0 +1,82 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=1]
|
||||||
|
[ext_resource path="res://widgets/RomSelect.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="RomSelect" type="PanelContainer"]
|
||||||
|
margin_right = 8.0
|
||||||
|
margin_bottom = 8.0
|
||||||
|
theme = ExtResource( 1 )
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
margin_left = 4.0
|
||||||
|
margin_top = 4.0
|
||||||
|
margin_right = 352.0
|
||||||
|
margin_bottom = 225.0
|
||||||
|
custom_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||||
|
margin_right = 348.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
text = "Select a ROM from your device's storage"
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 348.0
|
||||||
|
margin_bottom = 49.0
|
||||||
|
rect_min_size = Vector2( 0, 31 )
|
||||||
|
scroll_vertical_enabled = false
|
||||||
|
|
||||||
|
[node name="folder_buttons" type="HBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
||||||
|
custom_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
margin_top = 53.0
|
||||||
|
margin_right = 348.0
|
||||||
|
margin_bottom = 221.0
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_right = 200.0
|
||||||
|
margin_bottom = 168.0
|
||||||
|
rect_min_size = Vector2( 200, 0 )
|
||||||
|
|
||||||
|
[node name="ItemList" type="ItemList" parent="VBoxContainer/HBoxContainer/PanelContainer"]
|
||||||
|
margin_left = 4.0
|
||||||
|
margin_top = 4.0
|
||||||
|
margin_right = 196.0
|
||||||
|
margin_bottom = 164.0
|
||||||
|
rect_min_size = Vector2( 120, 160 )
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 208.0
|
||||||
|
margin_right = 348.0
|
||||||
|
margin_bottom = 168.0
|
||||||
|
|
||||||
|
[node name="file_info" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"]
|
||||||
|
margin_right = 140.0
|
||||||
|
margin_bottom = 138.0
|
||||||
|
rect_min_size = Vector2( 140, 0 )
|
||||||
|
size_flags_vertical = 3
|
||||||
|
text = "Filename:
|
||||||
|
myfile.iso
|
||||||
|
Size:
|
||||||
|
400 KiB
|
||||||
|
Type:
|
||||||
|
CD-ROM image
|
||||||
|
|
||||||
|
PLAYSTATION
|
||||||
|
SCEA_000.00"
|
||||||
|
|
||||||
|
[node name="btn_ok" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer"]
|
||||||
|
margin_left = 57.0
|
||||||
|
margin_top = 146.0
|
||||||
|
margin_right = 82.0
|
||||||
|
margin_bottom = 168.0
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 8
|
||||||
|
disabled = true
|
||||||
|
text = "OK"
|
||||||
|
|
||||||
|
[connection signal="item_activated" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_activated"]
|
||||||
|
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/PanelContainer/ItemList" to="." method="_on_ItemList_item_selected"]
|
Loading…
Reference in New Issue