| @@ -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*); | |||