1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
pub mod misc_section;

use byteorder::{BigEndian, ReadBytesExt};

use crate::script::Script;
use crate::script;
use crate::util;
use misc_section::MiscSection;

pub(crate) fn arc_fighter_data(parent_data: &[u8], data: &[u8]) -> ArcFighterData {
    let subaction_flags_start     = (&data[0..]).read_i32::<BigEndian>().unwrap();
    let model_visibility_start     = (&data[4..]).read_i32::<BigEndian>().unwrap();
    let attribute_start            = (&data[8..]).read_i32::<BigEndian>().unwrap();
    let sse_attribute_start        = (&data[12..]).read_i32::<BigEndian>().unwrap();
    let misc_section_offset        = (&data[16..]).read_i32::<BigEndian>().unwrap();
    let common_action_flags_start  = (&data[20..]).read_i32::<BigEndian>().unwrap();
    let action_flags_start         = (&data[24..]).read_i32::<BigEndian>().unwrap();
    let _unknown0                  = (&data[28..]).read_i32::<BigEndian>().unwrap();
    let action_interrupts          = (&data[32..]).read_i32::<BigEndian>().unwrap();
    let entry_actions_start        = (&data[36..]).read_i32::<BigEndian>().unwrap();
    let exit_actions_start         = (&data[40..]).read_i32::<BigEndian>().unwrap();
    let action_pre_start           = (&data[44..]).read_i32::<BigEndian>().unwrap();
    let subaction_main_start      = (&data[48..]).read_i32::<BigEndian>().unwrap();
    let subaction_gfx_start       = (&data[52..]).read_i32::<BigEndian>().unwrap();
    let subaction_sfx_start       = (&data[56..]).read_i32::<BigEndian>().unwrap();
    let subaction_other_start     = (&data[60..]).read_i32::<BigEndian>().unwrap();
    let anchored_item_positions    = (&data[64..]).read_i32::<BigEndian>().unwrap();
    let gooey_bomb_positions       = (&data[68..]).read_i32::<BigEndian>().unwrap();
    let bone_ref1                  = (&data[72..]).read_i32::<BigEndian>().unwrap();
    let bone_ref2                  = (&data[76..]).read_i32::<BigEndian>().unwrap();
    let entry_action_overrides     = (&data[80..]).read_i32::<BigEndian>().unwrap();
    let exit_action_overrides      = (&data[84..]).read_i32::<BigEndian>().unwrap();
    let _unknown1                  = (&data[88..]).read_i32::<BigEndian>().unwrap();
    let samus_arm_cannon_positions = (&data[92..]).read_i32::<BigEndian>().unwrap();
    let _unknown2                  = (&data[96..]).read_i32::<BigEndian>().unwrap();
    let static_articles_start      = (&data[100..]).read_i32::<BigEndian>().unwrap();
    let entry_articles_start       = (&data[104..]).read_i32::<BigEndian>().unwrap();
    let flags1                     = (&data[116..]).read_u32::<BigEndian>().unwrap();
    let flags2                     = (&data[120..]).read_i32::<BigEndian>().unwrap();

    let sizes = get_sizes(data);

    let subaction_flags_num = sizes.iter().find(|x| x.offset == subaction_flags_start as usize).unwrap().size / SUB_ACTION_FLAGS_SIZE;
    let subaction_flags = subaction_flags(parent_data, &parent_data[subaction_flags_start as usize ..], subaction_flags_num);

    let model_visibility = model_visibility(parent_data, model_visibility_start);

    let action_flags_num = sizes.iter().find(|x| x.offset == action_flags_start as usize).unwrap().size / ACTION_FLAGS_SIZE;
    let action_flags = action_flags(&parent_data[action_flags_start as usize ..], action_flags_num);

    let entry_actions_num = sizes.iter().find(|x| x.offset == entry_actions_start as usize).unwrap().size / 4; // divide by integer size
    let entry_actions = script::scripts(parent_data, &parent_data[entry_actions_start as usize ..], entry_actions_num);
    let exit_actions = script::scripts(parent_data, &parent_data[exit_actions_start as usize ..], entry_actions_num);

    let subaction_main_num = sizes.iter().find(|x| x.offset == subaction_main_start as usize).unwrap().size / 4; // divide by integer size
    let subaction_main = script::scripts(parent_data, &parent_data[subaction_main_start as usize ..], subaction_main_num);
    let subaction_gfx = script::scripts(parent_data, &parent_data[subaction_gfx_start as usize ..], subaction_main_num);
    let subaction_sfx = script::scripts(parent_data, &parent_data[subaction_sfx_start as usize ..], subaction_main_num);
    let subaction_other = script::scripts(parent_data, &parent_data[subaction_other_start as usize ..], subaction_main_num);

    let attributes = fighter_attributes(&parent_data[attribute_start as usize ..]);
    let misc = misc_section::misc_section(&parent_data[misc_section_offset as usize ..], parent_data);

    ArcFighterData {
        subaction_flags,
        attributes,
        misc,
        action_flags,
        entry_actions,
        exit_actions,
        subaction_main,
        subaction_gfx,
        subaction_sfx,
        subaction_other,
        model_visibility,
        sse_attribute_start,
        common_action_flags_start,
        action_interrupts,
        action_pre_start,
        anchored_item_positions,
        gooey_bomb_positions,
        bone_ref1,
        bone_ref2,
        entry_action_overrides,
        exit_action_overrides,
        samus_arm_cannon_positions,
        static_articles_start,
        entry_articles_start,
        flags1,
        flags2,
    }
}

