GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
save.c
Go to the documentation of this file.
1
4#include "save.h"
5
6#include "audio_utils.h"
7#include "bitset.h"
8#include "card.h"
9#include "game.h"
10#include "game_variables.h"
11#include "joker.h"
12#include "list.h"
13#include "util.h"
14#include "version.h"
15
16#include <stdlib.h>
17#include <string.h>
18
19// See https://gbadev.net/gbadoc/memory.html for more details on SRAM
20// A few important pieces of info:
21// - Read/Writes are limited to 8-bits words so they are done byte per byte
22// - Memory is filled with 1s by default
23// (at least in mgba, not sure about real HW)
24
25#define HEADER_ADDRESS 0x0
26#define OPTIONS_ADDRESS 0x10
27#define GAME_ADDRESS 0x30
28
29#define SAVE_SECTION_FLAG_NONE 0
30#define SAVE_SECTION_FLAG_OPTIONS (1 << 0)
31#define SAVE_SECTION_FLAG_GAME (1 << 1)
32
33#define CHECK_MAGIC 0x4C414247 // Spells GBAL, used to determine if the save data is junk
34#define CHECK_HASH_SIZE 7
35#define GIT_HASH_START 17 // starts after "GBALATRO-VERSION:" in the gbalatro_version var
36
37#define SAVE_LABEL_SIZE 16
38
39// clang-format off
51// clang-format on
52typedef struct SaveHeader
53{
54 u32 magic;
55 bool dirty;
56 char githash[CHECK_HASH_SIZE];
57 u32 valid_sections;
59
64 .magic = CHECK_MAGIC,
65 .dirty = false,
66 .githash = "fffffff",
67 .valid_sections = SAVE_SECTION_FLAG_NONE
68};
69
70// clang-format off
85// clang-format on
86typedef struct SaveOptions
87{
88 char tag_options[SAVE_LABEL_SIZE];
89 u8 game_speed;
90 bool cards_high_contrast;
91 bool cards_more_readable;
92 u8 music_volume;
93 u8 sound_volume;
94 s8 padding[11];
96
100// clang-format off
102 .tag_options = "- OPTIONS DATA -",
103 .game_speed = GAME_SPEED_MIN,
104 .cards_high_contrast = DEFAULT_HIGH_CONTRAST,
105 .cards_more_readable = DEFAULT_MORE_READABLE,
106 .music_volume = VOLUME_OPTION_MAX,
107 .sound_volume = VOLUME_OPTION_MAX,
108 .padding = {
109 UNDEFINED, UNDEFINED, UNDEFINED,
110 UNDEFINED, UNDEFINED, UNDEFINED,
111 UNDEFINED, UNDEFINED, UNDEFINED,
112 UNDEFINED, UNDEFINED
113 }
114};
115// clang-format on
116
123{
124 u32 id;
125 u32 persistent_state;
127
128// clang-format off
158// clang-format on
159typedef struct SaveGame
160{
161 char tag_internal[SAVE_LABEL_SIZE];
162 s32 timer;
163 RngInfo rng_info;
164 int round;
165 int ante;
166 int money;
167 s32 padding[2];
168
169 char tag_jokers[SAVE_LABEL_SIZE];
170 JokerObjectSaveData jokers_data[MAX_JOKERS_HELD_SIZE];
171
172 char tag_end[4];
174
179 .tag_internal = "-INTERNAL DATA -",
180 .timer = 0,
181 .rng_info = {0, {0}},
182 .round = 0,
183 .ante = 0,
184 .money = 0,
185 .padding = {UNDEFINED, UNDEFINED},
186
187 .tag_jokers = "- OWNED JOKERS -",
188 .jokers_data = {},
189
190 .tag_end = "_END"
191};
192
200static inline void write_sram(u32 sram_base, const u8* bytes, u32 size)
201{
202 if (sram_base + size > SRAM_SIZE)
203 return;
204
205 for (u32 i = 0; i < size; i++)
206 {
207 sram_mem[sram_base + i] = bytes[i];
208 }
209}
210
216static inline void read_sram(u32 sram_base, u8* bytes, u32 size)
217{
218 if (sram_base + size > SRAM_SIZE)
219 return;
220
221 for (u32 i = 0; i < size; i++)
222 {
223 bytes[i] = sram_mem[sram_base + i];
224 }
225}
226
234static inline bool check_hash(const char* prefix)
235{
236 for (u32 i = 0; i < CHECK_HASH_SIZE; i++)
237 {
238 if (gbalatro_version[GIT_HASH_START + i] != prefix[i])
239 {
240 return false;
241 }
242 }
243
244 return true;
245}
246
254static inline bool is_version_dirty(void)
255{
256 return strlen(gbalatro_version) > GIT_HASH_START + CHECK_HASH_SIZE;
257}
258
265static inline bool get_save_header(SaveHeader* header)
266{
267 read_sram(HEADER_ADDRESS, (u8*)header, sizeof(*header));
268 return (header->magic == CHECK_MAGIC) && header->dirty == is_version_dirty() &&
269 check_hash(header->githash);
270}
271
283static inline void set_save_header(u32 section_flag)
284{
285 SaveHeader header;
286
287 // Check for valid data. If it's junk, set all sections as invalid, else keep the flags.
288 // Then add the requested flag.
289 if (!get_save_header(&header))
290 {
291 memcpy(&header, &SaveHeader_default, sizeof(SaveHeader_default));
292 }
293
294 header.valid_sections |= section_flag;
295
296 header.dirty = is_version_dirty();
297 memcpy(&(header.githash), gbalatro_version + GIT_HASH_START, CHECK_HASH_SIZE);
298
299 write_sram(HEADER_ADDRESS, (const u8*)&header, sizeof(header));
300}
301
302void save_options(void)
303{
305
306 options.game_speed = g_game_vars.game_speed;
307 options.cards_high_contrast = get_cards_high_contrast();
308 options.cards_more_readable = get_cards_more_readable();
309 options.music_volume = g_game_vars.music_volume;
310 options.sound_volume = g_game_vars.sound_volume;
311
312 write_sram(OPTIONS_ADDRESS, (const u8*)&options, sizeof(options));
313 set_save_header(SAVE_SECTION_FLAG_OPTIONS);
314}
315
316void load_options(void)
317{
318 SaveHeader header;
320
321 // If options data doesn't exist or is invalid, just don't read from
322 // SRAM and apply the default values
323 if (get_save_header(&header) && (header.valid_sections & SAVE_SECTION_FLAG_OPTIONS))
324 read_sram(OPTIONS_ADDRESS, (u8*)&options, sizeof(options));
325
326 g_game_vars.game_speed = options.game_speed;
327 g_game_vars.music_volume = options.music_volume;
328 g_game_vars.sound_volume = options.sound_volume;
329
330 set_volume(volume_module_step_to_val(g_game_vars.music_volume));
331 set_cards_high_contrast(options.cards_high_contrast);
332 set_cards_more_readable(options.cards_more_readable);
333}
334
336{
337 SaveHeader header;
338 return get_save_header(&header) && (header.valid_sections & SAVE_SECTION_FLAG_GAME);
339}
340
341void save_game(void)
342{
344
345 // Fixed data
346
347 game.timer = g_game_vars.timer;
348 game.rng_info = g_game_vars.rng_info;
349 game.round = g_game_vars.round;
350 game.ante = g_game_vars.ante;
351 game.money = g_game_vars.money;
352
353 // Lists
354
355 List* jokers_list = get_jokers_list();
356 int nb_jokers = list_get_len(jokers_list);
357
358 int i = 0;
359 for (; i < nb_jokers; i++)
360 {
361 JokerObject* joker_object = list_get_at_idx(jokers_list, (u32)i);
362 JokerObjectSaveData data = {
363 (u32)joker_object->joker->id,
364 joker_object->joker->persistent_state
365 };
366 game.jokers_data[i] = data;
367 }
368 for (; i < MAX_JOKERS_HELD_SIZE; i++)
369 {
370 JokerObjectSaveData data = {UNDEFINED, UNDEFINED};
371 game.jokers_data[i] = data;
372 }
373
374 write_sram(GAME_ADDRESS, (const u8*)&game, sizeof(game));
375 set_save_header(SAVE_SECTION_FLAG_GAME);
376}
377
378void load_game(void)
379{
380 SaveHeader header;
381
382 if (!get_save_header(&header) || !(header.valid_sections & SAVE_SECTION_FLAG_GAME))
383 return;
384
386 read_sram(GAME_ADDRESS, (u8*)&game, sizeof(game));
387
388 g_game_vars.timer = game.timer;
389 rng_restore(game.rng_info);
390 g_game_vars.round = game.round;
391 g_game_vars.ante = game.ante;
392 g_game_vars.money = game.money;
393
394 // TODO: load Jokers from stored minimal data
395}
Utilities for using maxmod to play sound effects.
int volume_module_step_to_val(unsigned char step)
Get MaxMod module audio value from VOLUME_OPTION value.
Definition audio_utils.h:85
A bitset for operating on flags.
Game global game variables struct definition.
Functions relative to the handling of Jokers.
A doubly-linked list.
void * list_get_at_idx(List *list, unsigned int idx)
Definition list.c:219
int list_get_len(const List *list)
Definition list.c:214
void rng_restore(RngInfo info)
Restore RNG info struct in the GameVariables. Sets the seed and restores the state for each independe...
Definition random.c:77
bool is_game_data_valid(void)
Checks whether the SaveGame section is present and valid.
Definition save.c:335
static const SaveOptions SaveOptions_default
Default value for the SaveOptions struct, with tags already set.
Definition save.c:101
static bool is_version_dirty(void)
Determines if the current build is considered "dirty" aka has uncommitted changes....
Definition save.c:254
static bool check_hash(const char *prefix)
Checks the 7 chars of gbalatro_version after the "GBALATRO_VERSION" prefix representing the git hash ...
Definition save.c:234
static void read_sram(u32 sram_base, u8 *bytes, u32 size)
Read raw binary data from SRAM.
Definition save.c:216
void save_game(void)
Save current run data to SRAM.
Definition save.c:341
static bool get_save_header(SaveHeader *header)
Reads whether the save data exists and is valid.
Definition save.c:265
void load_game(void)
Load previous run data from SRAM.
Definition save.c:378
void load_options(void)
Load options values from SRAM.
Definition save.c:316
static const SaveGame SaveGame_default
Default value for the SaveGame struct, with tags already set.
Definition save.c:178
static const SaveHeader SaveHeader_default
Default value for the SaveHeader struct.
Definition save.c:63
static void write_sram(u32 sram_base, const u8 *bytes, u32 size)
Write raw binary data to SRAM.
Definition save.c:200
static void set_save_header(u32 section_flag)
Writes a magic number and ROM version info to SRAM to signal that the save data exists and allow the ...
Definition save.c:283
void save_options(void)
Save options values to SRAM.
Definition save.c:302
Utils functions to save/load data structures from/to the SRAM.
JokerObjectSaveData will hold the minimal amount of data necessary to reconstruct a Joker....
Definition save.c:123
A doubly-linked list.
Definition list.h:61
Information to track and restore RNG state.
Definition random.h:41
SaveGame will contain the data about the current run to be saved to SRAM. GameVariables was used for ...
Definition save.c:160
SaveHeader for validation checks to be packed and written to SRAM for validation. Defined in this dis...
Definition save.c:53
SaveOptions will only contain options data set in the Options Menu.
Definition save.c:87
Utilities relating around number string representation and protected arithmatic helper functions.
Game ROM version global var declaration.