Redefine struct DSL

Still tab-separated, but types now require u/s (e.g. 8 is now u8)
Array form changed from "type[x][y]" to "x of y of type"
Array of array is now recursive so arbitrary nesting is allowed
Also the new parser should be able to write as easily as it reads, hooray!
This commit is contained in:
Luke Hubmayer-Werner 2023-08-03 19:32:52 +09:30
parent 00496ed59f
commit 724f48a62e
4 changed files with 365 additions and 323 deletions

161
data/SNES_save.tsv Normal file
View File

@ -0,0 +1,161 @@
struct Character
u3 character_id # 0=Bartz, 1=Lenna, 2=Galuf, 3=Faris, 4=Krile, 5/6/7 unused
u1 is_female # 0=male, 1=female
u2 unk0 # Two unknown, possibly unused bits
u1 is_absent # 0=present, 1=absent
u1 is_back_row
u8 current_job_id
u8 level
u24 experience
u16 hp_current
u16 hp_max
u16 mp_current
u16 mp_max
u8 equipped_head
u8 equipped_body
u8 equipped_acc
u8 equipped_rh_shield
u8 equipped_lh_shield
u8 equipped_rh_weapon
u8 equipped_lh_weapon
u8 caught_monster
u8 ability_1
u8 ability_2
u8 ability_3
u8 ability_4
u8 status_1
u8 status_2
u8 status_3
u8 status_4
u8 action_flags
u8 damage_mod
u16 innates
u8 magic_element_up
u8 equip_weight
u8 base_strength
u8 base_agility
u8 base_stamina
u8 base_magic
u8 current_strength
u8 current_agility
u8 current_stamina
u8 current_magic
u8 evasion
u8 defense
u8 magic_evasion
u8 magic_defense
u8 elemental_absorb
u8 elemental_evade
u8 elemental_immune
u8 elemental_half
u8 elemental_weakness
u8 resistance_status_1
u8 resistance_status_2
u8 resistance_status_3
u8 specialty_weapon
u8 specialty_equipment
u8 current_job_level
u16 current_job_abp
u8 spell_level_1
u8 spell_level_2
u8 spell_level_3
u32 equipment_category
u16 attack
u8 attack_id_reaction_unused
u8 unk1
u8 unk2
u8 unk3
u8 freelancer_strength
u8 freelancer_agility
u8 freelancer_stamina
u8 freelancer_magic
u16 freelancer_innates
struct Job_progress
u12 abp
u4 level
struct Config
u3 battle_speed # 0=1 in-game, ..., 5=6 in-game
u1 is_wait_mode # 0=active, 1=wait???
u3 message_speed # 0=1 in-game, ..., 5=6 in-game
u1 command_set # 0=window, 1=shortcut
u5 menu_color_r
u5 menu_color_g
u5 menu_color_b
u1 padding
u1 reequip_mode # 0=optimum, 1=empty
u1 is_mono # 0=stereo, 1=mono
u1 is_memory_cursor # 0=reset, 1=memory
u4 unk0
u1 hide_atb_gauge # 0=show, 1=hide
u6 unk1
u1 is_controller_custom # 0=no, 1=yes
u1 is_controller_2p # 0=no, 1=yes
u8 button_A # Bit of action
u8 button_B # Bit of action
u8 button_X # Bit of action
u8 button_Y # Bit of action
u8 button_L # Bit of action
u8 button_R # Bit of action
u8 button_Select # Bit of action
4 of u8 character_player_nums # 0=controller 1, 1=controller 2
4 of 4 of u8 character_shortcut_commands # 0=ability_1, 1=ability_2, 2=ability_3, 3=ability_4
struct Vehicle
u2 mode_switching
u3 movement_type
u3 map_id
u7 unk0
u1 is_hidden # 0=show, 1=hide
u8 x
u8 y
struct Save_slot
4 of Character characters
256 of u8 inventory_item_ids
256 of u8 inventory_item_qtys
u24 unlocked_jobs
4 of 22 of Job_progress character_jobs_progress
4 of u8 character_abilities_learned_count
4 of 20 of u8 character_abilities_learned
u24 current_gil
u32 game_time_frames
u16 num_enemies_defeated
32 of u8 magic_learned
Config config
5 of 6 of u8 character_names # Bartz, Lenna, Galuf, Faris, Krile. Dialog is hardcoded for everyone except Bartz's name anyway...
6 of u8 unk0
u8 magic_lamp_next_summon
u8 num_battles_escaped # Brave Blade vs Chicken Knife
u8 wonder_rod_magic
9 of u8 unk1
u16 num_total_battles
u16 num_times_saved
u8 last_battle_results # 0=victory, 1=game over, 2=escaped
15 of u8 flags_battle_events
32 of u8 flags_treasure_opened
32 of u8 unk_probably_still_flags_treasure_opened
96 of u8 flags_events # RAM map mentions $D8E000. This is likely critical to story progression and scripting.
96 of u8 unk_probably_still_flags_events
u16 map_id_inner
u16 map_id_world
u8 pos_x
u8 pos_y
u8 current_character_sprite
u8 current_character_facing
u8 current_vehicle
Vehicle veh_chocobo
Vehicle veh_black_chocobo
Vehicle veh_hiryuu
Vehicle veh_submarine
Vehicle veh_steamship
Vehicle veh_airship
u16 teleport_map_id
u8 teleport_map_x
u8 teleport_map_y
u8 initial_seed
u8 walking_speed # 0=normal, 1=double (fast), 80=half (slow)
u8 timed_event_active
u16 timed_event_timer
u16 timed_event_end
1 struct Character
2 u3 character_id # 0=Bartz, 1=Lenna, 2=Galuf, 3=Faris, 4=Krile, 5/6/7 unused
3 u1 is_female # 0=male, 1=female
4 u2 unk0 # Two unknown, possibly unused bits
5 u1 is_absent # 0=present, 1=absent
6 u1 is_back_row
7 u8 current_job_id
8 u8 level
9 u24 experience
10 u16 hp_current
11 u16 hp_max
12 u16 mp_current
13 u16 mp_max
14 u8 equipped_head
15 u8 equipped_body
16 u8 equipped_acc
17 u8 equipped_rh_shield
18 u8 equipped_lh_shield
19 u8 equipped_rh_weapon
20 u8 equipped_lh_weapon
21 u8 caught_monster
22 u8 ability_1
23 u8 ability_2
24 u8 ability_3
25 u8 ability_4
26 u8 status_1
27 u8 status_2
28 u8 status_3
29 u8 status_4
30 u8 action_flags
31 u8 damage_mod
32 u16 innates
33 u8 magic_element_up
34 u8 equip_weight
35 u8 base_strength
36 u8 base_agility
37 u8 base_stamina
38 u8 base_magic
39 u8 current_strength
40 u8 current_agility
41 u8 current_stamina
42 u8 current_magic
43 u8 evasion
44 u8 defense
45 u8 magic_evasion
46 u8 magic_defense
47 u8 elemental_absorb
48 u8 elemental_evade
49 u8 elemental_immune
50 u8 elemental_half
51 u8 elemental_weakness
52 u8 resistance_status_1
53 u8 resistance_status_2
54 u8 resistance_status_3
55 u8 specialty_weapon
56 u8 specialty_equipment
57 u8 current_job_level
58 u16 current_job_abp
59 u8 spell_level_1
60 u8 spell_level_2
61 u8 spell_level_3
62 u32 equipment_category
63 u16 attack
64 u8 attack_id_reaction_unused
65 u8 unk1
66 u8 unk2
67 u8 unk3
68 u8 freelancer_strength
69 u8 freelancer_agility
70 u8 freelancer_stamina
71 u8 freelancer_magic
72 u16 freelancer_innates
73
74 struct Job_progress
75 u12 abp
76 u4 level
77
78 struct Config
79 u3 battle_speed # 0=1 in-game, ..., 5=6 in-game
80 u1 is_wait_mode # 0=active, 1=wait???
81 u3 message_speed # 0=1 in-game, ..., 5=6 in-game
82 u1 command_set # 0=window, 1=shortcut
83 u5 menu_color_r
84 u5 menu_color_g
85 u5 menu_color_b
86 u1 padding
87 u1 reequip_mode # 0=optimum, 1=empty
88 u1 is_mono # 0=stereo, 1=mono
89 u1 is_memory_cursor # 0=reset, 1=memory
90 u4 unk0
91 u1 hide_atb_gauge # 0=show, 1=hide
92 u6 unk1
93 u1 is_controller_custom # 0=no, 1=yes
94 u1 is_controller_2p # 0=no, 1=yes
95 u8 button_A # Bit of action
96 u8 button_B # Bit of action
97 u8 button_X # Bit of action
98 u8 button_Y # Bit of action
99 u8 button_L # Bit of action
100 u8 button_R # Bit of action
101 u8 button_Select # Bit of action
102 4 of u8 character_player_nums # 0=controller 1, 1=controller 2
103 4 of 4 of u8 character_shortcut_commands # 0=ability_1, 1=ability_2, 2=ability_3, 3=ability_4
104
105 struct Vehicle
106 u2 mode_switching
107 u3 movement_type
108 u3 map_id
109 u7 unk0
110 u1 is_hidden # 0=show, 1=hide
111 u8 x
112 u8 y
113
114 struct Save_slot
115 4 of Character characters
116 256 of u8 inventory_item_ids
117 256 of u8 inventory_item_qtys
118 u24 unlocked_jobs
119 4 of 22 of Job_progress character_jobs_progress
120 4 of u8 character_abilities_learned_count
121 4 of 20 of u8 character_abilities_learned
122 u24 current_gil
123 u32 game_time_frames
124 u16 num_enemies_defeated
125 32 of u8 magic_learned
126 Config config
127 5 of 6 of u8 character_names # Bartz, Lenna, Galuf, Faris, Krile. Dialog is hardcoded for everyone except Bartz's name anyway...
128 6 of u8 unk0
129 u8 magic_lamp_next_summon
130 u8 num_battles_escaped # Brave Blade vs Chicken Knife
131 u8 wonder_rod_magic
132 9 of u8 unk1
133 u16 num_total_battles
134 u16 num_times_saved
135 u8 last_battle_results # 0=victory, 1=game over, 2=escaped
136 15 of u8 flags_battle_events
137 32 of u8 flags_treasure_opened
138 32 of u8 unk_probably_still_flags_treasure_opened
139 96 of u8 flags_events # RAM map mentions $D8E000. This is likely critical to story progression and scripting.
140 96 of u8 unk_probably_still_flags_events
141 u16 map_id_inner
142 u16 map_id_world
143 u8 pos_x
144 u8 pos_y
145 u8 current_character_sprite
146 u8 current_character_facing
147 u8 current_vehicle
148 Vehicle veh_chocobo
149 Vehicle veh_black_chocobo
150 Vehicle veh_hiryuu
151 Vehicle veh_submarine
152 Vehicle veh_steamship
153 Vehicle veh_airship
154 u16 teleport_map_id
155 u8 teleport_map_x
156 u8 teleport_map_y
157 u8 initial_seed
158 u8 walking_speed # 0=normal, 1=double (fast), 80=half (slow)
159 u8 timed_event_active
160 u16 timed_event_timer
161 u16 timed_event_end