fn fighter_attributes(data: &[u8]) -> FighterAttributes {
    FighterAttributes {
        walk_init_vel:                     (&data[0x00..]).read_f32::<BigEndian>().unwrap(),
        walk_acc:                          (&data[0x04..]).read_f32::<BigEndian>().unwrap(),
        walk_max_vel:                      (&data[0x08..]).read_f32::<BigEndian>().unwrap(),
        ground_friction:                   (&data[0x0c..]).read_f32::<BigEndian>().unwrap(),
        dash_init_vel:                     (&data[0x10..]).read_f32::<BigEndian>().unwrap(),
        dash_run_acc_a:                    (&data[0x14..]).read_f32::<BigEndian>().unwrap(),
        dash_run_acc_b:                    (&data[0x18..]).read_f32::<BigEndian>().unwrap(),
        dash_run_term_vel:                 (&data[0x1c..]).read_f32::<BigEndian>().unwrap(),
        grounded_max_x_vel:                (&data[0x24..]).read_f32::<BigEndian>().unwrap(),
        dash_cancel_frame_window:          (&data[0x28..]).read_i32::<BigEndian>().unwrap(),
        guard_on_max_momentum:             (&data[0x2c..]).read_f32::<BigEndian>().unwrap(),
        jump_squat_frames:                 (&data[0x30..]).read_i32::<BigEndian>().unwrap(),
        jump_x_init_vel:                   (&data[0x34..]).read_f32::<BigEndian>().unwrap(),
        jump_y_init_vel:                   (&data[0x38..]).read_f32::<BigEndian>().unwrap(),
        jump_x_vel_ground_mult:            (&data[0x3c..]).read_f32::<BigEndian>().unwrap(),
        jump_x_init_term_vel:              (&data[0x40..]).read_f32::<BigEndian>().unwrap(),
        jump_y_init_vel_short:             (&data[0x44..]).read_f32::<BigEndian>().unwrap(),
        air_jump_x_mult:                   (&data[0x48..]).read_f32::<BigEndian>().unwrap(),
        air_jump_y_mult:                   (&data[0x4c..]).read_f32::<BigEndian>().unwrap(),
        footstool_init_vel:                (&data[0x50..]).read_f32::<BigEndian>().unwrap(),
        footstool_init_vel_short:          (&data[0x54..]).read_f32::<BigEndian>().unwrap(),
        meteor_cancel_delay:               (&data[0x5c..]).read_f32::<BigEndian>().unwrap(),
        num_jumps:                         (&data[0x60..]).read_u32::<BigEndian>().unwrap(),
        gravity:                           (&data[0x64..]).read_f32::<BigEndian>().unwrap(),
        term_vel:                          (&data[0x68..]).read_f32::<BigEndian>().unwrap(),
        air_friction_y:                    (&data[0x6c..]).read_f32::<BigEndian>().unwrap(),
        air_y_term_vel:                    (&data[0x70..]).read_f32::<BigEndian>().unwrap(),
        air_mobility_a:                    (&data[0x74..]).read_f32::<BigEndian>().unwrap(),
        air_mobility_b:                    (&data[0x78..]).read_f32::<BigEndian>().unwrap(),
        air_x_term_vel:                    (&data[0x7c..]).read_f32::<BigEndian>().unwrap(),
        air_friction_x:                    (&data[0x80..]).read_f32::<BigEndian>().unwrap(),
        fastfall_velocity:                 (&data[0x84..]).read_f32::<BigEndian>().unwrap(),
        air_x_term_vel_hard:               (&data[0x88..]).read_f32::<BigEndian>().unwrap(),
        glide_frame_window:                (&data[0x8c..]).read_u32::<BigEndian>().unwrap(),
        jab2_window:                       (&data[0x94..]).read_f32::<BigEndian>().unwrap(),
        jab3_window:                       (&data[0x98..]).read_f32::<BigEndian>().unwrap(),
        ftilt2_window:                     (&data[0x9c..]).read_f32::<BigEndian>().unwrap(),
        ftilt3_window:                     (&data[0xa0..]).read_f32::<BigEndian>().unwrap(),
        fsmash2_window:                    (&data[0xa4..]).read_f32::<BigEndian>().unwrap(),
        flip_dir_frame:                    (&data[0xa8..]).read_f32::<BigEndian>().unwrap(),
        weight:                            (&data[0xb0..]).read_f32::<BigEndian>().unwrap(),
        size:                              (&data[0xb4..]).read_f32::<BigEndian>().unwrap(),
        results_screen_size:               (&data[0xb8..]).read_f32::<BigEndian>().unwrap(),
        shield_size:                       (&data[0xc4..]).read_f32::<BigEndian>().unwrap(),
        shield_break_vel:                  (&data[0xc8..]).read_f32::<BigEndian>().unwrap(),
        shield_strength:                   (&data[0xcc..]).read_f32::<BigEndian>().unwrap(),
        respawn_platform_size:             (&data[0xd4..]).read_f32::<BigEndian>().unwrap(),
        edge_jump_x_vel:                   (&data[0xf4..]).read_f32::<BigEndian>().unwrap(),
        edge_jump_y_vel:                   (&data[0xfc..]).read_f32::<BigEndian>().unwrap(),
        item_throw_strength:               (&data[0x118..]).read_f32::<BigEndian>().unwrap(),
        projectile_item_move_speed:        (&data[0x128..]).read_f32::<BigEndian>().unwrap(),
        projectile_item_move_speed_dash_f: (&data[0x12c..]).read_f32::<BigEndian>().unwrap(),
        projectile_item_move_speed_dash_b: (&data[0x130..]).read_f32::<BigEndian>().unwrap(),
        light_landing_lag:                 (&data[0x138..]).read_f32::<BigEndian>().unwrap(),
        normal_landing_lag:                (&data[0x13c..]).read_f32::<BigEndian>().unwrap(),
        nair_landing_lag:                  (&data[0x140..]).read_f32::<BigEndian>().unwrap(),
        fair_landing_lag:                  (&data[0x144..]).read_f32::<BigEndian>().unwrap(),
        bair_landing_lag:                  (&data[0x148..]).read_f32::<BigEndian>().unwrap(),
        uair_landing_lag:                  (&data[0x14c..]).read_f32::<BigEndian>().unwrap(),
        dair_landing_lag:                  (&data[0x150..]).read_f32::<BigEndian>().unwrap(),
        term_vel_hard_frames:              (&data[0x154..]).read_u32::<BigEndian>().unwrap(),
        hip_n_bone:                        (&data[0x158..]).read_u32::<BigEndian>().unwrap(),
        tag_height_value:                  (&data[0x15c..]).read_f32::<BigEndian>().unwrap(),
        walljump_x_vel:                    (&data[0x164..]).read_f32::<BigEndian>().unwrap(),
        walljump_y_vel:                    (&data[0x168..]).read_f32::<BigEndian>().unwrap(),
        lhand_n_bone:                      (&data[0x180..]).read_u32::<BigEndian>().unwrap(),
        rhand_n_bone:                      (&data[0x184..]).read_u32::<BigEndian>().unwrap(),
        water_y_acc:                       (&data[0x18c..]).read_f32::<BigEndian>().unwrap(),
        spit_star_size:                    (&data[0x1a4..]).read_f32::<BigEndian>().unwrap(),
        spit_star_damage:                  (&data[0x1a8..]).read_u32::<BigEndian>().unwrap(),
        egg_size:                          (&data[0x1ac..]).read_f32::<BigEndian>().unwrap(),
        hip_n_bone2:                       (&data[0x1cc..]).read_u32::<BigEndian>().unwrap(),
        x_rot_n_bone:                      (&data[0x1e0..]).read_u32::<BigEndian>().unwrap(),
    }
}

