summaryrefslogtreecommitdiff
path: root/mm/2s2h/SaveManager/BinarySaveConverter.cpp
blob: 750430a07d21f1233610786efc3b1f1dd373d6e6 (plain)
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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
#include <fstream>
#include <filesystem>
#include <nlohmann/json.hpp>

#include "2s2h/BenGui/Notification.h"
#include "2s2h/SaveManager/SaveManager.h"
#include <ship/utils/binarytools/BinaryReader.h>
#include <string>
#include <spdlog/spdlog.h>
#include <ship/Context.h>
#include <ship/window/Window.h>

extern "C" {
#include "z64math.h"
#include "macros.h"
#include "src/overlays/gamestates/ovl_file_choose/z_file_select.h"
extern FileSelectState* gFileSelectState;
}

using json = nlohmann::json;

// WOA WOA WOA! What are you doing here?!
// The structs in this file should never be modified.

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_ItemEquips {
    /* 0x00 */ u8 buttonItems[4][4];  // "register_item"
    /* 0x10 */ u8 cButtonSlots[4][4]; // "register_item_pt"
    /* 0x20 */ u16 equipment;
} Legacy_ItemEquips; // size = 0x22

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_Inventory {
    /* 0x00 */ u8 items[48];        // "item_register", first 24 elements are normal items and the other 24 are masks
    /* 0x30 */ s8 ammo[24];         // "item_count"
    /* 0x48 */ u32 upgrades;        // "non_equip_register" some bits are wallet upgrades
    /* 0x4C */ u32 questItems;      // "collect_register"
    /* 0x50 */ u8 dungeonItems[10]; // "key_compass_map"
    /* 0x5A */ s8 dungeonKeys[9];   // "key_register"
    /* 0x63 */ s8 defenseHearts;
    /* 0x64 */ s8 strayFairies[10];                 // "orange_fairy"
    /* 0x6E */ char dekuPlaygroundPlayerName[3][8]; // "degnuts_memory_name" Stores playerName (8 char) over (3 days)
                                                    // when getting a new high score
} Legacy_Inventory;                                 // size = 0x88