View File

@ -1,161 +0,0 @@
struct Character
3 character_id # 0=Bartz, 1=Lenna, 2=Galuf, 3=Faris, 4=Krile, 5/6/7 unused
1 is_female # 0=male, 1=female
2 unk0 # Two unknown, possibly unused bits
1 is_absent # 0=present, 1=absent
1 is_back_row
8 current_job_id
8 level
24 experience
16 hp_current
16 hp_max
16 mp_current
16 mp_max
8 equipped_head
8 equipped_body
8 equipped_acc
8 equipped_rh_shield
8 equipped_lh_shield
8 equipped_rh_weapon
8 equipped_lh_weapon
8 caught_monster
8 ability_1
8 ability_2
8 ability_3
8 ability_4
8 status_1
8 status_2
8 status_3
8 status_4
8 action_flags
8 damage_mod
16 innates
8 magic_element_up
8 equip_weight
8 base_strength
8 base_agility
8 base_stamina
8 base_magic
8 current_strength
8 current_agility
8 current_stamina
8 current_magic
8 evasion
8 defense
8 magic_evasion
8 magic_defense
8 elemental_absorb
8 elemental_evade
8 elemental_immune
8 elemental_half
8 elemental_weakness
8 resistance_status_1
8 resistance_status_2
8 resistance_status_3
8 specialty_weapon
8 specialty_equipment
8 current_job_level
16 current_job_abp
8 spell_level_1
8 spell_level_2
8 spell_level_3
32 equipment_category
16 attack
8 attack_id_reaction_unused
8 unk1
8 unk2
8 unk3
8 freelancer_strength
8 freelancer_agility
8 freelancer_stamina
8 freelancer_magic
16 freelancer_innates
struct Job_progress
12 abp
4 level
struct Config
3 battle_speed # 0=1 in-game, ..., 5=6 in-game
1 is_wait_mode # 0=active, 1=wait???
3 message_speed # 0=1 in-game, ..., 5=6 in-game
1 command_set # 0=window, 1=shortcut
5 menu_color_r
5 menu_color_g
5 menu_color_b
1 padding
1 reequip_mode # 0=optimum, 1=empty
1 is_mono # 0=stereo, 1=mono
1 is_memory_cursor # 0=reset, 1=memory
4 unk0
1 hide_atb_gauge # 0=show, 1=hide
6 unk1
1 is_controller_custom # 0=no, 1=yes
1 is_controller_2p # 0=no, 1=yes
8 button_A # Bit of action
8 button_B # Bit of action
8 button_X # Bit of action
8 button_Y # Bit of action
8 button_L # Bit of action
8 button_R # Bit of action
8 button_Select # Bit of action
8[4] character_player_nums # 0=controller 1, 1=controller 2
8[4][4] character_shortcut_commands # 0=ability_1, 1=ability_2, 2=ability_3, 3=ability_4
struct Vehicle
2 mode_switching
3 movement_type
3 map_id
7 unk0
1 is_hidden # 0=show, 1=hide
8 x
8 y
struct Save_slot
Character[4] characters
8[256] inventory_item_ids
8[256] inventory_item_qtys
24 unlocked_jobs
Job_progress[4][22] character_jobs_progress
8[4] character_abilities_learned_count
8[4][20] character_abilities_learned
24 current_gil
32 game_time_frames
16 num_enemies_defeated
8[32] magic_learned
Config config
8[5][6] character_names # Bartz, Lenna, Galuf, Faris, Krile. Dialog is hardcoded for everyone except Bartz's name anyway...
8[6] unk0
8 magic_lamp_next_summon
8 num_battles_escaped # Brave Blade vs Chicken Knife
8 wonder_rod_magic
8[9] unk1
16 num_total_battles
16 num_times_saved
8 last_battle_results # 0=victory, 1=game over, 2=escaped
8[15] flags_battle_events
8[32] flags_treasure_opened
8[32] unk_probably_still_flags_treasure_opened
8[96] flags_events # RAM map mentions $D8E000. This is likely critical to story progression and scripting.
8[96] unk_probably_still_flags_events
16 map_id_inner
16 map_id_world
8 pos_x
8 pos_y
8 current_character_sprite
8 current_character_facing
8 current_vehicle
Vehicle veh_chocobo
Vehicle veh_black_chocobo
Vehicle veh_hiryuu
Vehicle veh_submarine
Vehicle veh_steamship
Vehicle veh_airship
16 teleport_map_id
8 teleport_map_x
8 teleport_map_y
8 initial_seed
8 walking_speed # 0=normal, 1=double (fast), 80=half (slow)
8 timed_event_active
16 timed_event_timer
16 timed_event_end