const _ARC_FIGHTER_DATA_HEADER_SIZE: usize = 0x7c;
#[derive(Debug)]
pub struct ArcFighterData {
    pub subaction_flags: Vec<SubactionFlags>,
    pub attributes: FighterAttributes,
    pub misc: MiscSection,
    pub action_flags: Vec<ActionFlags>,
    pub entry_actions: Vec<Script>,
    pub exit_actions: Vec<Script>,
    pub subaction_main: Vec<Script>,
    pub subaction_gfx: Vec<Script>,
    pub subaction_sfx: Vec<Script>,
    pub subaction_other: Vec<Script>,
    pub model_visibility: ModelVisibility,
    sse_attribute_start: i32,
    common_action_flags_start: i32,
    action_interrupts: i32,
    action_pre_start: i32,
    anchored_item_positions: i32,
    gooey_bomb_positions: i32,
    bone_ref1: i32,
    bone_ref2: i32,
    entry_action_overrides: i32,
    exit_action_overrides: i32,
    samus_arm_cannon_positions: i32,
    static_articles_start: i32,
    entry_articles_start: i32,
    flags1: u32,
    flags2: i32,
}

#[derive(Serialize, Clone, Debug)]
pub struct FighterAttributes {
    pub walk_init_vel: f32,
    pub walk_acc: f32,
    pub walk_max_vel: f32,
    pub ground_friction: f32,
    pub dash_init_vel: f32,
    pub dash_run_acc_a: f32,
    pub dash_run_acc_b: f32,
    pub dash_run_term_vel: f32,
    pub grounded_max_x_vel: f32,
    pub dash_cancel_frame_window: i32, // spreadsheet is unsure
    pub guard_on_max_momentum: f32,
    pub jump_squat_frames: i32,
    pub jump_x_init_vel: f32,
    pub jump_y_init_vel: f32,
    pub jump_x_vel_ground_mult: f32,
    pub jump_x_init_term_vel: f32, // TODO: does melee include this max in name?
    pub jump_y_init_vel_short: f32,
    pub air_jump_x_mult: f32,
    pub air_jump_y_mult: f32,
    pub footstool_init_vel: f32,
    pub footstool_init_vel_short: f32,
    pub meteor_cancel_delay: f32,
    pub num_jumps: u32,
    pub gravity: f32,
    pub term_vel: f32,
    pub air_friction_y: f32,
    pub air_y_term_vel: f32,
    pub air_mobility_a: f32,
    pub air_mobility_b: f32,
    pub air_x_term_vel: f32,
    pub air_friction_x: f32,
    pub fastfall_velocity: f32,
    pub air_x_term_vel_hard: f32,
    pub glide_frame_window: u32,
    pub jab2_window: f32,
    pub jab3_window: f32,
    pub ftilt2_window: f32,
    pub ftilt3_window: f32,
    pub fsmash2_window: f32,
    pub flip_dir_frame: f32,
    pub weight: f32,
    pub size: f32,
    pub results_screen_size: f32,
    pub shield_size: f32,
    pub shield_break_vel: f32,
    pub shield_strength: f32,
    pub respawn_platform_size: f32,
    pub edge_jump_x_vel: f32,
    pub edge_jump_y_vel: f32,
    pub item_throw_strength: f32,
    pub projectile_item_move_speed: f32,
    pub projectile_item_move_speed_dash_f: f32,
    pub projectile_item_move_speed_dash_b: f32,
    pub light_landing_lag: f32,
    pub normal_landing_lag: f32,
    pub nair_landing_lag: f32,
    pub fair_landing_lag: f32,
    pub bair_landing_lag: f32,
    pub uair_landing_lag: f32,
    pub dair_landing_lag: f32,
    pub term_vel_hard_frames: u32,
    pub hip_n_bone: u32, // spreadsheet is unsure
    pub tag_height_value: f32,
    pub walljump_x_vel: f32, // used for normal walljumps and walljump techs
    pub walljump_y_vel: f32, // used for normal walljumps and walljump techs
    pub lhand_n_bone: u32,
    pub rhand_n_bone: u32,
    pub water_y_acc: f32,
    pub spit_star_size: f32,
    pub spit_star_damage: u32,
    pub egg_size: f32,
    pub hip_n_bone2: u32,
    pub x_rot_n_bone: u32, // bone to be grabbed from?
}