typedef struct {
    /* 0x0 */ s16 x;
    /* 0x2 */ s16 y;
    /* 0x4 */ s16 z;
} Legacy_Vec3s; // size = 0x6

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_HorseData {
    /* 0x0 */ s16 sceneId;      // "spot_no"
    /* 0x2 */ Legacy_Vec3s pos; // "horse_x", "horse_y" and "horse_z"
    /* 0x8 */ s16 yaw;          // "horse_a"
} Legacy_HorseData;             // size = 0xA

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_RespawnData {
    /* 0x00 */ Vec3f pos;
    /* 0x0C */ s16 yaw;
    /* 0x0E */ s16 playerParams;
    /* 0x10 */ u16 entrance;
    /* 0x12 */ u8 roomIndex;
    /* 0x13 */ s8 data;
    /* 0x14 */ u32 tempSwitchFlags;
    /* 0x18 */ u32 unk_18;
    /* 0x1C */ u32 tempCollectFlags;
} Legacy_RespawnData; // size = 0x20

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_PermanentSceneFlags {
    /* 0x00 */ u32 chest;
    /* 0x04 */ u32 switch0;
    /* 0x08 */ u32 switch1;
    /* 0x0C */ u32 clearedRoom;
    /* 0x10 */ u32 collectible;
    /* 0x14 */ u32 unk_14; // varies based on scene. For dungeons, floors visited.
    /* 0x18 */ u32 rooms;
} Legacy_PermanentSceneFlags; // size = 0x1C

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_CycleSceneFlags {
    /* 0x00 */ u32 chest;
    /* 0x04 */ u32 switch0;
    /* 0x08 */ u32 switch1;
    /* 0x0C */ u32 clearedRoom;
    /* 0x10 */ u32 collectible;
} Legacy_CycleSceneFlags; // size = 0x14

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_SaveOptions {
    /* 0x0 */ u16 optionId;       // "option_id"
    /* 0x2 */ u8 language;        // "j_n"
    /* 0x3 */ u8 audioSetting;    // "s_sound"
    /* 0x4 */ u8 languageSetting; // "language"
    /* 0x5 */ u8 zTargetSetting;  // "z_attention", 0: Switch; 1: Hold
} Legacy_SaveOptions;             // size = 0x6

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_SavePlayerData {
    /* 0x00 */ char newf[6];             // "newf"               Will always be "ZELDA3 for a valid save
    /* 0x06 */ u16 threeDayResetCount;   // "savect"
    /* 0x08 */ char playerName[8];       // "player_name"
    /* 0x10 */ s16 healthCapacity;       // "max_life"
    /* 0x12 */ s16 health;               // "now_life"
    /* 0x14 */ s8 magicLevel;            // 0 for no magic/new load, 1 for magic, 2 for double magic "magic_max"
    /* 0x15 */ s8 magic;                 // current magic available for use "magic_now"
    /* 0x16 */ s16 rupees;               // "lupy_count"
    /* 0x18 */ u16 swordHealth;          // "long_sword_hp"
    /* 0x1A */ u16 tatlTimer;            // "navi_timer"
    /* 0x1C */ u8 isMagicAcquired;       // "magic_mode"
    /* 0x1D */ u8 isDoubleMagicAcquired; // "magic_ability"
    /* 0x1E */ u8 doubleDefense;         // "life_ability"
    /* 0x1F */ u8 unk_1F;                // "ocarina_round"
    /* 0x20 */ u8 unk_20;                // "first_memory"
    /* 0x22 */ u16 owlActivationFlags;   // "memory_warp_point"
    /* 0x24 */ u8 unk_24;                // "last_warp_pt"
    /* 0x26 */ s16 savedSceneId;         // "scene_data_ID"
} Legacy_SavePlayerData;                 // size = 0x28

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_SaveInfo {
    /* 0x000 */ Legacy_SavePlayerData playerData;
    /* 0x028 */ Legacy_ItemEquips equips;
    /* 0x04C */ Legacy_Inventory inventory;
    /* 0x0D4 */ Legacy_PermanentSceneFlags permanentSceneFlags[120];
    /* 0xDF4 */ s8 unk_DF4[0x54];
    /* 0xE48 */ u32 dekuPlaygroundHighScores[3];
    /* 0xE54 */ u32 pictoFlags0; // Flags set by `PictoActor`s if pictograph is valid
    /* 0xE58 */ u32 pictoFlags1; // Flags set by Snap_ValidatePictograph() to record errors; volatile since that
                                 // function is run many times in succession
    /* 0xE5C */ u32 unk_E5C;
    /* 0xE60 */ u32 unk_E60;
    /* 0xE64 */ u32 unk_E64[7];       // Invadepoh flags
    /* 0xE80 */ u32 scenesVisible[7]; // tingle maps and clouded regions on pause map. Stores scenes bitwise for up to
                                      // 224 scenes even though there are not that many scenes
    /* 0xE9C */ u32 skullTokenCount;  // upper 16 bits store Swamp skulls, lower 16 bits store Ocean skulls
    /* 0xEA0 */ u32 unk_EA0;          // Gossic stone heart piece flags
    /* 0xEA4 */ u32 unk_EA4;
    /* 0xEA8 */ u32 unk_EA8[2];  // Related to blue warps
    /* 0xEB0 */ u32 stolenItems; // Items stolen by Takkuri and given to Curiosity Shop Man
    /* 0xEB4 */ u32 unk_EB4;
    /* 0xEB8 */ u32 highScores[7];
    /* 0xED4 */ u8 weekEventReg[100];        // "week_event_reg"
    /* 0xF38 */ u32 regionsVisited;          // "area_arrival"
    /* 0xF3C */ u32 worldMapCloudVisibility; // "cloud_clear"
    /* 0xF40 */ u8 unk_F40;                  // "oca_rec_flag"                   has scarecrows song
    /* 0xF41 */ u8 scarecrowSpawnSongSet;    // "oca_rec_flag8"
    /* 0xF42 */ u8 scarecrowSpawnSong[128];
    /* 0xFC2 */ s8 bombersCaughtNum;        // "aikotoba_index"
    /* 0xFC3 */ s8 bombersCaughtOrder[5];   // "aikotoba_table"
    /* 0xFC8 */ s8 lotteryCodes[3][3];      // "numbers_table", Preset lottery codes
    /* 0xFD1 */ s8 spiderHouseMaskOrder[6]; // "kinsta_color_table"
    /* 0xFD7 */ s8 bomberCode[5];           // "bombers_aikotoba_table"
    /* 0xFDC */ Legacy_HorseData horseData;
    /* 0xFE6 */ u16 checksum; // "check_sum"
} Legacy_SaveInfo;            // size = 0xFE8

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_Save {
    /* 0x00 */ s32 entrance;    // "scene_no"
    /* 0x04 */ u8 equippedMask; // "player_mask"
    /* 0x05 */ u8 isFirstCycle; // "opening_flag"
    /* 0x06 */ u8 unk_06;
    /* 0x07 */ u8 linkAge;        // "link_age"
    /* 0x08 */ s32 cutsceneIndex; // "day_time"
    /* 0x0C */ u16 time;          // "zelda_time"
    /* 0x0E */ u16 owlSaveLocation;
    /* 0x10 */ s32 isNight;         // "asahiru_fg"
    /* 0x14 */ s32 timeSpeedOffset; // "change_zelda_time"
    /* 0x18 */ s32 day;             // "totalday"
    /* 0x1C */ s32 eventDayCount;   // "eventday"
    /* 0x20 */ u8 playerForm;       // "player_character"
    /* 0x21 */ u8 snowheadCleared;  // "spring_flag"
    /* 0x22 */ u8 hasTatl;          // "bell_flag"
    /* 0x23 */ u8 isOwlSave;
    /* 0x24 */ Legacy_SaveInfo saveInfo;
} Legacy_Save; // size = 0x100C

