Browse Source

Add support for saved game files for cart RAM (fixes #3)

master
Ben Kurtovic 2 years ago
parent
commit
a0c12d019d
12 changed files with 398 additions and 28 deletions
  1. 1
    0
      .gitignore
  2. 1
    1
      LICENSE
  3. 5
    0
      README.md
  4. 39
    13
      src/config.c
  5. 3
    1
      src/config.h
  6. 13
    2
      src/emulator.c
  7. 15
    2
      src/gamegear.c
  8. 4
    2
      src/gamegear.h
  9. 43
    5
      src/mmu.c
  10. 6
    2
      src/mmu.h
  11. 239
    0
      src/save.c
  12. 29
    0
      src/save.h

+ 1
- 0
.gitignore View File

@@ -5,3 +5,4 @@ crater
5 5
 crater-dev
6 6
 tests/runner
7 7
 tests/asm/*.gg
8
+*.sav

+ 1
- 1
LICENSE View File

@@ -1,4 +1,4 @@
1
-Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
 
3 3
 Permission is hereby granted, free of charge, to any person obtaining a copy
4 4
 of this software and associated documentation files (the "Software"), to deal

+ 5
- 0
README.md View File

@@ -53,6 +53,11 @@ Add `--fullscreen` (`-f`) to enable fullscreen mode, or `--scale <n>`
53 53
 (`-s <n>`) to scale the game screen by an integer factor in windowed mode (this
54 54
 only sets the starting configuration; the window should be resizeable).
55 55
 
56
+By default, crater will save cartridge RAM ("battery saves"; these are distinct
57
+from save states, which are not yet supported) to a file named `<rom>.sav`,
58
+where `<rom>` is the path to the ROM file. You can set a custom save location
59
+with `--save <path>` or disable saving entirely with `--no-save`.
60
+
56 61
 Add `--debug` (`-g`) to show logging information while running. Pass it twice
57 62
 (`-gg`) to show more detailed logs, including an emulator trace.
58 63
 

+ 39
- 13
src/config.c View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #include <dirent.h>
@@ -39,6 +39,9 @@ static void print_help(const char *arg1)
39 39
 "                      to show more detailed logs, including an emulator trace\n"
40 40
 "    -s, --scale <n>   scale the game screen by an integer factor\n"
41 41
 "                      (applies to windowed mode only; defaults to 4)\n"
42
+"    -b, --save <path> save cartridge RAM (\"battery save\") to the given file\n"
43
+"                      (defaults to <rom_path>.sav)\n"
44
+"    -n, --no-save     disable saving cartridge RAM entirely\n"
42 45
 "    -a, --assemble <in> [<out>]\n"
43 46
 "                      convert z80 assembly source code into a binary file that\n"
44 47
 "                      can be run by crater\n"
@@ -179,9 +182,7 @@ static int parse_pos_arg(Config *config, Arguments *args, const char *arg)
179 182
         return CONFIG_EXIT_FAILURE;
180 183
     }
181 184
 
182
-    char *path = cr_malloc(sizeof(char) * (strlen(arg) + 1));
183
-    strcpy(path, arg);
184
-
185
+    char *path = cr_strdup(arg);
185 186
     if (args->paths_read == 1) {
186 187
         /* If this is the second path given, it can only be an output file for
187 188
            the assembler. If the assembler is not enabled by subsequent
@@ -242,6 +243,18 @@ static int parse_opt_arg(Config *config, Arguments *args, const char *arg)
242 243
         }
243 244
         config->scale = scale;
244 245
     }
246
+    else if (arg_check(arg, "b", "save")) {
247
+        const char *next = consume_next(args);
248
+        if (!next) {
249
+            ERROR("the save option requires an argument")
250
+            return CONFIG_EXIT_FAILURE;
251
+        }
252
+        free(config->sav_path);
253
+        config->sav_path = cr_strdup(next);
254
+    }
255
+    else if (arg_check(arg, "n", "no-save")) {
256
+        config->no_saving = true;
257
+    }
245 258
     else if (arg_check(arg, "g", "debug")) {
246 259
         config->debug++;
247 260
     }
@@ -287,17 +300,19 @@ static int parse_args(Config *config, int argc, char *argv[])
287 300
             return retval;
288 301
     }
289 302
 
290
-    if (!config->assemble && !config->disassemble && args.paths_read >= 2) {
291
-        ERROR("too many arguments given - emulator mode accepts one ROM file")
292
-        return CONFIG_EXIT_FAILURE;
293
-    }
294
-    if (!config->assemble && !config->disassemble && args.paths_read == 0) {
295
-        char *path = get_rom_path_from_user();
296
-        if (path[0] == '\0') {
297
-            ERROR("no ROM image given")
303
+    if (!config->assemble && !config->disassemble) {
304
+        if (args.paths_read >= 2) {
305
+            ERROR("too many arguments given - emulator mode accepts one ROM file")
298 306
             return CONFIG_EXIT_FAILURE;
299 307
         }
300
-        config->rom_path = path;
308
+        if (args.paths_read == 0) {
309
+            char *path = get_rom_path_from_user();
310
+            if (path[0] == '\0') {
311
+                ERROR("no ROM image given")
312
+                return CONFIG_EXIT_FAILURE;
313
+            }
314
+            config->rom_path = path;
315
+        }
301 316
     }
302 317
 
303 318
     return CONFIG_OK;
@@ -367,6 +382,12 @@ static bool set_defaults(Config *config)
367 382
         ERROR("refusing to overwrite the assembler input file; pass -r to override")
368 383
         return false;
369 384
     }
385
+    if (!assembler && !config->sav_path && !config->no_saving) {
386
+        config->sav_path = cr_malloc(sizeof(char) *
387
+            (strlen(config->rom_path) + 4));
388
+        strcpy(config->sav_path, config->rom_path);
389
+        strcat(config->sav_path, ".sav");
390
+    }
370 391
     return true;
371 392
 }
372 393
 
@@ -389,9 +410,11 @@ int config_create(Config** config_ptr, int argc, char* argv[])
389 410
     config->fullscreen = false;
390 411
     config->scale = 0;
391 412
     config->rom_path = NULL;
413
+    config->sav_path = NULL;
392 414
     config->src_path = NULL;
393 415
     config->dst_path = NULL;
394 416
     config->overwrite = false;
417
+    config->no_saving = false;
395 418
 
396 419
     retval = parse_args(config, argc, argv);
397 420
     if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config)))
@@ -411,6 +434,7 @@ int config_create(Config** config_ptr, int argc, char* argv[])
411 434
 void config_destroy(Config *config)
412 435
 {
413 436
     free(config->rom_path);
437
+    free(config->sav_path);
414 438
     free(config->src_path);
415 439
     free(config->dst_path);
416 440
     free(config);
@@ -429,7 +453,9 @@ void config_dump_args(const Config* config)
429 453
     DEBUG("- fullscreen:  %s", config->fullscreen ? "true" : "false")
430 454
     DEBUG("- scale:       %d", config->scale)
431 455
     DEBUG("- rom_path:    %s", config->rom_path ? config->rom_path : "(null)")
456
+    DEBUG("- sav_path:    %s", config->sav_path ? config->sav_path : "(null)")
432 457
     DEBUG("- src_path:    %s", config->src_path ? config->src_path : "(null)")
433 458
     DEBUG("- dst_path:    %s", config->dst_path ? config->dst_path : "(null)")
434 459
     DEBUG("- overwrite:   %s", config->overwrite ? "true" : "false")
460
+    DEBUG("- no_saving:   %s", config->no_saving ? "true" : "false")
435 461
 }

+ 3
- 1
src/config.h View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #pragma once
@@ -27,9 +27,11 @@ typedef struct {
27 27
     bool fullscreen;
28 28
     unsigned scale;
29 29
     char *rom_path;
30
+    char *sav_path;
30 31
     char *src_path;
31 32
     char *dst_path;
32 33
     bool overwrite;
34
+    bool no_saving;
33 35
 } Config;
34 36
 
35 37
 /* Functions */

+ 13
- 2
src/emulator.c View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #include <signal.h>
@@ -8,6 +8,7 @@
8 8
 #include "emulator.h"
9 9
 #include "gamegear.h"
10 10
 #include "logging.h"
11
+#include "save.h"
11 12
 #include "util.h"
12 13
 
13 14
 typedef struct {
@@ -176,13 +177,21 @@ static void cleanup_graphics()
176 177
 */
177 178
 void emulate(ROM *rom, Config *config)
178 179
 {
180
+    Save save;
181
+    if (!config->no_saving) {
182
+        if (!save_init(&save, config->sav_path, rom))
183
+            return;
184
+    }
185
+
179 186
     emu.gg = gamegear_create();
180 187
     signal(SIGINT, handle_sigint);
181 188
     setup_graphics(config->fullscreen, config->scale);
182 189
 
183 190
     gamegear_attach_callback(emu.gg, frame_callback);
184 191
     gamegear_attach_display(emu.gg, emu.pixels);
185
-    gamegear_load(emu.gg, rom);
192
+    gamegear_load_rom(emu.gg, rom);
193
+    if (!config->no_saving)
194
+        gamegear_load_save(emu.gg, &save);
186 195
 
187 196
     gamegear_simulate(emu.gg);
188 197
 
@@ -197,4 +206,6 @@ void emulate(ROM *rom, Config *config)
197 206
     signal(SIGINT, SIG_DFL);
198 207
     gamegear_destroy(emu.gg);
199 208
     emu.gg = NULL;
209
+    if (!config->no_saving)
210
+        save_free(&save);
200 211
 }

+ 15
- 2
src/gamegear.c View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #include <stdlib.h>
@@ -59,7 +59,7 @@ void gamegear_destroy(GameGear *gg)
59 59
     until another ROM is loaded or the GameGear is destroyed. Calling this
60 60
     function while the GameGear is powered on has no effect.
61 61
 */
62
-void gamegear_load(GameGear *gg, const ROM *rom)
62
+void gamegear_load_rom(GameGear *gg, const ROM *rom)
63 63
 {
64 64
     if (gg->powered)
65 65
         return;
@@ -68,6 +68,19 @@ void gamegear_load(GameGear *gg, const ROM *rom)
68 68
 }
69 69
 
70 70
 /*
71
+    Load a game save into the GameGear object.
72
+
73
+    The same rules with gamegear_load_rom() apply here.
74
+*/
75
+void gamegear_load_save(GameGear *gg, Save *save)
76
+{
77
+    if (gg->powered)
78
+        return;
79
+
80
+    mmu_load_save(&gg->mmu, save);
81
+}
82
+
83
+/*
71 84
     Update the GameGear's button/joystick state.
72 85
 
73 86
     'state' should be true when the button is pressed, and false when it is

+ 4
- 2
src/gamegear.h View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #pragma once
@@ -10,6 +10,7 @@
10 10
 #include "mmu.h"
11 11
 #include "psg.h"
12 12
 #include "rom.h"
13
+#include "save.h"
13 14
 #include "z80.h"
14 15
 
15 16
 #define GG_SCREEN_WIDTH  160
@@ -48,7 +49,8 @@ typedef enum {
48 49
 
49 50
 GameGear* gamegear_create();
50 51
 void gamegear_destroy(GameGear*);
51
-void gamegear_load(GameGear*, const ROM*);
52
+void gamegear_load_rom(GameGear*, const ROM*);
53
+void gamegear_load_save(GameGear*, Save*);
52 54
 void gamegear_simulate(GameGear*);
53 55
 void gamegear_input(GameGear*, GGButton, bool);
54 56
 void gamegear_power_off(GameGear*);

+ 43
- 5
src/mmu.c View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #include <stdlib.h>
@@ -15,9 +15,11 @@
15 15
 void mmu_init(MMU *mmu)
16 16
 {
17 17
     mmu->system_ram = cr_malloc(sizeof(uint8_t) * MMU_SYSTEM_RAM_SIZE);
18
-    mmu->cart_ram = cr_malloc(sizeof(uint8_t) * MMU_CART_RAM_SIZE);
19
-    mmu->cart_ram_slot = mmu->cart_ram;
18
+    mmu->cart_ram = NULL;
19
+    mmu->cart_ram_slot = NULL;
20 20
     mmu->cart_ram_mapped = false;
21
+    mmu->cart_ram_external = false;
22
+    mmu->save = NULL;
21 23
 
22 24
     for (size_t slot = 0; slot < MMU_NUM_SLOTS; slot++)
23 25
         mmu->rom_slots[slot] = NULL;
@@ -32,7 +34,8 @@ void mmu_init(MMU *mmu)
32 34
 void mmu_free(MMU *mmu)
33 35
 {
34 36
     free(mmu->system_ram);
35
-    free(mmu->cart_ram);
37
+    if (!mmu->cart_ram_external)
38
+        free(mmu->cart_ram);
36 39
 }
37 40
 
38 41
 /*
@@ -86,6 +89,30 @@ void mmu_load_rom(MMU *mmu, const uint8_t *data, size_t size)
86 89
 }
87 90
 
88 91
 /*
92
+    Load a save into the MMU.
93
+
94
+    If the save has valid cartridge RAM from a previous game, we will load that
95
+    into the MMU. Otherwise, we will defer creating fresh cartridge RAM until
96
+    it is requested by the system.
97
+
98
+    This function can be called while the system is running, but it may have
99
+    strange consequences. It will replace any existing cart RAM with the save
100
+    RAM, which will likely confuse the program.
101
+*/
102
+void mmu_load_save(MMU *mmu, Save *save)
103
+{
104
+    mmu->save = save;
105
+    if (save_has_cart_ram(save)) {
106
+        if (mmu->cart_ram && !mmu->cart_ram_external)
107
+            free(mmu->cart_ram);
108
+
109
+        DEBUG("MMU loading cartridge RAM from external save")
110
+        mmu->cart_ram = save_get_cart_ram(save);
111
+        mmu->cart_ram_external = true;
112
+    }
113
+}
114
+
115
+/*
89 116
     Map the given RAM slot to the given ROM bank.
90 117
 */
91 118
 static inline void map_rom_slot(MMU *mmu, size_t slot, size_t bank)
@@ -106,7 +133,6 @@ void mmu_power(MMU *mmu)
106 133
         map_rom_slot(mmu, slot, slot);
107 134
 
108 135
     memset(mmu->system_ram, 0xFF, MMU_SYSTEM_RAM_SIZE);
109
-    memset(mmu->cart_ram,   0xFF, MMU_CART_RAM_SIZE);
110 136
 }
111 137
 
112 138
 /*
@@ -177,6 +203,18 @@ static void write_ram_control_register(MMU *mmu, uint8_t value)
177 203
     else if (!slot2_enable && mmu->cart_ram_mapped)
178 204
         TRACE("MMU disabling cart RAM in memory slot 2")
179 205
 
206
+    if (slot2_enable && !mmu->cart_ram) {
207
+        DEBUG("MMU initializing cartridge RAM (fresh battery save)")
208
+        if (mmu->save && save_init_cart_ram(mmu->save)) {
209
+            mmu->cart_ram = save_get_cart_ram(mmu->save);
210
+            mmu->cart_ram_external = true;
211
+        } else {
212
+            mmu->cart_ram = cr_malloc(sizeof(uint8_t) * MMU_CART_RAM_SIZE);
213
+            mmu->cart_ram_external = false;
214
+        }
215
+        memset(mmu->cart_ram, 0xFF, MMU_CART_RAM_SIZE);
216
+    }
217
+
180 218
     mmu->cart_ram_slot =
181 219
         bank_select ? (mmu->cart_ram + 0x4000) : mmu->cart_ram;
182 220
     mmu->cart_ram_mapped = slot2_enable;

+ 6
- 2
src/mmu.h View File

@@ -1,4 +1,4 @@
1
-/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2 2
    Released under the terms of the MIT License. See LICENSE for details. */
3 3
 
4 4
 #pragma once
@@ -7,6 +7,8 @@
7 7
 #include <stddef.h>
8 8
 #include <stdint.h>
9 9
 
10
+#include "save.h"
11
+
10 12
 #define MMU_NUM_SLOTS       (3)
11 13
 #define MMU_NUM_ROM_BANKS   (64)
12 14
 #define MMU_ROM_BANK_SIZE   (16 * 1024)
@@ -21,7 +23,8 @@ typedef struct {
21 23
     const uint8_t *rom_slots[MMU_NUM_SLOTS];
22 24
     const uint8_t *rom_banks[MMU_NUM_ROM_BANKS];
23 25
     uint8_t *cart_ram_slot;
24
-    bool cart_ram_mapped;
26
+    bool cart_ram_mapped, cart_ram_external;
27
+    Save *save;
25 28
 } MMU;
26 29
 
27 30
 /* Functions */
@@ -29,6 +32,7 @@ typedef struct {
29 32
 void mmu_init(MMU*);
30 33
 void mmu_free(MMU*);
31 34
 void mmu_load_rom(MMU*, const uint8_t*, size_t);
35
+void mmu_load_save(MMU*, Save*);
32 36
 void mmu_power(MMU*);
33 37
 uint8_t mmu_read_byte(const MMU*, uint16_t);
34 38
 uint16_t mmu_read_double(const MMU*, uint16_t);

+ 239
- 0
src/save.c View File

@@ -0,0 +1,239 @@
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2
+   Released under the terms of the MIT License. See LICENSE for details. */
3
+
4
+#include <errno.h>
5
+#include <fcntl.h>
6
+#include <stdio.h>
7
+#include <string.h>
8
+#include <sys/mman.h>
9
+#include <sys/stat.h>
10
+#include <sys/types.h>
11
+#include <unistd.h>
12
+
13
+#include "save.h"
14
+#include "mmu.h"
15
+#include "util.h"
16
+
17
+static const char *MAGIC = "CRATER GAMEGEAR SAVE FILE\n";
18
+static size_t HEADER_LEN = 64;
19
+
20
+/*
21
+    Log an error while trying to load the save file.
22
+*/
23
+static void log_error(const char *action, const char *path, const char *reason)
24
+{
25
+    ERROR("couldn't %s save file '%s': %s", action, path, reason)
26
+}
27
+
28
+/*
29
+    Log an error in a standard library function.
30
+*/
31
+static void log_stdlib_error(const char *action, const char *path,
32
+    const char *func)
33
+{
34
+    ERROR("couldn't %s save file '%s': %s(): %s",
35
+        action, path, func, strerror(errno))
36
+}
37
+
38
+/*
39
+    Parse the header of a save file, and return whether it is valid.
40
+
41
+    If the load succeeds and off is not NULL, it will be set to the address
42
+    of the first byte of non-header data.
43
+*/
44
+static bool parse_save_header(void *ptr, size_t size, size_t *off,
45
+    const ROM *rom, const char *path)
46
+{
47
+    const char *str = ptr;
48
+    if (size < HEADER_LEN) {
49
+        log_error("load", path, "too short");
50
+        return false;
51
+    }
52
+    if (strncmp(str, MAGIC, strlen(MAGIC))) {
53
+        log_error("load", path,
54
+            "invalid header (was this save created by crater?)");
55
+        return false;
56
+    }
57
+    str += strlen(MAGIC);
58
+
59
+    int version;
60
+    uint32_t prodcode;
61
+    uint16_t checksum;
62
+    if (sscanf(str, "%d:%06d:0x%04hX\n", &version, &prodcode, &checksum) < 3) {
63
+        log_error("load", path, "invalid header (failed to parse)");
64
+        return false;
65
+    }
66
+    if (version != 1) {
67
+        log_error("load", path, "unknown or unsupported save file version");
68
+        return false;
69
+    }
70
+    if (prodcode != rom->product_code || checksum != rom->expected_checksum) {
71
+        log_error("load", path, "save was created for a different ROM");
72
+        return false;
73
+    }
74
+    if (size != HEADER_LEN + MMU_CART_RAM_SIZE) {
75
+        log_error("load", path, "cart RAM size is wrong; file may be corrupt");
76
+        return false;
77
+    }
78
+
79
+    if (off)
80
+        *off = HEADER_LEN;
81
+    return true;
82
+}
83
+
84
+/*
85
+    Initialize a save object, which represents persistent RAM.
86
+
87
+    The given path will be used to store save data. If it already exists,
88
+    it will be loaded here. The return value indicates whether the load was
89
+    successful; if it is false, then a file exists in the save location but is
90
+    not valid, and this save should not be used. save_free() does not need to
91
+    be called in this case.
92
+*/
93
+bool save_init(Save *save, const char *path, const ROM *rom)
94
+{
95
+    save->path = NULL;
96
+    save->rom = rom;
97
+    save->map = NULL;
98
+    save->mapsize = 0;
99
+    save->cart_ram_offset = 0;
100
+    save->has_cart_ram = false;
101
+
102
+    if (!path)
103
+        return true;
104
+
105
+    int fd = open(path, O_RDWR);
106
+    if (fd < 0) {
107
+        if (errno == ENOENT) {
108
+            save->path = cr_strdup(path);
109
+            return true;
110
+        }
111
+        log_stdlib_error("load", path, "open");
112
+        return false;
113
+    }
114
+
115
+    struct stat s;
116
+    if (fstat(fd, &s) < 0) {
117
+        close(fd);
118
+        log_stdlib_error("load", path, "fstat");
119
+        return false;
120
+    }
121
+
122
+    size_t size = s.st_size;
123
+    void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
124
+    close(fd);
125
+    if (ptr == MAP_FAILED) {
126
+        log_stdlib_error("load", path, "mmap");
127
+        return false;
128
+    }
129
+
130
+    size_t offset;
131
+    if (!parse_save_header(ptr, size, &offset, rom, path))
132
+        return false;
133
+
134
+    save->map = ptr;
135
+    save->mapsize = size;
136
+    save->cart_ram_offset = offset;
137
+    save->has_cart_ram = true;
138
+    return true;
139
+}
140
+
141
+/*
142
+    Free memory previously allocated by the save.
143
+
144
+    Will flush the save data to disk if necessary.
145
+*/
146
+void save_free(Save *save)
147
+{
148
+    free(save->path);
149
+    if (save->map) {
150
+        msync(save->map, save->mapsize, MS_SYNC);
151
+        munmap(save->map, save->mapsize);
152
+    }
153
+}
154
+
155
+/*
156
+    Return whether the save has existing cartridge RAM.
157
+*/
158
+bool save_has_cart_ram(const Save *save)
159
+{
160
+    return save->has_cart_ram;
161
+}
162
+
163
+/*
164
+    Return a readable and writable pointer to existing cartridge RAM.
165
+*/
166
+uint8_t* save_get_cart_ram(Save *save)
167
+{
168
+    if (!save->has_cart_ram)
169
+        return NULL;
170
+    return ((uint8_t*) save->map) + save->cart_ram_offset;
171
+}
172
+
173
+/*
174
+    Initialize the save file with fresh cartridge RAM as appropriate.
175
+
176
+    If the save file is already loaded, return true. Otherwise, the return
177
+    value indicates whether the save file creation was successful.
178
+*/
179
+bool save_init_cart_ram(Save *save)
180
+{
181
+    if (save->has_cart_ram)
182
+        return true;
183
+    if (!save->path || save->map)  // This should not happen normally
184
+        return false;
185
+
186
+    DEBUG("Creating new save file at %s", save->path)
187
+
188
+    int fd = open(save->path, O_RDWR|O_CREAT|O_EXCL, 0644);
189
+    if (fd < 0) {
190
+        log_stdlib_error("create", save->path, "open");
191
+        return false;
192
+    }
193
+
194
+    // Write header
195
+    static const int VERSION = 1;
196
+    int header_len = dprintf(fd, "%s%d:%06d:0x%04hX\n", MAGIC, VERSION,
197
+        save->rom->product_code, save->rom->expected_checksum);
198
+    if (header_len < 0 || (unsigned) header_len > HEADER_LEN) {
199
+        if (header_len < 0)
200
+            log_stdlib_error("create", save->path, "dprintf");
201
+        else
202
+            log_error("create", save->path, "header was unexpectedly long");
203
+        close(fd);
204
+        unlink(save->path);
205
+        return false;
206
+    }
207
+
208
+    // Zero out space for the cartridge RAM
209
+    char buf[4096];
210
+    memset(buf, 0, sizeof(buf));
211
+    size_t rem = MMU_CART_RAM_SIZE + (HEADER_LEN - header_len);
212
+    while (rem > 0) {
213
+        ssize_t chunk = rem > sizeof(buf) ? sizeof(buf) : rem;
214
+        if (write(fd, buf, chunk) < chunk) {
215
+            log_stdlib_error("create", save->path, "write");
216
+            close(fd);
217
+            unlink(save->path);
218
+            return false;
219
+        }
220
+        rem -= chunk;
221
+    }
222
+
223
+    // Try to MMAP
224
+    size_t size = HEADER_LEN + MMU_CART_RAM_SIZE;
225
+    lseek(fd, 0, SEEK_SET);
226
+    void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
227
+    close(fd);
228
+    if (ptr == MAP_FAILED) {
229
+        log_stdlib_error("create", save->path, "mmap");
230
+        unlink(save->path);
231
+        return false;
232
+    }
233
+
234
+    save->map = ptr;
235
+    save->mapsize = size;
236
+    save->cart_ram_offset = HEADER_LEN;
237
+    save->has_cart_ram = true;
238
+    return true;
239
+}

+ 29
- 0
src/save.h View File

@@ -0,0 +1,29 @@
1
+/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
2
+   Released under the terms of the MIT License. See LICENSE for details. */
3
+
4
+#pragma once
5
+
6
+#include <stdbool.h>
7
+#include <stdint.h>
8
+
9
+#include "rom.h"
10
+
11
+/* Structs */
12
+
13
+typedef struct {
14
+    char *path;
15
+    const ROM *rom;
16
+    void *map;
17
+    size_t mapsize;
18
+    size_t cart_ram_offset;
19
+    bool has_cart_ram;
20
+} Save;
21
+
22
+/* Functions */
23
+
24
+bool save_init(Save*, const char*, const ROM*);
25
+void save_free(Save*);
26
+
27
+bool save_has_cart_ram(const Save*);
28
+uint8_t* save_get_cart_ram(Save*);
29
+bool save_init_cart_ram(Save*);

Loading…
Cancel
Save