bitflags! {
    #[derive(Serialize)]
    pub struct AnimationFlags: u8 {
        const NONE                      = 0x0;
        const NO_OUT_TRANSITION         = 0x1;
        const LOOP                      = 0x2;
        const MOVES_CHARACTER           = 0x4;
        const FIXED_TRANSLATION         = 0x8;
        const FIXED_ROTATION            = 0x10;
        const FIXED_SCALE               = 0x20;
        const TRANSITION_OUT_FROM_START = 0x40;
        const UNKNOWN                   = 0x80;
    }
}

fn subaction_flags(parent_data: &[u8], data: &[u8], num: usize) -> Vec<SubactionFlags> {
    let mut result = vec!();
    let num = num + 1;
    for i in 0..num {
        let in_translation_time =   data[i * SUB_ACTION_FLAGS_SIZE + 0];
        let animation_flags_int =   data[i * SUB_ACTION_FLAGS_SIZE + 1];
        //  padding               (&data[i * SUB_ACTION_FLAGS_SIZE + 2..]).read_u16
        let string_offset       = (&data[i * SUB_ACTION_FLAGS_SIZE + 4..]).read_i32::<BigEndian>().unwrap();

        let animation_flags = AnimationFlags::from_bits(animation_flags_int).unwrap();
        let name = if string_offset == 0 {
            String::new()
        } else {
            util::parse_str(&parent_data[string_offset as usize ..]).unwrap().to_string()
        };

        result.push(SubactionFlags {
            in_translation_time,
            animation_flags,
            name,
        });
    }
    result
}