//
//   DO   NOT   MODIFY   THIS   FILE!
//
typedef struct Legacy_SaveContext {
    /* 0x0000 */ Legacy_Save save;
    /* 0x100C */ u8 eventInf[8]; // "event_inf"
    /* 0x1014 */ u8 unk_1014;    // "stone_set_flag"
    /* 0x1015 */ u8 bButtonStatus;
    /* 0x1016 */ u16 jinxTimer;
    /* 0x1018 */ s16 rupeeAccumulator;              // "lupy_udct"
    /* 0x101A */ u8 bottleTimerStates[6];           // See the `BottleTimerState` enum. "bottle_status"
    /* 0x1020 */ OSTime bottleTimerStartOsTimes[6]; // The osTime when the timer starts. "bottle_ostime"
    /* 0x1050 */ u64 bottleTimerTimeLimits[6];      // The original total time given before the timer expires, in
                                                    // centiseconds (1/100th sec). "bottle_sub"
    /* 0x1080 */ u64 bottleTimerCurTimes[6];        // The remaining time left before the timer expires, in centiseconds
                                                    // (1/100th sec). "bottle_time"
    /* 0x10B0 */ OSTime
        bottleTimerPausedOsTimes[6]; // The cumulative osTime spent with the timer paused. "bottle_stop_time"
    /* 0x10E0 */ u8
        pictoPhotoI5[((160 * 112) * 5 / 8)]; // buffer containing the pictograph photo, compressed to I5 from I8
    /* 0x3CA0 */ s32 fileNum;                // "file_no"
    /* 0x3CA4 */ s16 powderKegTimer;         // "big_bom_timer"
    /* 0x3CA6 */ u8 unk_3CA6;
    /* 0x3CA7 */ u8 unk_3CA7;                   // "day_night_flag"
    /* 0x3CA8 */ s32 gameMode;                  // "mode"
    /* 0x3CAC */ s32 sceneLayer;                // "counter"
    /* 0x3CB0 */ s32 respawnFlag;               // "restart_flag"
    /* 0x3CB4 */ Legacy_RespawnData respawn[8]; // "restart_data"
    /* 0x3DB4 */ f32 entranceSpeed;             // "player_wipe_speedF"
    /* 0x3DB8 */ u16 entranceSound;             // "player_wipe_door_SE"
    /* 0x3DBA */ u8 unk_3DBA;                   // "player_wipe_item"
    /* 0x3DBB */ u8 retainWeatherMode;          // "next_walk"
    /* 0x3DBC */ s16 dogParams;                 // OoT leftover. "dog_flag"
    /* 0x3DBE */ u8 envHazardTextTriggerFlags;  // "guide_status"
    /* 0x3DBF */ u8 showTitleCard;              // "name_display"
    /* 0x3DC0 */ s16 nayrusLoveTimer;           // remnant of OoT, "shield_magic_timer"
    /* 0x3DC2 */ u8 unk_3DC2;                   // "pad1"
    /* 0x3DC8 */ OSTime postmanTimerStopOsTime; // The osTime when the timer stops for the postman minigame. "get_time"
    /* 0x3DD0 */ u8 timerStates[7];             // See the `TimerState` enum. "event_fg"
    /* 0x3DD7 */ u8 timerDirections[7];         // See the `TimerDirection` enum. "calc_flag"
    /* 0x3DE0 */ u64 timerCurTimes[7]; // For countdown, the remaining time left. For countup, the time since the start.
                                       // In centiseconds (1/100th sec). "event_ostime"
    /* 0x3E18 */ u64 timerTimeLimits[7]; // The original total time given for the timer to count from, in centiseconds
                                         // (1/100th sec). "event_sub"
    /* 0x3E50 */ OSTime timerStartOsTimes[7]; // The osTime when the timer starts. "func_time"
    /* 0x3E88 */ u64 timerStopTimes[7]; // The total amount of time taken between the start and end of the timer, in
                                        // centiseconds (1/100th sec). "func_end_time"
    /* 0x3EC0 */ OSTime timerPausedOsTimes[7]; // The cumulative osTime spent with the timer paused. "func_stop_time"
    /* 0x3EF8 */ s16 timerX[7];                // "event_xp"
    /* 0x3F06 */ s16 timerY[7];                // "event_yp"
    /* 0x3F14 */ s16 unk_3F14;                 // "character_change"
    /* 0x3F16 */ u8 seqId;                     // "old_bgm"
    /* 0x3F17 */ u8 ambienceId;                // "old_env"
    /* 0x3F18 */ u8 buttonStatus[6];           // "button_item"
    /* 0x3F1E */ u8 hudVisibilityForceButtonAlphasByStatus; // if btn alphas are updated through
                                                            // Interface_UpdateButtonAlphas, instead update them through
                                                            // Interface_UpdateButtonAlphasByStatus "ck_fg"
    /* 0x3F20 */ u16 nextHudVisibility;  // triggers the hud to change visibility to the requested value. Reset to
                                         // HUD_VISIBILITY_IDLE when target is reached "alpha_type"
    /* 0x3F22 */ u16 hudVisibility;      // current hud visibility "prev_alpha_type"
    /* 0x3F24 */ u16 hudVisibilityTimer; // number of frames in the transition to a new hud visibility. Used to step
                                         // alpha "alpha_count"
    /* 0x3F26 */ u16
        prevHudVisibility; // used to store and recover hud visibility for pause menu and text boxes "last_time_type"
    /* 0x3F28 */ s16 magicState;          // determines magic meter behavior on each frame "magic_flag"
    /* 0x3F2A */ s16 isMagicRequested;    // a request to add magic has been given "recovery_magic_flag"
    /* 0x3F2C */ s16 magicFlag;           // Set to 0 in func_80812D94(), otherwise unused "keep_magic_flag"
    /* 0x3F2E */ s16 magicCapacity;       // maximum magic available "magic_now_max"
    /* 0x3F30 */ s16 magicFillTarget;     // target used to fill magic "magic_now_now"
    /* 0x3F32 */ s16 magicToConsume;      // accumulated magic that is requested to be consumed "magic_used"
    /* 0x3F34 */ s16 magicToAdd;          // accumulated magic that is requested to be added "magic_recovery"
    /* 0x3F36 */ u16 mapIndex;            // "scene_ID"
    /* 0x3F38 */ u16 minigameStatus;      // "yabusame_mode"
    /* 0x3F3A */ u16 minigameScore;       // "yabusame_total"
    /* 0x3F3C */ u16 minigameHiddenScore; // "yabusame_out_ct"
    /* 0x3F3E */ u8 unk_3F3E;             // "no_save"
    /* 0x3F3F */ u8 flashSaveAvailable;   // "flash_flag"
    /* 0x3F40 */ Legacy_SaveOptions options;
    /* 0x3F46 */ u16 forcedSeqId;              // "NottoriBgm"
    /* 0x3F48 */ u8 cutsceneTransitionControl; // "fade_go"
    /* 0x3F4A */ u16 nextCutsceneIndex;        // "next_daytime"
    /* 0x3F4C */ u8 cutsceneTrigger;           // "doukidemo"
    /* 0x3F4D */ u8 chamberCutsceneNum;        // remnant of OoT "Kenjya_no"
    /* 0x3F4E */ u16 nextDayTime;              // "next_zelda_time"
    /* 0x3F50 */ u8 transFadeDuration;         // "fade_speed"
    /* 0x3F51 */ u8 transWipeSpeed;            // "wipe_speed"           transition related
    /* 0x3F52 */ u16 skyboxTime;               // "kankyo_time"
    /* 0x3F54 */ u8 dogIsLost;                 // "dog_event_flag"
    /* 0x3F55 */ u8 nextTransitionType;        // "next_wipe"
    /* 0x3F56 */ s16 worldMapArea;             // "area_type"
    /* 0x3F58 */ s16 sunsSongState;            // "sunmoon_flag"
    /* 0x3F5A */ s16 healthAccumulator;        // "life_mode"
    /* 0x3F5C */ s32 unk_3F5C;                 // "bet_rupees"
    /* 0x3F60 */ u8 screenScaleFlag;           // "framescale_flag"
    /* 0x3F64 */ f32 screenScale;              // "framescale_scale"
    /* 0x3F68 */ Legacy_CycleSceneFlags
        cycleSceneFlags[120];      // Scene flags that are temporarily stored over the duration of a single 3-day cycle
    /* 0x48C8 */ u16 dungeonIndex; // "scene_id_mix"
    /* 0x48CA */ u8 masksGivenOnMoon[27]; // bit-packed, masks given away on the Moon. "mask_mask_bit"
} Legacy_SaveContext;                     // size = 0x48C8

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_ItemEquips& itemEquips) {
    j = json{
        { "buttonItems", itemEquips.buttonItems },
        { "cButtonSlots", itemEquips.cButtonSlots },
        { "equipment", itemEquips.equipment },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_Inventory& inventory) {
    // Setup and copy u8 arrays to avoid json treating char[] as strings
    // These char[] are not null-terminated, so saving as strings causes overflow/corruption
    uint8_t dekuPlaygroundPlayerName[3][8];
    memcpy(dekuPlaygroundPlayerName, inventory.dekuPlaygroundPlayerName, sizeof(dekuPlaygroundPlayerName));

    j = json{
        { "items", inventory.items },
        { "ammo", inventory.ammo },
        { "upgrades", inventory.upgrades },
        { "questItems", inventory.questItems },
        { "dungeonItems", inventory.dungeonItems },
        { "dungeonKeys", inventory.dungeonKeys },
        { "defenseHearts", inventory.defenseHearts },
        { "strayFairies", inventory.strayFairies },
        { "dekuPlaygroundPlayerName", dekuPlaygroundPlayerName },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_PermanentSceneFlags& permanentSceneFlags) {
    j = json{
        { "chest", permanentSceneFlags.chest },
        { "switch0", permanentSceneFlags.switch0 },
        { "switch1", permanentSceneFlags.switch1 },
        { "clearedRoom", permanentSceneFlags.clearedRoom },
        { "collectible", permanentSceneFlags.collectible },
        { "unk_14", permanentSceneFlags.unk_14 },
        { "rooms", permanentSceneFlags.rooms },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_SavePlayerData& savePlayerData) {
    // Setup and copy u8 arrays to avoid json treating char[] as strings
    // These char[] are not null-terminated, so saving as strings causes overflow/corruption
    u8 newf[6];
    u8 playerName[8];
    memcpy(newf, savePlayerData.newf, sizeof(newf));
    memcpy(playerName, savePlayerData.playerName, sizeof(playerName));

    j = json{
        { "newf", newf },
        { "threeDayResetCount", savePlayerData.threeDayResetCount },
        { "playerName", playerName },
        { "healthCapacity", savePlayerData.healthCapacity },
        { "health", savePlayerData.health },
        { "magicLevel", savePlayerData.magicLevel },
        { "magic", savePlayerData.magic },
        { "rupees", savePlayerData.rupees },
        { "swordHealth", savePlayerData.swordHealth },
        { "tatlTimer", savePlayerData.tatlTimer },
        { "isMagicAcquired", savePlayerData.isMagicAcquired },
        { "isDoubleMagicAcquired", savePlayerData.isDoubleMagicAcquired },
        { "doubleDefense", savePlayerData.doubleDefense },
        { "unk_1F", savePlayerData.unk_1F },
        { "unk_20", savePlayerData.unk_20 },
        { "owlActivationFlags", savePlayerData.owlActivationFlags },
        { "unk_24", savePlayerData.unk_24 },
        { "savedSceneId", savePlayerData.savedSceneId },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_Vec3s& vec) {
    j = json{
        { "x", vec.x },
        { "y", vec.y },
        { "z", vec.z },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_HorseData& horseData) {
    j = json{
        { "sceneId", horseData.sceneId },
        { "pos", horseData.pos },
        { "yaw", horseData.yaw },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_SaveInfo& saveInfo) {
    j = json{
        { "playerData", saveInfo.playerData },
        { "equips", saveInfo.equips },
        { "inventory", saveInfo.inventory },
        { "permanentSceneFlags", saveInfo.permanentSceneFlags },
        { "unk_DF4", saveInfo.unk_DF4 },
        { "dekuPlaygroundHighScores", saveInfo.dekuPlaygroundHighScores },
        { "pictoFlags0", saveInfo.pictoFlags0 },
        { "pictoFlags1", saveInfo.pictoFlags1 },
        { "unk_E5C", saveInfo.unk_E5C },
        { "unk_E60", saveInfo.unk_E60 },
        { "unk_E64", saveInfo.unk_E64 },
        { "scenesVisible", saveInfo.scenesVisible },
        { "skullTokenCount", saveInfo.skullTokenCount },
        { "unk_EA0", saveInfo.unk_EA0 },
        { "unk_EA4", saveInfo.unk_EA4 },
        { "unk_EA8", saveInfo.unk_EA8 },
        { "stolenItems", saveInfo.stolenItems },
        { "unk_EB4", saveInfo.unk_EB4 },
        { "highScores", saveInfo.highScores },
        { "weekEventReg", saveInfo.weekEventReg },
        { "regionsVisited", saveInfo.regionsVisited },
        { "worldMapCloudVisibility", saveInfo.worldMapCloudVisibility },
        { "unk_F40", saveInfo.unk_F40 },
        { "scarecrowSpawnSongSet", saveInfo.scarecrowSpawnSongSet },
        { "scarecrowSpawnSong", saveInfo.scarecrowSpawnSong },
        { "bombersCaughtNum", saveInfo.bombersCaughtNum },
        { "bombersCaughtOrder", saveInfo.bombersCaughtOrder },
        { "lotteryCodes", saveInfo.lotteryCodes },
        { "spiderHouseMaskOrder", saveInfo.spiderHouseMaskOrder },
        { "bomberCode", saveInfo.bomberCode },
        { "horseData", saveInfo.horseData },
        { "checksum", saveInfo.checksum },
    };
}

//
//   DO   NOT   MODIFY   THIS   FILE!
//
void to_json(json& j, const Legacy_Save& save) {
    j = json{
        { "entrance", save.entrance },
        { "equippedMask", save.equippedMask },
        { "isFirstCycle", save.isFirstCycle },
        { "unk_06", save.unk_06 },
        { "linkAge", save.linkAge },
        { "cutsceneIndex", save.cutsceneIndex },
        { "time", save.time },
        { "owlSaveLocation", save.owlSaveLocation },
        { "isNight", save.isNight },
        { "timeSpeedOffset", save.timeSpeedOffset },
        { "day", save.day },
        { "eventDayCount", save.eventDayCount },
        { "playerForm", save.playerForm },
        { "snowheadCleared", save.snowheadCleared },
        { "hasTatl", save.hasTatl },
        { "isOwlSave", save.isOwlSave },
        { "saveInfo", save.saveInfo },
    };
}

void to_json(json& j, const Legacy_SaveContext& saveContext) {
    j = json{
        { "save", saveContext.save },
        { "eventInf", saveContext.eventInf },
        { "unk_1014", saveContext.unk_1014 },
        { "bButtonStatus", saveContext.bButtonStatus },
        { "jinxTimer", saveContext.jinxTimer },
        { "rupeeAccumulator", saveContext.rupeeAccumulator },
        { "bottleTimerStates", saveContext.bottleTimerStates },
        { "bottleTimerStartOsTimes", saveContext.bottleTimerStartOsTimes },
        { "bottleTimerTimeLimits", saveContext.bottleTimerTimeLimits },
        { "bottleTimerCurTimes", saveContext.bottleTimerCurTimes },
        { "bottleTimerPausedOsTimes", saveContext.bottleTimerPausedOsTimes },
        { "pictoPhotoI5", saveContext.pictoPhotoI5 },
    };
}

void BinarySaveConverter_ReadBufferToSave(Legacy_SaveContext* saveContext, std::shared_ptr<Ship::BinaryReader> reader) {
    // Save
    saveContext->save.entrance = reader->ReadInt32();
    saveContext->save.equippedMask = reader->ReadUByte();
    saveContext->save.isFirstCycle = reader->ReadUByte();
    saveContext->save.unk_06 = reader->ReadUByte();
    saveContext->save.linkAge = reader->ReadUByte();
    saveContext->save.cutsceneIndex = reader->ReadInt32();
    saveContext->save.time = reader->ReadUInt16();
    saveContext->save.owlSaveLocation = reader->ReadUInt16();
    saveContext->save.isNight = reader->ReadInt32();
    saveContext->save.timeSpeedOffset = reader->ReadInt32();
    saveContext->save.day = reader->ReadInt32();
    saveContext->save.eventDayCount = reader->ReadInt32();
    saveContext->save.playerForm = reader->ReadUByte();
    saveContext->save.snowheadCleared = reader->ReadUByte();
    saveContext->save.hasTatl = reader->ReadUByte();
    saveContext->save.isOwlSave = reader->ReadUByte();

    // Save.SaveInfo.SavePlayerData
    reader->Read(saveContext->save.saveInfo.playerData.newf, sizeof(saveContext->save.saveInfo.playerData.newf));
    saveContext->save.saveInfo.playerData.threeDayResetCount = reader->ReadUInt16();
    reader->Read(saveContext->save.saveInfo.playerData.playerName,
                 sizeof(saveContext->save.saveInfo.playerData.playerName));
    saveContext->save.saveInfo.playerData.healthCapacity = reader->ReadInt16();
    saveContext->save.saveInfo.playerData.health = reader->ReadInt16();
    saveContext->save.saveInfo.playerData.magicLevel = reader->ReadInt8();
    saveContext->save.saveInfo.playerData.magic = reader->ReadInt8();
    saveContext->save.saveInfo.playerData.rupees = reader->ReadInt16();
    saveContext->save.saveInfo.playerData.swordHealth = reader->ReadUInt16();
    saveContext->save.saveInfo.playerData.tatlTimer = reader->ReadUInt16();
    saveContext->save.saveInfo.playerData.isMagicAcquired = reader->ReadUByte();
    saveContext->save.saveInfo.playerData.isDoubleMagicAcquired = reader->ReadUByte();
    saveContext->save.saveInfo.playerData.doubleDefense = reader->ReadUByte();
    saveContext->save.saveInfo.playerData.unk_1F = reader->ReadUByte();
    saveContext->save.saveInfo.playerData.unk_20 = reader->ReadUByte();
    reader->ReadUByte(); // Align
    saveContext->save.saveInfo.playerData.owlActivationFlags = reader->ReadUInt16();
    saveContext->save.saveInfo.playerData.unk_24 = reader->ReadUByte();
    reader->ReadUByte(); // Align
    saveContext->save.saveInfo.playerData.savedSceneId = reader->ReadInt16();

    // Save.SaveInfo.ItemEquips
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            saveContext->save.saveInfo.equips.buttonItems[i][j] = reader->ReadUByte();
        }
    }
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            saveContext->save.saveInfo.equips.cButtonSlots[i][j] = reader->ReadUByte();
        }
    }
    saveContext->save.saveInfo.equips.equipment = reader->ReadUInt16();

    reader->ReadInt16(); // Align

    // Save.SaveInfo.Inventory
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.items); i++) {
        saveContext->save.saveInfo.inventory.items[i] = reader->ReadUByte();
    }
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.ammo); i++) {
        saveContext->save.saveInfo.inventory.ammo[i] = reader->ReadInt8();
    }
    saveContext->save.saveInfo.inventory.upgrades = reader->ReadUInt32();
    saveContext->save.saveInfo.inventory.questItems = reader->ReadUInt32();
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.dungeonItems); i++) {
        saveContext->save.saveInfo.inventory.dungeonItems[i] = reader->ReadUByte();
    }
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.dungeonKeys); i++) {
        saveContext->save.saveInfo.inventory.dungeonKeys[i] = reader->ReadInt8();
    }
    saveContext->save.saveInfo.inventory.defenseHearts = reader->ReadInt8();
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.strayFairies); i++) {
        saveContext->save.saveInfo.inventory.strayFairies[i] = reader->ReadInt8();
    }
    for (int i = 0; i < ARRAY_COUNT(saveContext->save.saveInfo.inventory.dekuPlaygroundPlayerName); i++) {
        reader->Read(saveContext->save.saveInfo.inventory.dekuPlaygroundPlayerName[i],
                     sizeof(saveContext->save.saveInfo.inventory.dekuPlaygroundPlayerName[i]));
    }

    reader->ReadInt16(); // Align

    // Save.SaveInfo
    for (int i = 0; i < 120; i++) {
        saveContext->save.saveInfo.permanentSceneFlags[i].chest = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].switch0 = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].switch1 = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].clearedRoom = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].collectible = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].unk_14 = reader->ReadUInt32();
        saveContext->save.saveInfo.permanentSceneFlags[i].rooms = reader->ReadUInt32();
    }
    for (int i = 0; i < 0x54; i++) {
        saveContext->save.saveInfo.unk_DF4[i] = reader->ReadInt8();
    }
    for (int i = 0; i < 3; i++) {
        saveContext->save.saveInfo.dekuPlaygroundHighScores[i] = reader->ReadUInt32();
    }
    saveContext->save.saveInfo.pictoFlags0 = reader->ReadUInt32();
    saveContext->save.saveInfo.pictoFlags1 = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_E5C = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_E60 = reader->ReadUInt32();
    for (int i = 0; i < 7; i++) {
        saveContext->save.saveInfo.unk_E64[i] = reader->ReadUInt32();
    }
    for (int i = 0; i < 7; i++) {
        saveContext->save.saveInfo.scenesVisible[i] = reader->ReadUInt32();
    }
    saveContext->save.saveInfo.skullTokenCount = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_EA0 = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_EA4 = reader->ReadUInt32();
    for (int i = 0; i < 2; i++) {
        saveContext->save.saveInfo.unk_EA8[i] = reader->ReadUInt32();
    }
    saveContext->save.saveInfo.stolenItems = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_EB4 = reader->ReadUInt32();
    for (int i = 0; i < HS_MAX; i++) {
        saveContext->save.saveInfo.highScores[i] = reader->ReadUInt32();
    }
    for (int i = 0; i < 100; i++) {
        saveContext->save.saveInfo.weekEventReg[i] = reader->ReadUByte();
    }
    saveContext->save.saveInfo.regionsVisited = reader->ReadUInt32();
    saveContext->save.saveInfo.worldMapCloudVisibility = reader->ReadUInt32();
    saveContext->save.saveInfo.unk_F40 = reader->ReadUByte();
    saveContext->save.saveInfo.scarecrowSpawnSongSet = reader->ReadUByte();
    for (int i = 0; i < 128; i++) {
        saveContext->save.saveInfo.scarecrowSpawnSong[i] = reader->ReadUByte();
    }
    saveContext->save.saveInfo.bombersCaughtNum = reader->ReadInt8();
    for (int i = 0; i < 5; i++) {
        saveContext->save.saveInfo.bombersCaughtOrder[i] = reader->ReadInt8();
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            saveContext->save.saveInfo.lotteryCodes[i][j] = reader->ReadInt8();
        }
    }
    for (int i = 0; i < 6; i++) {
        saveContext->save.saveInfo.spiderHouseMaskOrder[i] = reader->ReadInt8();
    }
    for (int i = 0; i < 5; i++) {
        saveContext->save.saveInfo.bomberCode[i] = reader->ReadInt8();
    }

    // Save.SaveInfo.HorseData
    saveContext->save.saveInfo.horseData.sceneId = reader->ReadInt16();
    saveContext->save.saveInfo.horseData.pos.x = reader->ReadInt16();
    saveContext->save.saveInfo.horseData.pos.y = reader->ReadInt16();
    saveContext->save.saveInfo.horseData.pos.z = reader->ReadInt16();
    saveContext->save.saveInfo.horseData.yaw = reader->ReadInt16();

    saveContext->save.saveInfo.checksum = 1;
}