View File

@ -1,6 +1,7 @@
extends Node
const STRUCT := preload('res://scripts/struct.gd')
const SLOT_IN_USE := 0xE41B
var schema := {}
var struct_types := STRUCT.get_base_structarraytypes()
# FFV SRAM is 4 slots of 0x700byte save files
# $0000-$06FF - Save Slot 1
@ -38,148 +39,13 @@ func get_slot_checksum(slot: StreamPeerBuffer) -> int:
checksum += 1 # TODO: confirm this carry flag edge case is correct!
return checksum
func get_character(slot: StreamPeerBuffer, character_slot_id: int) -> Dictionary:
var character := {}
slot.seek(0x50 * character_slot_id)
var b := slot.get_u8()
character['id'] = b & 0x07 # 0Bartz/1Lenna/2Galuf/3Faris/4Krile/5/6/7
character['gender'] = (b >> 3) & 1 # 0 = male, 1 = female
# Two unknown, possibly unused bits
character['present'] = (b >> 6) & 1 # 0 = absent?
character['back_row'] = (b >> 7) & 1
character['job_id'] = slot.get_u8()
character['level'] = slot.get_u8()
character['experience'] = slot.get_u16() | (slot.get_u8() << 16)
character['hp_current'] = slot.get_u16()
character['hp_max'] = slot.get_u16()
character['mp_current'] = slot.get_u16()
character['mp_max'] = slot.get_u16()
character['equipped_head'] = slot.get_u8()
character['equipped_body'] = slot.get_u8()
character['equipped_acc'] = slot.get_u8()
character['equipped_rh_shield'] = slot.get_u8()
character['equipped_lh_shield'] = slot.get_u8()
character['equipped_rh_weapon'] = slot.get_u8()
character['equipped_lh_weapon'] = slot.get_u8()
character['caught_monster'] = slot.get_u8()
character['ability_1'] = slot.get_u8()
character['ability_2'] = slot.get_u8()
character['ability_3'] = slot.get_u8()
character['ability_4'] = slot.get_u8()
character['action_flags'] = slot.get_u8()
character['damage_mod'] = slot.get_u8()
character['innates'] = slot.get_u16()
character['magic_element_up'] = slot.get_u8()
character['equip_weight'] = slot.get_u8()
character['base_strength'] = slot.get_u8()
character['base_agility'] = slot.get_u8()
character['base_stamina'] = slot.get_u8()
character['base_magic'] = slot.get_u8()
character['current_strength'] = slot.get_u8()
character['current_agility'] = slot.get_u8()
character['current_stamina'] = slot.get_u8()
character['current_magic'] = slot.get_u8()
character['evasion'] = slot.get_u8()
character['defense'] = slot.get_u8()
character['magic_evasion'] = slot.get_u8()
character['magic_defense'] = slot.get_u8()
character['elemental_absorb'] = slot.get_u8()
character['elemental_evade'] = slot.get_u8()
character['elemental_immune'] = slot.get_u8()
character['elemental_half'] = slot.get_u8()
character['elemental_weakness'] = slot.get_u8()
character['resistance_status_1'] = slot.get_u8()
character['resistance_status_2'] = slot.get_u8()
character['resistance_status_3'] = slot.get_u8()
character['specialty_weapon'] = slot.get_u8()
character['specialty_equipment'] = slot.get_u8()
character['current_job_level'] = slot.get_u8()
character['current_job_abp'] = slot.get_u16()
character['spell_level_1'] = slot.get_u8()
character['spell_level_2'] = slot.get_u8()
character['spell_level_3'] = slot.get_u8()
character['equipment_category'] = slot.get_u32()
character['attack'] = slot.get_u16()
character['attack_id_reaction_unused'] = slot.get_u8()
character['unk1'] = slot.get_u8()
character['unk2'] = slot.get_u8()
character['unk3'] = slot.get_u8()
character['freelancer_strength'] = slot.get_u8()
character['freelancer_agility'] = slot.get_u8()
character['freelancer_stamina'] = slot.get_u8()
character['freelancer_magic'] = slot.get_u8()
character['freelancer_innates'] = slot.get_u16()
return character
func get_struct(buffer: StreamPeer, struct_name: String) -> Dictionary:
# As this is recursive, it does not seek to the start of the buffer.
# Does not seek to the start of the buffer.
# Make sure it is set to where you want to start reading.
if not (struct_name in schema):
if not (struct_name in struct_types):
print_debug('Attempted to get undeclared struct: "%s"' % struct_name)
return {}
var struct := {}
var leftover_bits := 0
var leftover_bits_value := 0
for line in schema[struct_name]:
var t: String = line[0]
var k: String = line[1]
match t:
'8':
struct[k] = buffer.get_u8()
'16':
struct[k] = buffer.get_u16()
'24':
struct[k] = buffer.get_u16() | (buffer.get_u8() << 16)
'32':
struct[k] = buffer.get_u32()
_:
var arr_split = t.split('[')
match arr_split.size():
1:
# Check if it's an odd number of bits
var b := int(t)
if b > 0:
# Check if we have leftover bits to fill this order
while leftover_bits < b:
leftover_bits_value |= buffer.get_u8() << leftover_bits
leftover_bits += 8
struct[k] = leftover_bits_value & ((1 << b)-1)
leftover_bits_value = leftover_bits_value >> b
leftover_bits -= b
else:
# It's a struct
struct[k] = get_struct(buffer, t)
2:
var n0 := int(arr_split[1].trim_suffix(']'))
var arr = []
if arr_split[0] == '8':
arr = PoolByteArray()
for i in n0:
arr.append(buffer.get_u8())
else:
for i in n0:
arr.append(get_struct(buffer, arr_split[0]))
struct[k] = arr
3:
var n0 := int(arr_split[1].trim_suffix(']'))
var n1 := int(arr_split[2].trim_suffix(']'))
var arr0 := []
if arr_split[0] == '8':
for i in n0:
var arr1 := PoolByteArray()
for j in n1:
arr1.append(buffer.get_u8())
arr0.append(arr1)
else:
for i in n0:
var arr1 := []
for j in n1:
arr1.append(get_struct(buffer, arr_split[0]))
arr0.append(arr1)
struct[k] = arr0
var s:
print_debug('struct value array dims of %d are not yet supported (struct "%s")' % [s, struct_name])
return struct
return struct_types[struct_name].get_value(buffer, [0, 0])
func get_save_slot(sram: File, slot_id: int):
var buffer := StreamPeerBuffer.new()
@ -201,37 +67,24 @@ func delete_save_slot(sram: File, slot_id: int):
func _ready():
var file := File.new()
var error = file.open('res://data/SNES_save.txt', File.READ)
var error = file.open('res://data/SNES_save.tsv', File.READ)
if error == OK:
var current_struct_name = null
var current_struct = []
var line_num := 0
var current_struct: STRUCT.Struct
var line_num := 0 # Currently only used for step-through debugging
while file.get_position() < file.get_len():
var line := file.get_csv_line('\t')
line_num += 1
var size = line.size()
if size < 2:
if current_struct_name:
# Store struct we just finished declaring
schema[current_struct_name] = current_struct
current_struct = []
current_struct_name = null
continue
# if size < 2:
# print_debug('Malformed schema file: line %d - size %d - "%s"' % [line_num, line.size(), line])
# continue
# Size is at least 2
if line[0] == 'struct':
var type := line[0]
var label := line[1]
if type == 'struct':
# New struct declaration
if current_struct_name:
# Store one we just finished declaring
schema[current_struct_name] = current_struct
current_struct = []
current_struct_name = line[1]
else:
current_struct = STRUCT.Struct.new()
struct_types[label] = current_struct
elif type and label:
# TODO: Maybe store the trailing comments somewhere?
current_struct.append(line)
# Make sure we saved the final struct
if current_struct_name:
schema[current_struct_name] = current_struct
current_struct.members.append([label, STRUCT.get_structarraytype(type, struct_types)])
file.close()

