CD Parsing and WIP ROM selector menu

This commit is contained in:
Luke Hubmayer-Werner 2023-08-01 23:22:31 +09:30
parent 9d36e2afb5
commit 5acc618852
18 changed files with 594 additions and 22 deletions

View File

@ -1,3 +1,5 @@
# Chocolate Bird
# What? # What?
Interactive Final Fantasy V save editor Interactive Final Fantasy V save editor

View File

@ -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.

View File

@ -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

View File

@ -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"

171
scripts/loaders/cd/image.gd Normal file
View File

@ -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]

View File

@ -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:

BIN
theme/ThemeElements.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

View File

@ -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

7
theme/icon_cart.tres Normal file
View File

@ -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 )

7
theme/icon_disc.tres Normal file
View File

@ -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 )

7
theme/icon_folder.tres Normal file
View File

@ -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 )

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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 )

View File

@ -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

133
widgets/RomSelect.gd Normal file
View File

@ -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))

82
widgets/RomSelect.tscn Normal file
View File

@ -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"]