bool BinarySaveConverter_HandleFileDropped(char* filePath) {
    try {
        std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate);

        if (!fileStream.is_open()) {
            return false;
        }

        std::streamsize size = fileStream.tellg();
        fileStream.seekg(0, std::ios::beg);

        std::vector<char> buffer(size);
        if (!fileStream.read(buffer.data(), size)) {
            return false;
        }

        std::string sequence = "ZELD";
        std::string swappedSequence = "DLEZ";
        std::vector<size_t> saveOffsets = {};

        for (size_t i = 0; i <= size - sequence.size(); ++i) {
            if (std::equal(sequence.begin(), sequence.end(), buffer.begin() + i)) {
                saveOffsets.push_back(i);
            } else if (std::equal(swappedSequence.begin(), swappedSequence.end(), buffer.begin() + i)) {
                // Swap the entire file and start over
                std::vector<char> byteSwapBuffer(4);
                for (size_t i = 0; i < buffer.size(); i += 4) {
                    byteSwapBuffer[0] = buffer[i + 3];
                    byteSwapBuffer[1] = buffer[i + 2];
                    byteSwapBuffer[2] = buffer[i + 1];
                    byteSwapBuffer[3] = buffer[i];
                    memcpy(&buffer[i], byteSwapBuffer.data(), 4);
                }
                i = 0;
            }
        }

        if (saveOffsets.size() == 0) {
            SPDLOG_DEBUG("Not a valid binary save file");
            return false;
        }

        int saveSlot = SaveManager_GetOpenFileSlot();
        if (saveSlot == -1) {
            SPDLOG_ERROR("No save slot available");
            Notification::Emit({ .message = "No save slot available" });
            return true;
        }

        Legacy_SaveContext saveContext;
        auto reader = std::make_shared<Ship::BinaryReader>(buffer.data(), buffer.size());
        reader->SetEndianness(Ship::Endianness::Big);

        // Seek to front of first available save
        reader->Seek(saveOffsets[0] - 0x24, Ship::SeekOffsetType::Start);
        BinarySaveConverter_ReadBufferToSave(&saveContext, reader);
        nlohmann::json j;
        std::string fileName = SaveManager_GetFileName(saveSlot);

        j["newCycleSave"]["save"] = saveContext.save;
        j["type"] = "2S2H_SAVE";
        j["version"] = 4;

        // Based on where the save was found, determine if the two possible owl save offsets are in the list of save
        // offsets. If they are, we can assume that the save is the owl save associated with the other file above
        bool foundOwlSave = false;
        if (std::find(saveOffsets.begin(), saveOffsets.end(), saveOffsets[0] + 32768) != saveOffsets.end()) {
            foundOwlSave = true;
            reader->Seek(saveOffsets[0] + 32768 - 0x24, Ship::SeekOffsetType::Start);
        } else if (std::find(saveOffsets.begin(), saveOffsets.end(), saveOffsets[0] + 49152) != saveOffsets.end()) {
            foundOwlSave = true;
            reader->Seek(saveOffsets[0] + 49152 - 0x24, Ship::SeekOffsetType::Start);
        }

        if (foundOwlSave) {
            memset(&saveContext, 0, sizeof(Legacy_SaveContext));
            BinarySaveConverter_ReadBufferToSave(&saveContext, reader);

            j["owlSave"] = saveContext;
        }

        SaveManager_WriteSaveFile(fileName, j);

        // Reset the file select state to reload the save metadata
        if (gFileSelectState != NULL) {
            STOP_GAMESTATE(&gFileSelectState->state);
            SET_NEXT_GAMESTATE(&gFileSelectState->state, FileSelect_Init, sizeof(FileSelectState));
        }

        SPDLOG_INFO("Successfully imported save into slot {}", saveSlot);
        Notification::Emit({ .message = "Successfully imported save into slot", .suffix = std::to_string(saveSlot) });

        return true;
    } catch (std::exception& e) {
        SPDLOG_ERROR("Failed to load file: {}", e.what());
        Notification::Emit({ .message = "Failed to load file" });
        return false;
    } catch (...) {
        SPDLOG_ERROR("Failed to load file");
        Notification::Emit({ .message = "Failed to load file" });
        return false;
    }
}