@@ -5,3 +5,4 @@ crater | |||
crater-dev | |||
tests/runner | |||
tests/asm/*.gg | |||
*.sav |
@@ -1,4 +1,4 @@ | |||
Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
@@ -53,6 +53,11 @@ Add `--fullscreen` (`-f`) to enable fullscreen mode, or `--scale <n>` | |||
(`-s <n>`) to scale the game screen by an integer factor in windowed mode (this | |||
only sets the starting configuration; the window should be resizeable). | |||
By default, crater will save cartridge RAM ("battery saves"; these are distinct | |||
from save states, which are not yet supported) to a file named `<rom>.sav`, | |||
where `<rom>` is the path to the ROM file. You can set a custom save location | |||
with `--save <path>` or disable saving entirely with `--no-save`. | |||
Add `--debug` (`-g`) to show logging information while running. Pass it twice | |||
(`-gg`) to show more detailed logs, including an emulator trace. | |||
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <dirent.h> | |||
@@ -39,6 +39,9 @@ static void print_help(const char *arg1) | |||
" to show more detailed logs, including an emulator trace\n" | |||
" -s, --scale <n> scale the game screen by an integer factor\n" | |||
" (applies to windowed mode only; defaults to 4)\n" | |||
" -b, --save <path> save cartridge RAM (\"battery save\") to the given file\n" | |||
" (defaults to <rom_path>.sav)\n" | |||
" -n, --no-save disable saving cartridge RAM entirely\n" | |||
" -a, --assemble <in> [<out>]\n" | |||
" convert z80 assembly source code into a binary file that\n" | |||
" can be run by crater\n" | |||
@@ -179,9 +182,7 @@ static int parse_pos_arg(Config *config, Arguments *args, const char *arg) | |||
return CONFIG_EXIT_FAILURE; | |||
} | |||
char *path = cr_malloc(sizeof(char) * (strlen(arg) + 1)); | |||
strcpy(path, arg); | |||
char *path = cr_strdup(arg); | |||
if (args->paths_read == 1) { | |||
/* If this is the second path given, it can only be an output file for | |||
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) | |||
} | |||
config->scale = scale; | |||
} | |||
else if (arg_check(arg, "b", "save")) { | |||
const char *next = consume_next(args); | |||
if (!next) { | |||
ERROR("the save option requires an argument") | |||
return CONFIG_EXIT_FAILURE; | |||
} | |||
free(config->sav_path); | |||
config->sav_path = cr_strdup(next); | |||
} | |||
else if (arg_check(arg, "n", "no-save")) { | |||
config->no_saving = true; | |||
} | |||
else if (arg_check(arg, "g", "debug")) { | |||
config->debug++; | |||
} | |||
@@ -287,17 +300,19 @@ static int parse_args(Config *config, int argc, char *argv[]) | |||
return retval; | |||
} | |||
if (!config->assemble && !config->disassemble && args.paths_read >= 2) { | |||
ERROR("too many arguments given - emulator mode accepts one ROM file") | |||
return CONFIG_EXIT_FAILURE; | |||
} | |||
if (!config->assemble && !config->disassemble && args.paths_read == 0) { | |||
char *path = get_rom_path_from_user(); | |||
if (path[0] == '\0') { | |||
ERROR("no ROM image given") | |||
if (!config->assemble && !config->disassemble) { | |||
if (args.paths_read >= 2) { | |||
ERROR("too many arguments given - emulator mode accepts one ROM file") | |||
return CONFIG_EXIT_FAILURE; | |||
} | |||
config->rom_path = path; | |||
if (args.paths_read == 0) { | |||
char *path = get_rom_path_from_user(); | |||
if (path[0] == '\0') { | |||
ERROR("no ROM image given") | |||
return CONFIG_EXIT_FAILURE; | |||
} | |||
config->rom_path = path; | |||
} | |||
} | |||
return CONFIG_OK; | |||
@@ -367,6 +382,12 @@ static bool set_defaults(Config *config) | |||
ERROR("refusing to overwrite the assembler input file; pass -r to override") | |||
return false; | |||
} | |||
if (!assembler && !config->sav_path && !config->no_saving) { | |||
config->sav_path = cr_malloc(sizeof(char) * | |||
(strlen(config->rom_path) + 4)); | |||
strcpy(config->sav_path, config->rom_path); | |||
strcat(config->sav_path, ".sav"); | |||
} | |||
return true; | |||
} | |||
@@ -389,9 +410,11 @@ int config_create(Config** config_ptr, int argc, char* argv[]) | |||
config->fullscreen = false; | |||
config->scale = 0; | |||
config->rom_path = NULL; | |||
config->sav_path = NULL; | |||
config->src_path = NULL; | |||
config->dst_path = NULL; | |||
config->overwrite = false; | |||
config->no_saving = false; | |||
retval = parse_args(config, argc, argv); | |||
if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config))) | |||
@@ -411,6 +434,7 @@ int config_create(Config** config_ptr, int argc, char* argv[]) | |||
void config_destroy(Config *config) | |||
{ | |||
free(config->rom_path); | |||
free(config->sav_path); | |||
free(config->src_path); | |||
free(config->dst_path); | |||
free(config); | |||
@@ -429,7 +453,9 @@ void config_dump_args(const Config* config) | |||
DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false") | |||
DEBUG("- scale: %d", config->scale) | |||
DEBUG("- rom_path: %s", config->rom_path ? config->rom_path : "(null)") | |||
DEBUG("- sav_path: %s", config->sav_path ? config->sav_path : "(null)") | |||
DEBUG("- src_path: %s", config->src_path ? config->src_path : "(null)") | |||
DEBUG("- dst_path: %s", config->dst_path ? config->dst_path : "(null)") | |||
DEBUG("- overwrite: %s", config->overwrite ? "true" : "false") | |||
DEBUG("- no_saving: %s", config->no_saving ? "true" : "false") | |||
} |
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#pragma once | |||
@@ -27,9 +27,11 @@ typedef struct { | |||
bool fullscreen; | |||
unsigned scale; | |||
char *rom_path; | |||
char *sav_path; | |||
char *src_path; | |||
char *dst_path; | |||
bool overwrite; | |||
bool no_saving; | |||
} Config; | |||
/* Functions */ | |||
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <signal.h> | |||
@@ -8,6 +8,7 @@ | |||
#include "emulator.h" | |||
#include "gamegear.h" | |||
#include "logging.h" | |||
#include "save.h" | |||
#include "util.h" | |||
typedef struct { | |||
@@ -176,13 +177,21 @@ static void cleanup_graphics() | |||
*/ | |||
void emulate(ROM *rom, Config *config) | |||
{ | |||
Save save; | |||
if (!config->no_saving) { | |||
if (!save_init(&save, config->sav_path, rom)) | |||
return; | |||
} | |||
emu.gg = gamegear_create(); | |||
signal(SIGINT, handle_sigint); | |||
setup_graphics(config->fullscreen, config->scale); | |||
gamegear_attach_callback(emu.gg, frame_callback); | |||
gamegear_attach_display(emu.gg, emu.pixels); | |||
gamegear_load(emu.gg, rom); | |||
gamegear_load_rom(emu.gg, rom); | |||
if (!config->no_saving) | |||
gamegear_load_save(emu.gg, &save); | |||
gamegear_simulate(emu.gg); | |||
@@ -197,4 +206,6 @@ void emulate(ROM *rom, Config *config) | |||
signal(SIGINT, SIG_DFL); | |||
gamegear_destroy(emu.gg); | |||
emu.gg = NULL; | |||
if (!config->no_saving) | |||
save_free(&save); | |||
} |
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <stdlib.h> | |||
@@ -59,7 +59,7 @@ void gamegear_destroy(GameGear *gg) | |||
until another ROM is loaded or the GameGear is destroyed. Calling this | |||
function while the GameGear is powered on has no effect. | |||
*/ | |||
void gamegear_load(GameGear *gg, const ROM *rom) | |||
void gamegear_load_rom(GameGear *gg, const ROM *rom) | |||
{ | |||
if (gg->powered) | |||
return; | |||
@@ -68,6 +68,19 @@ void gamegear_load(GameGear *gg, const ROM *rom) | |||
} | |||
/* | |||
Load a game save into the GameGear object. | |||
The same rules with gamegear_load_rom() apply here. | |||
*/ | |||
void gamegear_load_save(GameGear *gg, Save *save) | |||
{ | |||
if (gg->powered) | |||
return; | |||
mmu_load_save(&gg->mmu, save); | |||
} | |||
/* | |||
Update the GameGear's button/joystick state. | |||
'state' should be true when the button is pressed, and false when it is | |||
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#pragma once | |||
@@ -10,6 +10,7 @@ | |||
#include "mmu.h" | |||
#include "psg.h" | |||
#include "rom.h" | |||
#include "save.h" | |||
#include "z80.h" | |||
#define GG_SCREEN_WIDTH 160 | |||
@@ -48,7 +49,8 @@ typedef enum { | |||
GameGear* gamegear_create(); | |||
void gamegear_destroy(GameGear*); | |||
void gamegear_load(GameGear*, const ROM*); | |||
void gamegear_load_rom(GameGear*, const ROM*); | |||
void gamegear_load_save(GameGear*, Save*); | |||
void gamegear_simulate(GameGear*); | |||
void gamegear_input(GameGear*, GGButton, bool); | |||
void gamegear_power_off(GameGear*); | |||
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <stdlib.h> | |||
@@ -15,9 +15,11 @@ | |||
void mmu_init(MMU *mmu) | |||
{ | |||
mmu->system_ram = cr_malloc(sizeof(uint8_t) * MMU_SYSTEM_RAM_SIZE); | |||
mmu->cart_ram = cr_malloc(sizeof(uint8_t) * MMU_CART_RAM_SIZE); | |||
mmu->cart_ram_slot = mmu->cart_ram; | |||
mmu->cart_ram = NULL; | |||
mmu->cart_ram_slot = NULL; | |||
mmu->cart_ram_mapped = false; | |||
mmu->cart_ram_external = false; | |||
mmu->save = NULL; | |||
for (size_t slot = 0; slot < MMU_NUM_SLOTS; slot++) | |||
mmu->rom_slots[slot] = NULL; | |||
@@ -32,7 +34,8 @@ void mmu_init(MMU *mmu) | |||
void mmu_free(MMU *mmu) | |||
{ | |||
free(mmu->system_ram); | |||
free(mmu->cart_ram); | |||
if (!mmu->cart_ram_external) | |||
free(mmu->cart_ram); | |||
} | |||
/* | |||
@@ -86,6 +89,30 @@ void mmu_load_rom(MMU *mmu, const uint8_t *data, size_t size) | |||
} | |||
/* | |||
Load a save into the MMU. | |||
If the save has valid cartridge RAM from a previous game, we will load that | |||
into the MMU. Otherwise, we will defer creating fresh cartridge RAM until | |||
it is requested by the system. | |||
This function can be called while the system is running, but it may have | |||
strange consequences. It will replace any existing cart RAM with the save | |||
RAM, which will likely confuse the program. | |||
*/ | |||
void mmu_load_save(MMU *mmu, Save *save) | |||
{ | |||
mmu->save = save; | |||
if (save_has_cart_ram(save)) { | |||
if (mmu->cart_ram && !mmu->cart_ram_external) | |||
free(mmu->cart_ram); | |||
DEBUG("MMU loading cartridge RAM from external save") | |||
mmu->cart_ram = save_get_cart_ram(save); | |||
mmu->cart_ram_external = true; | |||
} | |||
} | |||
/* | |||
Map the given RAM slot to the given ROM bank. | |||
*/ | |||
static inline void map_rom_slot(MMU *mmu, size_t slot, size_t bank) | |||
@@ -106,7 +133,6 @@ void mmu_power(MMU *mmu) | |||
map_rom_slot(mmu, slot, slot); | |||
memset(mmu->system_ram, 0xFF, MMU_SYSTEM_RAM_SIZE); | |||
memset(mmu->cart_ram, 0xFF, MMU_CART_RAM_SIZE); | |||
} | |||
/* | |||
@@ -177,6 +203,18 @@ static void write_ram_control_register(MMU *mmu, uint8_t value) | |||
else if (!slot2_enable && mmu->cart_ram_mapped) | |||
TRACE("MMU disabling cart RAM in memory slot 2") | |||
if (slot2_enable && !mmu->cart_ram) { | |||
DEBUG("MMU initializing cartridge RAM (fresh battery save)") | |||
if (mmu->save && save_init_cart_ram(mmu->save)) { | |||
mmu->cart_ram = save_get_cart_ram(mmu->save); | |||
mmu->cart_ram_external = true; | |||
} else { | |||
mmu->cart_ram = cr_malloc(sizeof(uint8_t) * MMU_CART_RAM_SIZE); | |||
mmu->cart_ram_external = false; | |||
} | |||
memset(mmu->cart_ram, 0xFF, MMU_CART_RAM_SIZE); | |||
} | |||
mmu->cart_ram_slot = | |||
bank_select ? (mmu->cart_ram + 0x4000) : mmu->cart_ram; | |||
mmu->cart_ram_mapped = slot2_enable; | |||
@@ -1,4 +1,4 @@ | |||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#pragma once | |||
@@ -7,6 +7,8 @@ | |||
#include <stddef.h> | |||
#include <stdint.h> | |||
#include "save.h" | |||
#define MMU_NUM_SLOTS (3) | |||
#define MMU_NUM_ROM_BANKS (64) | |||
#define MMU_ROM_BANK_SIZE (16 * 1024) | |||
@@ -21,7 +23,8 @@ typedef struct { | |||
const uint8_t *rom_slots[MMU_NUM_SLOTS]; | |||
const uint8_t *rom_banks[MMU_NUM_ROM_BANKS]; | |||
uint8_t *cart_ram_slot; | |||
bool cart_ram_mapped; | |||
bool cart_ram_mapped, cart_ram_external; | |||
Save *save; | |||
} MMU; | |||
/* Functions */ | |||
@@ -29,6 +32,7 @@ typedef struct { | |||
void mmu_init(MMU*); | |||
void mmu_free(MMU*); | |||
void mmu_load_rom(MMU*, const uint8_t*, size_t); | |||
void mmu_load_save(MMU*, Save*); | |||
void mmu_power(MMU*); | |||
uint8_t mmu_read_byte(const MMU*, uint16_t); | |||
uint16_t mmu_read_double(const MMU*, uint16_t); | |||
@@ -0,0 +1,239 @@ | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <sys/mman.h> | |||
#include <sys/stat.h> | |||
#include <sys/types.h> | |||
#include <unistd.h> | |||
#include "save.h" | |||
#include "mmu.h" | |||
#include "util.h" | |||
static const char *MAGIC = "CRATER GAMEGEAR SAVE FILE\n"; | |||
static size_t HEADER_LEN = 64; | |||
/* | |||
Log an error while trying to load the save file. | |||
*/ | |||
static void log_error(const char *action, const char *path, const char *reason) | |||
{ | |||
ERROR("couldn't %s save file '%s': %s", action, path, reason) | |||
} | |||
/* | |||
Log an error in a standard library function. | |||
*/ | |||
static void log_stdlib_error(const char *action, const char *path, | |||
const char *func) | |||
{ | |||
ERROR("couldn't %s save file '%s': %s(): %s", | |||
action, path, func, strerror(errno)) | |||
} | |||
/* | |||
Parse the header of a save file, and return whether it is valid. | |||
If the load succeeds and off is not NULL, it will be set to the address | |||
of the first byte of non-header data. | |||
*/ | |||
static bool parse_save_header(void *ptr, size_t size, size_t *off, | |||
const ROM *rom, const char *path) | |||
{ | |||
const char *str = ptr; | |||
if (size < HEADER_LEN) { | |||
log_error("load", path, "too short"); | |||
return false; | |||
} | |||
if (strncmp(str, MAGIC, strlen(MAGIC))) { | |||
log_error("load", path, | |||
"invalid header (was this save created by crater?)"); | |||
return false; | |||
} | |||
str += strlen(MAGIC); | |||
int version; | |||
uint32_t prodcode; | |||
uint16_t checksum; | |||
if (sscanf(str, "%d:%06d:0x%04hX\n", &version, &prodcode, &checksum) < 3) { | |||
log_error("load", path, "invalid header (failed to parse)"); | |||
return false; | |||
} | |||
if (version != 1) { | |||
log_error("load", path, "unknown or unsupported save file version"); | |||
return false; | |||
} | |||
if (prodcode != rom->product_code || checksum != rom->expected_checksum) { | |||
log_error("load", path, "save was created for a different ROM"); | |||
return false; | |||
} | |||
if (size != HEADER_LEN + MMU_CART_RAM_SIZE) { | |||
log_error("load", path, "cart RAM size is wrong; file may be corrupt"); | |||
return false; | |||
} | |||
if (off) | |||
*off = HEADER_LEN; | |||
return true; | |||
} | |||
/* | |||
Initialize a save object, which represents persistent RAM. | |||
The given path will be used to store save data. If it already exists, | |||
it will be loaded here. The return value indicates whether the load was | |||
successful; if it is false, then a file exists in the save location but is | |||
not valid, and this save should not be used. save_free() does not need to | |||
be called in this case. | |||
*/ | |||
bool save_init(Save *save, const char *path, const ROM *rom) | |||
{ | |||
save->path = NULL; | |||
save->rom = rom; | |||
save->map = NULL; | |||
save->mapsize = 0; | |||
save->cart_ram_offset = 0; | |||
save->has_cart_ram = false; | |||
if (!path) | |||
return true; | |||
int fd = open(path, O_RDWR); | |||
if (fd < 0) { | |||
if (errno == ENOENT) { | |||
save->path = cr_strdup(path); | |||
return true; | |||
} | |||
log_stdlib_error("load", path, "open"); | |||
return false; | |||
} | |||
struct stat s; | |||
if (fstat(fd, &s) < 0) { | |||
close(fd); | |||
log_stdlib_error("load", path, "fstat"); | |||
return false; | |||
} | |||
size_t size = s.st_size; | |||
void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); | |||
close(fd); | |||
if (ptr == MAP_FAILED) { | |||
log_stdlib_error("load", path, "mmap"); | |||
return false; | |||
} | |||
size_t offset; | |||
if (!parse_save_header(ptr, size, &offset, rom, path)) | |||
return false; | |||
save->map = ptr; | |||
save->mapsize = size; | |||
save->cart_ram_offset = offset; | |||
save->has_cart_ram = true; | |||
return true; | |||
} | |||
/* | |||
Free memory previously allocated by the save. | |||
Will flush the save data to disk if necessary. | |||
*/ | |||
void save_free(Save *save) | |||
{ | |||
free(save->path); | |||
if (save->map) { | |||
msync(save->map, save->mapsize, MS_SYNC); | |||
munmap(save->map, save->mapsize); | |||
} | |||
} | |||
/* | |||
Return whether the save has existing cartridge RAM. | |||
*/ | |||
bool save_has_cart_ram(const Save *save) | |||
{ | |||
return save->has_cart_ram; | |||
} | |||
/* | |||
Return a readable and writable pointer to existing cartridge RAM. | |||
*/ | |||
uint8_t* save_get_cart_ram(Save *save) | |||
{ | |||
if (!save->has_cart_ram) | |||
return NULL; | |||
return ((uint8_t*) save->map) + save->cart_ram_offset; | |||
} | |||
/* | |||
Initialize the save file with fresh cartridge RAM as appropriate. | |||
If the save file is already loaded, return true. Otherwise, the return | |||
value indicates whether the save file creation was successful. | |||
*/ | |||
bool save_init_cart_ram(Save *save) | |||
{ | |||
if (save->has_cart_ram) | |||
return true; | |||
if (!save->path || save->map) // This should not happen normally | |||
return false; | |||
DEBUG("Creating new save file at %s", save->path) | |||
int fd = open(save->path, O_RDWR|O_CREAT|O_EXCL, 0644); | |||
if (fd < 0) { | |||
log_stdlib_error("create", save->path, "open"); | |||
return false; | |||
} | |||
// Write header | |||
static const int VERSION = 1; | |||
int header_len = dprintf(fd, "%s%d:%06d:0x%04hX\n", MAGIC, VERSION, | |||
save->rom->product_code, save->rom->expected_checksum); | |||
if (header_len < 0 || (unsigned) header_len > HEADER_LEN) { | |||
if (header_len < 0) | |||
log_stdlib_error("create", save->path, "dprintf"); | |||
else | |||
log_error("create", save->path, "header was unexpectedly long"); | |||
close(fd); | |||
unlink(save->path); | |||
return false; | |||
} | |||
// Zero out space for the cartridge RAM | |||
char buf[4096]; | |||
memset(buf, 0, sizeof(buf)); | |||
size_t rem = MMU_CART_RAM_SIZE + (HEADER_LEN - header_len); | |||
while (rem > 0) { | |||
ssize_t chunk = rem > sizeof(buf) ? sizeof(buf) : rem; | |||
if (write(fd, buf, chunk) < chunk) { | |||
log_stdlib_error("create", save->path, "write"); | |||
close(fd); | |||
unlink(save->path); | |||
return false; | |||
} | |||
rem -= chunk; | |||
} | |||
// Try to MMAP | |||
size_t size = HEADER_LEN + MMU_CART_RAM_SIZE; | |||
lseek(fd, 0, SEEK_SET); | |||
void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); | |||
close(fd); | |||
if (ptr == MAP_FAILED) { | |||
log_stdlib_error("create", save->path, "mmap"); | |||
unlink(save->path); | |||
return false; | |||
} | |||
save->map = ptr; | |||
save->mapsize = size; | |||
save->cart_ram_offset = HEADER_LEN; | |||
save->has_cart_ram = true; | |||
return true; | |||
} |
@@ -0,0 +1,29 @@ | |||
/* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#pragma once | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#include "rom.h" | |||
/* Structs */ | |||
typedef struct { | |||
char *path; | |||
const ROM *rom; | |||
void *map; | |||
size_t mapsize; | |||
size_t cart_ram_offset; | |||
bool has_cart_ram; | |||
} Save; | |||
/* Functions */ | |||
bool save_init(Save*, const char*, const ROM*); | |||
void save_free(Save*); | |||
bool save_has_cart_ram(const Save*); | |||
uint8_t* save_get_cart_ram(Save*); | |||
bool save_init_cart_ram(Save*); |