189
scripts/struct.gd Normal file
View File

@ -0,0 +1,189 @@
#warning-ignore-all:shadowed_variable
#warning-ignore-all:unused_argument
# leftover_bits is array of form [count, value]
# array is used for reference semantics as get and put operations may mutate it
class StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
return
class U8 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_u8()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_u8(value)
class S8 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_8()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_8(value)
class U16 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_u16()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_u16(value)
class S16 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_16()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_16(value)
class U24 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_u16() | (buffer.get_u8() << 16)
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_u16(value & 0xFFFF)
buffer.put_u8(value >> 16)
class S24 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
var unsigned = buffer.get_u16() | (buffer.get_u8() << 16)
return unsigned - (2 * (unsigned & 0x800000))
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
var unsigned = value % 0x1000000
buffer.put_u16(unsigned & 0xFFFF)
buffer.put_u8(unsigned >> 16)
class U32 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_u32()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_u32(value)
class S32 extends StructType:
func get_value(buffer: StreamPeer, leftover_bits: Array):
return buffer.get_32()
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
buffer.put_32(value)
class UBits extends StructType:
var bits = 8
func _init(bits: int):
self.bits = bits
func get_value(buffer: StreamPeer, leftover_bits: Array):
while leftover_bits[0] < bits:
leftover_bits[1] |= buffer.get_u8() << leftover_bits[0]
leftover_bits[0] += 8
var value = leftover_bits[1] & ((1 << bits)-1)
leftover_bits[1] = leftover_bits[1] >> bits
leftover_bits[0] -= bits
return value
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
leftover_bits[1] |= value << bits
leftover_bits[0] += bits
while leftover_bits[0] >= 8:
buffer.put_8(leftover_bits[1] & 0xFF)
leftover_bits[0] -= 8
leftover_bits[1] = leftover_bits[1] >> 8
class Struct extends StructType:
var members := [] # Array of [name, StructType]
func get_value(buffer: StreamPeer, leftover_bits: Array):
var result = {}
for member in members:
var key: String = member[0]
var structType: StructType = member[1]
result[key] = structType.get_value(buffer, leftover_bits)
return result
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
for member in members:
var key: String = member[0]
var structType: StructType = member[1]
if not (key in value):
print_debug('Key "%s" missing from value supplied' % key)
return
structType.put_value(buffer, value[key], leftover_bits)
class StructArrayType extends StructType:
var count: int
var structType: StructType
func _init(count, structType) -> void:
self.count = count
self.structType = structType
func get_value(buffer: StreamPeer, leftover_bits: Array):
# Might be a bit too much branching but oh well
if self.structType is U8:
var result = PoolByteArray()
# Slight optimization over calling the method
for i in self.count:
result.append(buffer.get_u8())
return result
var result = []
for i in self.count:
result.append(self.structType.get_value(buffer, leftover_bits))
return result
func put_value(buffer: StreamPeer, value, leftover_bits: Array):
if len(value) < self.count:
print_debug('Not enough values supplied')
return
for i in self.count:
self.structType.put_value(buffer, value[i], leftover_bits)
static func get_base_structarraytypes() -> Dictionary:
return {
'u8': U8.new(),
'u16': U16.new(),
'u24': U24.new(),
'u32': U32.new(),
's8': S8.new(),
's16': S16.new(),
's24': S24.new(),
's32': S32.new(),
}
static func get_structarraytype(type: String, existing_structs: Dictionary):
var tokens := type.split(' ', false)
var t: String = tokens[-1]
var inner_type
if t in existing_structs:
inner_type = existing_structs[t]
elif t[0] == 'u':
var b := int(t.substr(1))
if b > 0:
inner_type = UBits.new(b)
existing_structs['u%d'%b] = inner_type # Cache it for future use
if not inner_type:
print_debug('typestring "%s" has no matches for "%s" in existing structs' % [type, t])
return
var l := len(tokens)
if l == 1:
return inner_type
# Our parsing goal is to turn 'a of b of c of d' into StructArrayType<StructArrayType<StructArrayType<d, c>, b>, a>
# Our strategy is to parse backwards over the tokens, changing inner_type at each point
# a of b of c of (d)
# a of b of (c of d)
# a of (b of c of d)
# (a of b of c of d)
# done
var i := l-2
while i > -1:
match tokens[i]:
'of':
i -= 1
var l1 = int(tokens[i])
if l1 > 1:
inner_type = StructArrayType.new(l1, inner_type) # Might be worth caching these later on if we use them more
i -= 1
var k:
print_debug('Invalid keyword used in type designator: "%s"' % k)
return
return inner_type