const SUB_ACTION_FLAGS_SIZE: usize = 0x8;
#[derive(Debug)]
pub struct SubactionFlags {
    pub in_translation_time: u8,
    pub animation_flags:     AnimationFlags,
    pub name:                String,
}

fn model_visibility(parent_data: &[u8], model_visibility_start: i32) -> ModelVisibility {
    let reference_offset  = (&parent_data[model_visibility_start as usize + 0x00..]).read_i32::<BigEndian>().unwrap();
    let bone_switch_count = (&parent_data[model_visibility_start as usize + 0x04..]).read_i32::<BigEndian>().unwrap();
    let defaults_offset   = (&parent_data[model_visibility_start as usize + 0x08..]).read_i32::<BigEndian>().unwrap();
    let defaults_count    = (&parent_data[model_visibility_start as usize + 0x0c..]).read_i32::<BigEndian>().unwrap();

    let mut references = vec!();
    if reference_offset != 0 {
        // this works because the data at reference_offset, defaults_offset and model_visibility_start are stored sequentially
        let reference_count = if defaults_offset == 0 {
            (model_visibility_start - reference_offset) / VISIBILITY_REFERENCE_SIZE as i32
        } else {
            (defaults_offset - reference_offset) / VISIBILITY_REFERENCE_SIZE as i32
        };
        if reference_count < 0 {
            error!("Oh no the reference_count calculation is messed up, please handle this case properly");
            return ModelVisibility {
                references: vec!(),
                defaults: vec!(),
            };
        }

        for reference_i in 0..reference_count as usize {
            let bone_switch_offset = (&parent_data[reference_offset as usize + VISIBILITY_REFERENCE_SIZE * reference_i ..]).read_i32::<BigEndian>().unwrap() as usize;
            let mut bone_switches = vec!();
            if bone_switch_offset != 0 {

                for bone_switch_i in 0..bone_switch_count as usize {
                    let visibility_group_list = util::list_offset(&parent_data[bone_switch_offset + util::LIST_OFFSET_SIZE * bone_switch_i ..]);
                    let mut groups = vec!();

                    for visibility_group_i in 0..visibility_group_list.count as usize {
                        let bone_list = util::list_offset(&parent_data[visibility_group_list.start_offset as usize + util::LIST_OFFSET_SIZE * visibility_group_i ..]);
                        let mut bones = vec!();

                        for bone_i in 0..bone_list.count as usize {
                            let bone = (&parent_data[bone_list.start_offset as usize + 4 * bone_i ..]).read_i32::<BigEndian>().unwrap();
                            bones.push(bone);
                        }

                        groups.push(VisibilityGroup { bones });
                    }

                    bone_switches.push(VisibilityBoneSwitch { groups });
                }
            }
            references.push(VisibilityReference { bone_switches });
        }
    }

    let mut defaults = vec!();
    for i in 0..defaults_count as usize {
        let switch_index = (&parent_data[defaults_offset as usize + VISIBILITY_DEFAULT_SIZE * i     ..]).read_i32::<BigEndian>().unwrap();
        let group_index  = (&parent_data[defaults_offset as usize + VISIBILITY_DEFAULT_SIZE * i + 4 ..]).read_i32::<BigEndian>().unwrap();

        defaults.push(VisibilityDefault { switch_index, group_index });
    }

    ModelVisibility {
        references,
        defaults,
    }
}

#[derive(Debug)]
pub struct ModelVisibility {
    pub references: Vec<VisibilityReference>,
    pub defaults:   Vec<VisibilityDefault>,
}

const VISIBILITY_REFERENCE_SIZE: usize = 0x4;
#[derive(Debug)]
pub struct VisibilityReference {
    pub bone_switches: Vec<VisibilityBoneSwitch>,
}

#[derive(Debug)]
pub struct VisibilityBoneSwitch {
    pub groups: Vec<VisibilityGroup>,
}

/// Enabling a `VisibilityGroup` will disable all other groups in the same `VisibilityBoneSwitch`
#[derive(Debug)]
pub struct VisibilityGroup {
    pub bones: Vec<i32>,
}

const VISIBILITY_DEFAULT_SIZE: usize = 0x8;
/// Enables the `VisibilityGroup` with the matching `switch_index` and `group_index` for all `VisibilityReferences`s.
/// When a new subaction is started, everything is set invisible and then all `VisibilityDefault`s are run.
#[derive(Debug)]
pub struct VisibilityDefault {
    pub switch_index: i32,
    pub group_index: i32,
}

fn action_flags(data: &[u8], num: usize) -> Vec<ActionFlags> {
    let mut result = vec!();
    for i in 0..num {
        result.push(ActionFlags {
            flag1: (&data[i * ACTION_FLAGS_SIZE + 0x0..]).read_u32::<BigEndian>().unwrap(),
            flag2: (&data[i * ACTION_FLAGS_SIZE + 0x4..]).read_u32::<BigEndian>().unwrap(),
            flag3: (&data[i * ACTION_FLAGS_SIZE + 0x8..]).read_u32::<BigEndian>().unwrap(),
            flag4: (&data[i * ACTION_FLAGS_SIZE + 0xc..]).read_u32::<BigEndian>().unwrap(),
        });
    }
    result
}

const ACTION_FLAGS_SIZE: usize = 0x10;
#[derive(Debug)]
pub struct ActionFlags {
    pub flag1: u32,
    pub flag2: u32,
    pub flag3: u32,
    pub flag4: u32,
}

struct OffsetSizePair {
    offset: usize,
    size: usize,
}

fn get_sizes(data: &[u8]) -> Vec<OffsetSizePair> {
    let mut pairs = vec!();
    for i in 0..27 {
        let offset = (&data[i * 4 ..]).read_i32::<BigEndian>().unwrap() as usize;
        if offset != 0 {
            pairs.push(OffsetSizePair { offset, size: 0 });
        }
    }

    pairs.sort_by_key(|x| x.offset);

    // fill in size for most elements
    for i in 0..pairs.len()-1 {
        pairs[i].size = pairs[i + 1].offset - pairs[i].offset
    }

    // Just pop the last element, so if we try to access it we get a panic
    pairs.pop();

    pairs
}