diff --git a/Makefile b/Makefile index d965fbdecf..8f0024f3ab 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ include config.mk TARGET = ssnes tools/ssnes-joyconfig -OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o +OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o HEADERS = $(wildcard */*.h) $(wildcard *.h) diff --git a/autosave.c b/autosave.c new file mode 100644 index 0000000000..587a7a53b2 --- /dev/null +++ b/autosave.c @@ -0,0 +1,118 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "autosave.h" +#include "SDL.h" +#include +#include +#include +#include +#include "general.h" + +struct autosave +{ + volatile bool quit; + SDL_mutex *lock; + + SDL_mutex *cond_lock; + SDL_cond *cond; + SDL_Thread *thread; + + void *buffer; + const void *snes_buffer; + const char *path; + size_t bufsize; + unsigned interval; +}; + +static int autosave_thread(void *data) +{ + autosave_t *save = data; + while (!save->quit) + { + autosave_lock(save); + memcpy(save->buffer, save->snes_buffer, save->bufsize); + autosave_unlock(save); + + // Should probably deal with this more elegantly. + FILE *file = fopen(save->path, "wb"); + if (file) + { + SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path); + fwrite(save->buffer, 1, save->bufsize, file); + fflush(file); + fclose(file); + } + + SDL_mutexP(save->cond_lock); + SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000); + SDL_mutexV(save->cond_lock); + } + + return 0; +} + +autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval) +{ + autosave_t *handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + handle->bufsize = size; + handle->interval = interval; + handle->path = path; + handle->buffer = malloc(size); + handle->snes_buffer = data; + + if (!handle->buffer) + { + free(handle); + return NULL; + } + + handle->lock = SDL_CreateMutex(); + handle->cond_lock = SDL_CreateMutex(); + handle->cond = SDL_CreateCond(); + + handle->thread = SDL_CreateThread(autosave_thread, handle); + + return handle; +} + +void autosave_lock(autosave_t *handle) +{ + SDL_mutexP(handle->lock); +} + +void autosave_unlock(autosave_t *handle) +{ + SDL_mutexV(handle->lock); +} + +void autosave_free(autosave_t *handle) +{ + handle->quit = true; + SDL_CondSignal(handle->cond); + SDL_WaitThread(handle->thread, NULL); + + SDL_DestroyMutex(handle->lock); + SDL_DestroyMutex(handle->cond_lock); + SDL_DestroyCond(handle->cond); + + free(handle->buffer); + free(handle); +} diff --git a/autosave.h b/autosave.h new file mode 100644 index 0000000000..59f420184b --- /dev/null +++ b/autosave.h @@ -0,0 +1,31 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#ifndef __SSNES_AUTOSAVE_H +#define __SSNES_AUTOSAVE_H + +#include + +typedef struct autosave autosave_t; + +autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval); +void autosave_lock(autosave_t *handle); +void autosave_unlock(autosave_t *handle); +void autosave_free(autosave_t *handle); + + +#endif diff --git a/config.def.h b/config.def.h index c4ffd5816b..2310ac4d11 100644 --- a/config.def.h +++ b/config.def.h @@ -159,6 +159,9 @@ static const unsigned rewind_granularity = 1; // Pause gameplay when gameplay loses focus. static const bool pause_nonactive = true; +// Saves non-volatile SRAM at a regular interval. It is measured in seconds. A value of 0 disables autosave. +static const unsigned autosave_interval = 0; + //////////////////// // Keybinds, Joypad diff --git a/general.h b/general.h index c118507756..70970af60b 100644 --- a/general.h +++ b/general.h @@ -26,6 +26,7 @@ #include "message.h" #include "rewind.h" #include "movie.h" +#include "autosave.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -94,6 +95,7 @@ struct settings unsigned rewind_granularity; bool pause_nonactive; + unsigned autosave_interval; }; enum ssnes_game_type @@ -172,6 +174,8 @@ struct global bool is_paused; + autosave_t *autosave; + #ifdef HAVE_FFMPEG ffemu_t *rec; char record_path[256]; diff --git a/libsnes.hpp b/libsnes.hpp index cd613f05f4..82fe8e93ef 100755 --- a/libsnes.hpp +++ b/libsnes.hpp @@ -7,53 +7,9 @@ extern "C" { #endif -/////////////////////////////////////////////////////////////////////////////// -// LIBSNES Super Nintendo emulation API -// -// Things you should know: -// - Linking against libsnes requires a C++ compiler. It can be compiled with -// a C99 compiler if you #include and if your C99 compiler's -// bool type is compatible with the bool type used by the C++ compiler used -// to compile libsnes. -// - libsnes supports exactly one emulated SNES; if you want to run two SNESes -// in a single process, you'll need to link against or dlopen() two -// different copies of the library. -// -// Typical usage of the libsnes API looks like this: -// -// 1. Call snes_init() to initialize the library. -// 2. Tell libsnes which callback should be called for each event (see the -// documentation on the individual callback types below. -// 3. Call one of the snes_load_cartridge_* functions to load cartridge data -// into the emulated SNES. -// 4. If the physical cart had any non-volatile storage, there may be data from -// a previous emulation run that needs to be loaded. Find the storage buffer -// by calling the snes_get_memory_* functions and load any saved data into -// it. -// 5. Call snes_set_controller_port_device() to connect appropriate controllers -// to the emulated SNES. -// 6. Call snes_get_region() to determine the intended screen refresh rate for -// this cartridge.. -// 7. Call snes_run() to emulate a single frame. Before snes_run() returns, the -// installed callbacks will be called - possibly multiple times. -// 8. When you're done, call snes_term() to free all memory allocated -// associated with the emulated SNES. -// -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Constants {{{ - -// These constants represent the two controller ports on the front of the SNES, -// for use with the snes_set_controller_port_device() function and the -// snes_input_state_t callback. #define SNES_PORT_1 0 #define SNES_PORT_2 1 -// These constants represent the different kinds of controllers that can be -// connected to a controller port, for use with the -// snes_set_controller_port_device() function and the snes_input_state_t -// callback. #define SNES_DEVICE_NONE 0 #define SNES_DEVICE_JOYPAD 1 #define SNES_DEVICE_MULTITAP 2 @@ -62,8 +18,6 @@ extern "C" { #define SNES_DEVICE_JUSTIFIER 5 #define SNES_DEVICE_JUSTIFIERS 6 -// These constants represent the button and axis inputs on various controllers, -// for use with the snes_input_state_t callback. #define SNES_DEVICE_ID_JOYPAD_B 0 #define SNES_DEVICE_ID_JOYPAD_Y 1 #define SNES_DEVICE_ID_JOYPAD_SELECT 2 @@ -94,13 +48,9 @@ extern "C" { #define SNES_DEVICE_ID_JUSTIFIER_TRIGGER 2 #define SNES_DEVICE_ID_JUSTIFIER_START 3 -// These constants will be returned by snes_get_region(), representing the -// region of the last loaded cartridge. #define SNES_REGION_NTSC 0 #define SNES_REGION_PAL 1 -// These constants represent the kinds of non-volatile memory a SNES cartridge -// might have, for use with the snes_get_memory_* functions. #define SNES_MEMORY_CARTRIDGE_RAM 0 #define SNES_MEMORY_CARTRIDGE_RTC 1 #define SNES_MEMORY_BSX_RAM 2 @@ -110,870 +60,54 @@ extern "C" { #define SNES_MEMORY_GAME_BOY_RAM 6 #define SNES_MEMORY_GAME_BOY_RTC 7 -// These constants represent the various kinds of volatile storage the SNES -// offers, to allow libsnes clients to implement things like cheat-searching -// and certain kinds of debugging. They are for use with the snes_get_memory_* -// functions. #define SNES_MEMORY_WRAM 100 #define SNES_MEMORY_APURAM 101 #define SNES_MEMORY_VRAM 102 #define SNES_MEMORY_OAM 103 #define SNES_MEMORY_CGRAM 104 -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Callback types {{{ -// -// In order to deliver controller input to the emulated SNES, and retrieve -// video frames and audio samples, you will need to register callbacks. - -// snes_audio_sample_t: -// -// This callback delivers a stereo audio sample pair generated by the -// emulated SNES. -// -// This function is called once for every audio frame (one sample from left -// and right channels). The SNES generates audio samples at a rate of about -// 32040Hz (varies from unit to unit). -// -// Because the SNES generates video at exactly 59.94fps and most computer -// monitors only support a 60fps refresh rate, real-time emulation needs to -// run slightly fast so that each computer frame displays one emulated SNES -// frame. Because the emulation runs slightly fast, and because most -// consumer audio hardware does not play audio at precisely the requested -// sample rate, you'll likely need to let the end-user tweak the effective -// sample rate by 100Hz or so in either direction. -// -// Although the parameters are declared as unsigned for historical reasons, -// the data they contain is actually signed. To work with the audio (e.g. -// resample), you will need to reinterpret the sample value: -// -// int16_t real_left = *(int16_t*)(&left); -// -// Parameters: -// -// left: -// A signed 16-bit integer containing the next audio sample from the -// left audio channel. Yes, it's declared as unsigned for historical -// reasons. -// -// right: -// A signed 16-bit integer containing the next audio sample from the -// right audio channel. Yes, it's declared as unsigned for historical -// reasons. -// - +typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height); typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right); - - -// snes_video_refresh_t: -// -// This callback delivers a single SNES frame, generated by the emulated -// SNES. The same memory buffer may be re-used later, so take a copy of the -// data if you want to refer to it after your callback returns. -// -// The framebuffer is an array of unsigned 16-bit pixels, in a somewhat -// complicated format. A quick refresher on SNES video modes: -// - The basic SNES video-mode renders 256 pixels per scanline for a total -// of 224 scanlines. -// - When "overscan" mode is enabled, the SNES renders a few extra -// scanlines at the end of the frame, for a total of 239 scanlines. -// - When "hi-res" mode is enabled, the SNES speeds up its pixel rendering -// to fit 512 pixels per scanline. -// - Normally the SNES renders its pixels to one field of the interlaced -// NTSC signal, but if "interlaced" mode is enabled the SNES renders -// a second set of scanlines inbetween the regular set, for a total of -// 448 (normal) or 478 (overscan) scanlines. -// -// Thus, the framebuffer memory layout for a standard 256x240 frame looks -// something like this (note that 'height' has been reduced to 4 or 8 for -// these examples): -// -// 0 1024b -// ,---------------------------------------. -// |====== width ======|...................| -. -// |.......................................| | -// |===================|...................| | -// |.......................................| +- height = 4 -// |===================|...................| | -// |.......................................| | -// |===================|...................| | -// |.......................................| -' -// `---------------------------------------' -// -// A hi-res frame would look like this: -// -// 0 1024b -// ,---------------------------------------. -// |================ width ================| -. -// |.......................................| | -// |=======================================| | -// |.......................................| +- height = 4 -// |=======================================| | -// |.......................................| | -// |=======================================| | -// |.......................................| -' -// `---------------------------------------' -// -// An interlaced frame would look like this: -// -// 0 1024b -// ,---------------------------------------. -// |====== width ======|...................| -. -// |===================|...................| | -// |===================|...................| | -// |===================|...................| +- height = 8 -// |===================|...................| | -// |===================|...................| | -// |===================|...................| | -// |===================|...................| -' -// `---------------------------------------' -// -// And of course a hi-res, interlaced frame would look like this: -// -// 0 1024b -// ,---------------------------------------. -// |================ width ================| -. -// |=======================================| | -// |=======================================| | -// |=======================================| |+- height = 8 -// |=======================================| | -// |=======================================| | -// |=======================================| | -// |=======================================| -' -// `---------------------------------------' -// -// More succinctly: -// - the buffer begins at the top-left of the frame -// - the first "width" bytes contain the first scanline. -// - if the emulated SNES is in an interlaced video-mode (that is, if the -// "height" parameter" is 448 or 478) then the second scanline begins at -// an offset of 1024 bytes (512 pixels) after the first. -// - otherwise the second scanline begins at an offset of 2048 bytes (1024 -// pixels) after the first. -// - there are "height" scanlines in total. -// -// Each pixel contains a 15-bit RGB tuple: 0RRRRRGGGGGBBBBB (XRGB1555) -// -// Example code: -// -// void pack_frame (uint16_t * restrict out, const uint16_t * restrict in, -// unsigned width, unsigned height) -// { -// // Normally our pitch is 2048 bytes. -// int pitch_pixels = 1024; -// // If we have an interlaced mode, pitch is 1024 bytes. -// if ( height == 448 || height == 478 ) -// pitch_pixels = 512; -// -// for ( int y = 0; y < height; y++ ) -// { -// const uint16_t *src = in + y * pitch_pixels; -// uint16_t *dst = out + y * width; -// -// memcpy(dst, src, width * sizeof(uint16_t)); -// } -// } -// -// Parameters: -// -// data: -// a pointer to the beginning of the framebuffer described above. -// -// width: -// the width of the frame, in pixels. -// -// height: -// the number of scanlines in the frame. - -typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, - unsigned height); - -// snes_input_poll_t: -// -// This callback requests that you poll your input devices for events, if -// required. -// -// Generally called once per frame before the snes_input_state_t callback is -// called. -// - typedef void (*snes_input_poll_t)(void); - -// snes_input_state_t: -// -// This callback asks for information about the state of a particular input. -// -// The callback may be called multiple times per frame with the same -// parameters. -// -// The callback might not be called at all, if the software running in the -// emulated SNES does not try to probe the controllers. -// -// The callback will not be called for a particular port if DEVICE_NONE is -// connected to it. -// -// If you wish to emulate any kind of turbo-fire, etc. then you will need to -// put that logic into this callback. -// -// Parameters: -// -// port: -// One of the constants SNES_PORT_1 or SNES_PORT_2, describing which -// controller port you should report. -// -// device: -// One of the SNES_DEVICE_* constants describing which type of device -// is currently connected to the given port. -// -// index: -// A number describing which of the devices connected to the port is -// being reported. It's only useful for SNES_DEVICE_MULTITAP and -// SNES_DEVICE_JUSTIFIERS - for other device types, this parameter is -// always 0. -// -// id: -// One of the SNES_DEVICE_ID_* constants for the given device, -// describing which button or axis is being reported (for -// SNES_DEVICE_MULTITAP, use the SNES_DEVICE_ID_JOYPAD_* IDs; for -// SNES_DEVICE_JUSTIFIERS use the SNES_DEVICE_ID_JUSTIFIER_* IDs.). -// -// Returns: -// -// An integer representing the state of the described button or axis. -// -// - If it represents a digital input such as SNES_DEVICE_ID_JOYPAD_B or -// SNES_DEVICE_ID_MOUSE_LEFT), return 1 if the button is pressed, and -// 0 otherwise. -// - If "id" is SNES_DEVICE_ID_MOUSE_X or SNES_DEVICE_ID_MOUSE_Y then -// return the relative movement of the mouse during the current frame; -// values outside the range -127 to +127 will be clamped. -// - If "id" is one of the light-gun axes (such as -// SNES_DEVICE_ID_JUSTIFIER_Y or SNES_DEVICE_ID_SUPER_SCOPE_X), you -// should return the relative movement of the pointing device during the -// current frame. - -typedef int16_t (*snes_input_state_t)(bool port, unsigned device, - unsigned index, unsigned id); - -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// libsnes setup {{{ -// -// These functions are used to get information about and manipulate the libsnes -// library itself, not the emulated SNES it implements. - -// snes_library_revision_major: -// -// Returns the major API version of this libsnes implementation. -// -// This number is increased every time there is a compatibility-breaking -// change to the libsnes API. At startup, your program should call this -// function and compare the return value to the major API version the -// program was designed to work with. If they are different, your program -// will (very likely) not work with this libsnes implementation. -// -// For example, if your program was designed to work with the libsnes API -// whose major.minor revision was 1.5, and this function returns a major -// version of 2, you have a problem. -// -// Returns: -// -// An integer, the major API version of this libsnes implementation. +typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id); unsigned snes_library_revision_major(void); - -// snes_library_revision_minor: -// -// Returns the minor API version of this libsnes implementation. -// -// This number is increased every time there is a backwards-compatible -// change to the libsnes API. At startup, your program should call this -// function and compare the return value to the minor API version the -// program was designed to work with. If the return value is less than the -// expected minor version, your program will (very likely) not work with -// this libsnes implementation. -// -// For example, if your program was designed to work with the libsnes API -// whose major.minor revision was 1.5, and this libsnes implementation's -// major.minor version is 1.3, it's probably missing features you require. -// On the other hand, if this libsnes implementation's major.minor version -// is 1.9, it probably has extra fancy features you don't need to worry -// about. -// -// Returns: -// -// An integer, the minor API version of this libsnes implementation. - unsigned snes_library_revision_minor(void); -// snes_init: -// -// Initializes the libsnes implementation. -// -// This function must be called exactly once before any other library -// functions are called. - -void snes_init(void); - -// snes_term: -// -// Shuts down the libsnes implementation. -// -// This function must be called exactly once. Once called, you should not -// call any other libsnes functions besides (perhaps) snes_init(). - -void snes_term(void); - -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Callback registration {{{ -// -// Note that all callbacks should be set up before snes_run() is called for the -// first time. - -// snes_set_video_refresh: -// -// Sets the callback that will receive new video frames. -// -// See the documentation for snes_video_refresh_t for details. -// -// Parameters: -// -// A pointer to a function matching the snes_video_refresh_t call -// signature. - void snes_set_video_refresh(snes_video_refresh_t); - -// snes_set_audio_sample -// -// Sets the callback that will receive new audio sample pairs. -// -// See the documentation for snes_audio_sample_t for details. -// -// Parameters: -// -// A pointer to a function matching the snes_audio_sample_t call -// signature. - void snes_set_audio_sample(snes_audio_sample_t); - -// snes_set_input_poll: -// -// Sets the callback that will be notified to poll input devices. -// -// See the documentation for snes_input_poll_t for details. -// -// Parameters: -// -// A pointer to a function matching the snes_input_poll_t call signature. - void snes_set_input_poll(snes_input_poll_t); - -// snes_set_input_state: -// -// Sets the callback that will be used to read input device state. -// -// See the documentation for snes_input_state_t for details. -// -// Parameters: -// -// A pointer to a function matching the snes_input_state_t call signature. - void snes_set_input_state(snes_input_state_t); -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// SNES operation {{{ -// -// Functions for manipulating the emulated SNES. - -// snes_set_controller_port_device: -// -// Sets the input device connected to a given controller port. -// -// Connecting a device to a port implicitly removes any device previously -// connected to that port. To remove a device without connecting a new one, -// pass DEVICE_NONE as the device parameter. From this point onward, the -// callback passed to set_input_state_cb() will be called with the -// appropriate device, index and id parameters. -// -// If this function is never called, the default is to have a DEVICE_JOYPAD -// connected to both ports. -// -// Calling this callback from inside the set_input_state_cb() has undefined -// results, so don't do that. -// -// Parameters: -// -// port: -// One of the constants SNES_PORT_1 or SNES_PORT_2, describing which -// controller port is being configured. -// -// device: -// One of the SNES_DEVICE_* constants describing which type of device -// should be connected to the given port. Note that some devices can -// only be connected to SNES_PORT_2. Attempting to connect -// a port-2-only device to SNES_PORT_1 has undefined results. -// -// These devices work in either port: -// - SNES_DEVICE_NONE: No device is connected to this port. -// - SNES_DEVICE_JOYPAD: A standard SNES gamepad. -// - SNES_DEVICE_MULTITAP: A multitap controller, which acts like -// 4 SNES_DEVICE_JOYPADs. Your input state callback will be -// passed "id" parameters between 0 and 3, inclusive. -// - SNES_DEVICE_MOUSE: A SNES mouse controller, as shipped with -// Mario Paint. -// -// These devices only work properly when connected to port 2: -// - SNES_DEVICE_SUPER_SCOPE: A Nintendo Super Scope light-gun -// device. -// - SNES_DEVICE_JUSTIFIER: A Konami Justifier light-gun device. -// - SNES_DEVICE_JUSTIFIERS: Two Konami Justifier light-gun -// devices, daisy-chained together. Your input state callback -// will be passed "id" parameters 0 and 1. - void snes_set_controller_port_device(bool port, unsigned device); +void snes_set_cartridge_basename(const char *basename); -// snes_power: -// -// Turns the emulated console off and back on. -// -// This functionality is sometimes called "hard reset" and guarantees that -// all hardware state is reset to a reasonable default. -// -// Before bsnes v070r07, this resets the controller ports to both contain -// SNES_DEVICE_JOYPADs. -// -// This requires that a cartridge is loaded. - +void snes_init(void); +void snes_term(void); void snes_power(void); - -// snes_reset: -// -// Presses the "reset" button on the emulated SNES. -// -// This functionality is sometimes called "soft reset". Most hardware state -// is reset to a reasonable befault, but not all. -// -// As of bsnes v073r01, this function (as a side-effect) resets the -// controller ports to both contain SNES_DEVICE_JOYPADs. -// -// This requires that a cartridge is loaded. - void snes_reset(void); - -// snes_run(): -// -// Runs the emulated SNES until the end of the next video frame. -// -// Usually causes each registered callback to be called before returning. -// -// This function will run as fast as possible. It is up to the caller to -// make sure that the game runs at the intended speed. -// -// For optimal A/V sync, make sure that the audio callback never blocks for -// longer than a frame (approx 16ms for NTSC, 20ms for PAL) -// -// Optimally, it should never block for more than a few ms at a time. - void snes_run(void); -// snes_get_region(): -// -// Determines the intended frame-rate of the loaded cartridge. -// -// The two main SNES hardware variants are the US/Japan variant, designed -// for NTSC output, and the European variant, designed for PAL output. -// However, the world is not quite so tidy as that, and there are countries -// like Brazil that use PAL output at NTSC frame-rates. -// -// For historical reasons this function is named snes_get_region(), but -// effectively the only information you can reliably infer is the -// frame-rate. -// -// Returns: -// -// One of the SNES_REGION_* constants. SNES_REGION_PAL means 50fps, -// SNES_REGION_NTSC means 60fps. - -bool snes_get_region(void); - -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Save state support {{{ -// -// libsnes has the ability to save the current emulation state and restore it -// at a later time. -// -// Note 1: It is impossible to reliably restore the *exact* state, although the -// difference is only a few cycles. If you demand the ability to reliably -// restore state, call snes_serialize() after each frame to ensure the emulated -// SNES is in a state that can be reliably restored. -// -// Note 2: The save state information is specific to a particular cartridge -// loaded into a particular version of a particular libsnes implementation. -// Unfortunately, there is not yet a way to determine whether a given save -// state is compatible with a given libsnes implementation, other than by -// loading it. However, if snes_serialize_size() does not match the size of an -// old save state, that's a strong hint that something has incompatibly -// changed. - -// snes_serialize_size: -// -// Determines the minimum size of a save state. -// -// This value can change depending on the features used by the loaded -// cartridge, and the version of the libsnes implementation used. -// -// Returns: -// -// An integer representing the number of bytes required to store the -// current emulation state. - unsigned snes_serialize_size(void); - -// snes_serialize: -// -// Serialize the current emulation state to a buffer. -// -// If the allocated buffer is smaller than the size returned by -// snes_serialize_size(), serialization will fail. If the allocated buffer -// is larger, only the first snes_serialize_size() bytes will be written to. -// -// The resulting buffer may be stored, and later passed to -// snes_unserialize() to restore the saved emulation state. -// -// Parameters: -// -// data: -// A pointer to an allocated buffer of memory. -// -// size: -// The size of the buffer pointed to by "data". Should be greater than -// or equal to the value returned by snes_serialize_size(). -// -// Returns: -// -// A boolean; True means the emulation state was serialized successfully, -// False means a problem was encountered. - bool snes_serialize(uint8_t *data, unsigned size); - -// snes_unserialize: -// -// Unserialize the emulation state from a buffer. -// -// If the serialization data in the buffer does not appear to be compatible -// with the current libsnes implementation, the function returns False and -// the current emulation state is not modified. -// -// Parameters: -// -// data: -// A pointer to an allocated buffer of memory. -// -// size: -// The size of the buffer pointed to by "data". Should be greater than -// or equal to the value returned by snes_serialize_size(). -// -// Returns: -// -// A boolean; True means the emulation state was loaded successfully, -// False means a problem was encountered. - bool snes_unserialize(const uint8_t *data, unsigned size); -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Cheat support {{{ -// -// libsnes does not include any kind of cheat management API; the intention is -// that any change to the set of applied cheats will cause the containing -// application to call snes_cheat_reset() then apply the new set of cheats with -// snes_cheat_set(). -// -// Any currently-applied cheats are discarded when a new cartridge is loaded. - -// snes_cheat_reset: -// -// Discards all cheat codes applied to the emulated SNES. - void snes_cheat_reset(void); - -// snes_cheat_set: -// -// Apply a sequence of cheat codes to the emulated SNES. -// -// Since a "cheat code" is basically an instruction to override the value of -// a particular byte in the SNES' memory, more complex cheats may require -// several individual codes applied at once. There's no effective difference -// between applying these codes in a group with one call to -// snes_cheat_set(), or applying them one at a time with individual calls. -// However, most cheat databases will have a collection of available cheats -// for each game, where each item in the collection has a description and -// a sequence of codes to be applied as a unit. This API makes it easy to -// present the list of descriptions to the user, and apply each cheat the -// user selects. -// -// Parameters: -// -// index: -// The given cheat code will be stored at this index in the array of -// applied cheats. If a cheat already exists at this location, it will -// be replaced by the new cheat. If the index is larger than any -// previously specififed index, the array will be resized to -// accommodate. -// -// enabled: -// True means that the cheat will actually be applied, False means -// that the cheat will have no effect. There is no way to enable or -// disable a cheat after it has been added, other than to call -// snes_cheat_set() a second time with the same values for "index" and -// "code". -// -// code: -// A string containing a sequence of cheat codes separated by '+' -// characters. Any spaces in the string will be removed before -// parsing. -// -// Each code in the sequence must be in either GameGenie format -// ("1234-ABCD") or ProActionReplay format ("1234AB:CD" or -// "1234ABCD"). - void snes_cheat_set(unsigned index, bool enabled, const char *code); -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Cartridge loading and unloading {{{ -// -// Before calling snes_run(), a cartridge must be loaded into the emulated SNES -// so that it has code to run. -// -// Loading a cartridge of any kind calls snes_cheat_reset() as a side-effect. - -// snes_load_cartridge_normal: -// -// Load a normal ROM image into the emulated SNES. -// -// Parameters: -// -// rom_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes where the ROM image is mapped into the SNES address -// space, what special chips it uses (and where they're mapped), etc. -// -// If NULL, libsnes will guess a memory map. The guessed memory map -// should be correct for all licenced games in all regions. -// -// rom_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image. -// -// rom_size: -// The length of the rom_data array, in bytes. -// -// Returns: -// -// A boolean; True means the cartridge was loaded correctly, False means -// an error occurred. - bool snes_load_cartridge_normal( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size ); -// snes_load_cartridge_bsx: -// -// Load a BS-X base cart image, optionally with a memory pack. -// -// The Satellaview system, abbreviated "BS-X" for unclear reasons, was an -// addon for the Super Famicom that connected it to the St. GIGA satellite -// network. The network would broadcast games at a particular time, and -// users could download them to replaceable memory packs. -// -// For more information, see http://en.wikipedia.org/wiki/Satellaview -// -// Parameters: -// -// rom_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes where the BS-X base cartridge ROM image is mapped -// into the SNES address space. -// -// If NULL, libsnes will guess a memory map. The guessed memory map -// should be correct for all known BS-X base cartridge images. -// -// rom_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image of the BS-X base cartridge. -// -// The BS-X base cartridge is named "BS-X - Sore wa Namae o Nusumareta -// Machi no Monogatari" in some SNES game databases. -// -// rom_size: -// The length of the rom_data array, in bytes. -// -// bsx_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes the BS-X memory pack. -// -// This parameter is currently ignored and should be passed as NULL. -// -// bsx_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless image of the BS-X memory-pack. -// -// If NULL, libsnes will behave as though no memory-pack were inserted -// into the base cartridge. -// -// bsx_size: -// The length of the bsx_data array, in bytes. -// -// Returns: -// -// A boolean; True means the cartridge was loaded correctly, False means -// an error occurred. - -bool snes_load_cartridge_bsx( - const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, - const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size -); - -// snes_load_cartridge_bsx_slotted: -// -// Load a BS-X slotted cartridge, optionally with a memory pack. -// -// A BS-X slotted cartridge is an ordinary SNES cartridge, with a slot in -// the top that accepts the same memory packs used by the BS-X base -// cartridge. -// -// Parameters: -// -// rom_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes where the ROM image is mapped into the SNES address -// space, what special chips it uses (and where they're mapped), etc. -// -// If NULL, libsnes will guess a memory map. The guessed memory map -// should be correct for all licenced games in all regions. -// -// rom_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image. -// -// rom_size: -// The length of the rom_data array, in bytes. -// -// bsx_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes the BS-X memory pack. -// -// This parameter is currently ignored and should be passed as NULL. -// -// bsx_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless image of the BS-X memory-pack. -// -// If NULL, libsnes will behave as though no memory-pack were inserted -// into the base cartridge. -// -// bsx_size: -// The length of the bsx_data array, in bytes. -// -// Returns: -// -// A boolean; True means the cartridge was loaded correctly, False means -// an error occurred. - bool snes_load_cartridge_bsx_slotted( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size ); -// snes_load_cartridge_sufami_turbo: -// -// Load a SuFami Turbo base cart image, optionally with game packs. -// -// The SuFami Turbo was a cartridge available for the Super Famicom, created -// by Bandai, with two slots in the top designed to accept special -// mini-cartridges. The cartridge in Slot A was the cartridge that actually -// ran, while the cartridge in Slot B was made available to the Slot -// A cartridge, enabling sharing of save-game data or using characters from -// one game in another. -// -// For more information, see: http://en.wikipedia.org/wiki/Sufami_Turbo -// -// Parameters: -// -// rom_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes where the SuFami Turbo base cartridge ROM image is -// mapped into the SNES address space. -// -// If NULL, libsnes will guess a memory map. The guessed memory map -// should be correct for all known SuFami Turbo base cartridge images. -// -// rom_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image of the SuFami Turbo base -// cartridge. -// -// The SuFami Turbo base cartridge is named "Sufami Turbo" in some -// SNES game databases. -// -// rom_size: -// The length of the rom_data array, in bytes. -// -// sta_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes the Sufami Turbo cartridge in Slot A. -// -// This parameter is currently ignored and should be passed as NULL. -// -// sta_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image of the SuFami Turbo cartridge -// in Slot A. -// -// This is the cartridge that will be executed by the SNES. -// -// If NULL, libsnes will behave as though no cartridge were inserted -// into the Slot A. -// -// sta_size: -// The length of the sta_data array, in bytes. -// -// stb_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes the Sufami Turbo cartridge in Slot B. -// -// This parameter is currently ignored and should be passed as NULL. -// -// stb_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image of the SuFami Turbo cartridge -// in Slot B. -// -// The data in this cartridge will be made available to the cartridge -// in Slot A. -// -// If NULL, libsnes will behave as though no cartridge were inserted -// into Slot B. -// -// stb_size: -// The length of the stb_data array, in bytes. -// -// Returns: -// -// A boolean; True means the cartridge was loaded correctly, False means -// an error occurred. +bool snes_load_cartridge_bsx( + const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, + const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size +); bool snes_load_cartridge_sufami_turbo( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, @@ -981,240 +115,17 @@ bool snes_load_cartridge_sufami_turbo( const char *stb_xml, const uint8_t *stb_data, unsigned stb_size ); -// snes_load_cartridge_super_game_boy: -// -// Load a Super Game Boy base cart, optionally with a Gameboy cartridge. -// -// The Super Game Boy was a cartridge available for the Super Famicom and -// Super Nintendo that accepted ordinary (original) Gameboy cartridges and -// allowed the user to play them with a Super Nintendo controller, on a TV. -// It extended the orginal Gameboy hardware in a few ways, including the -// ability to display games in various palettes (rather than strictly -// monochrome), to display a full-colour border image around the Gameboy -// video output, or even run native SNES code to enhance the game. -// -// For more information, see: http://en.wikipedia.org/wiki/Super_Game_Boy -// -// Up until bsnes v073, loading Super Game Boy cartridges only works if the -// libsupergameboy library from the bsnes release is installed. bsnes v074 -// includes a custom Gameboy emulation core, and external code is no longer -// required. -// -// Parameters: -// -// rom_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes where the Super Game Boy base cartridge ROM image is -// mapped into the SNES address space. -// -// If NULL, libsnes will guess a memory map. The guessed memory map -// should be correct for all known Super Game Boy base cartridge -// images. -// -// rom_data: -// A pointer to a byte array containing the uncompressed, -// de-interleaved, headerless ROM image of the Super Game Boy base -// cartridge. -// -// Appropriate base cartridge images are named "Super Game Boy" or -// "Super Game Boy 2" in some SNES game databases. -// -// rom_size: -// The length of the rom_data array, in bytes. -// -// dmg_xml: -// A pointer to a null-terminated string containing an XML memory map -// that describes the inserted Gameboy cartridge. -// -// If NULL, libsnes will guess a memory map. The guesed memory map -// should be correct for all licensed original Gameboy games in all -// regions. -// -// dmg_data: -// A pointer to a byte array containing the uncompressed, headerless -// ROM image of the inserted Gameboy cartridge. -// -// If NULL, libsnes will behave as though no cartridge were inserted. -// -// dmg_size: -// The length of the dmg_size array, in bytes. -// -// Returns: -// -// A boolean; True means the cartridge was loaded correctly, False means -// an error occurred. - bool snes_load_cartridge_super_game_boy( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, const char *dmg_xml, const uint8_t *dmg_data, unsigned dmg_size ); -// snes_set_cartridge_basename: -// -// Set the location and name of the loaded cartridge. -// -// libsnes uses this information to locate additional resources the -// cartridge might require. Currently, these resources include: -// -// - The MSU-1 data pack and associated audio tracks, if the cartridge makes -// use of bsnes' MSU-1 special-chip. -// - The serial-port data receiving library, if the cartridge makes uses of -// bsnes' serial-data-over-controller-port feature. -// -// Parameters: -// -// basename: -// The path and basename of the loaded cartridge. For example, if the -// full path to the loaded cartridge is "/path/to/filename.sfc", this -// parameter should be set to "/path/to/filename". - -void snes_set_cartridge_basename(const char *basename); - -// snes_unload_cartridge: -// -// Unloads the currently loaded cartridge from the emulated SNES. -// -// You will be unable to call snes_run() until another cartridge is loaded. - void snes_unload_cartridge(void); -////////////////////////////////////////////////////////////////////////////}}} - -/////////////////////////////////////////////////////////////////////////////// -// Volatile and non-volatile storage {{{ -// -// Certain SNES cartridges include non-volatile storage or other kinds of data -// that would persist after the SNES is turned off. libsnes exposes this -// information via the snes_get_memory_data() and snes_get_memory_size() -// functions. Since version 1.2 of the libsnes API, libsnes also exposes the -// contents of volatile storage such as WRAM and VRAM. -// -// After a cartridge is loaded, call snes_get_memory_size() and -// snes_get_memory_data() with the various SNES_MEMORY_* constants to determine -// which kinds of non-volatile storage the cartridge supports - unsupported -// storage types will have a size of 0 and the data pointer NULL. -// -// If you have non-volatile storage data from a previous run, you can memcpy() -// the data from your storage into the buffer described by the data and size -// values before calling snes_run(). Do not load non-volatile storage data if -// the size of the data you have is different from the size returned by -// snes_get_memory_size(). -// -// Before calling snes_unload_cartridge(), you should copy the contents of the -// relevant storage buffers into a file (or some non-volatile storage of your -// own) so that you can load it the next time you load the same cartridge into -// the emulated SNES. Do not call free() on the storage buffers; they will be -// handled by libsnes. Note: It is not necessary to store the contents of -// volatile storage; the emulated SNES expects information in volatile storage -// to be lost (hence the name 'volatile'). -// -// Because non-volatile storage is read and written by the software running on -// the emulated SNES, it should be compatible between different versions of -// different emulators running on different platforms, unlike save states. -// -// The various kinds of non-volatile storage and their uses are: -// -// SNES_MEMORY_CARTRIDGE_RAM: -// Standard battery-backed static RAM (SRAM). Traditionally, the SRAM for -// a ROM image named "foo.sfc" is stored in a file named "foo.srm" beside -// it. -// -// SNES_MEMORY_CARTRIDGE_RTC: -// Real-time clock data. Traditionally, the RTC data for a ROM image named -// "foo.sfc" is stored in a file named "foo.rtc" beside it. -// -// SNES_MEMORY_BSX_RAM: -// RAM data used with the BS-X base cartridge. -// -// SNES_MEMORY_BSX_PRAM: -// PRAM data used with the BS-X base cartridge. -// -// SNES_MEMORY_SUFAMI_TURBO_A_RAM: -// RAM data stored in the mini-cartridge inserted into Slot A of the -// SuFami Turbo base cartridge. -// -// SNES_MEMORY_SUFAMI_TURBO_B_RAM: -// RAM data stored in the mini-cartridge inserted into Slot B of the -// SuFami Turbo base cartridge. -// -// SNES_MEMORY_GAME_BOY_RAM: -// Standard battery-backed static RAM (SRAM) in the Gameboy cartridge -// inserted into the Super Game Boy base cartridge. Not all Gameboy games -// have SRAM. -// -// SNES_MEMORY_GAME_BOY_RTC: -// Real-time clock data in the Gameboy cartridge inserted into the Super -// Game Boy base cartridge. Not all Gameboy games have an RTC. -// -// The various kinds of volatile storage are: -// -// SNES_MEMORY_WRAM: -// Working RAM, accessible by the CPU. SNES software tends to keep runtime -// information in here; games' life-bars and inventory contents and so -// forth are in here somewhere. -// -// SNES_MEMORY_APURAM: -// RAM accessible by the Audio Processing Unit. Contains audio samples, -// music data and the code responsible for feeding the right notes to the -// DSP at the right times. -// -// SNES_MEMORY_VRAM: -// Video RAM. Stores almost everything related to video output, including -// the patterns used for each tile and sprite, tilemaps for each -// background. The exact format used depends on the current video mode of -// the emulated SNES. -// -// SNES_MEMORY_OAM: -// Object Attribute Memory. Stores the location, orientation and priority -// of all the sprites the SNES displays. -// -// SNES_MEMORY_CGRAM: -// Color Generator RAM. Contains the colour palettes used by tiles and -// sprites. Each palette entry is stored in a 16-bit int, in the standard -// XBGR1555 format. - -// snes_get_memory_data: -// -// Returns a pointer to the given non-volatile storage buffer. -// -// This requires that a cartridge is loaded. -// -// Parameters: -// -// id: -// One of the SNES_MEMORY_* constants. -// -// Returns: -// -// A pointer to the memory buffer used for storing the given type of data. -// The size of the buffer can be obtained from snes_get_memory_size(). -// -// If NULL, the loaded cartridge does not store the given type of data. - +bool snes_get_region(void); uint8_t* snes_get_memory_data(unsigned id); - -// snes_get_memory_size: -// -// Returns the size of the given non-volatile storage buffer. -// -// This requires that a cartridge is loaded. -// -// Parameters: -// -// id: -// One of the SNES_MEMORY_* constants. -// -// Returns: -// -// The size of the memory buffer used for storing the given type of data. -// A pointer to the buffer can be obtained from snes_get_memory_data(). -// -// If 0, the loaded cartridge does not store the given type of data. - unsigned snes_get_memory_size(unsigned id); -////////////////////////////////////////////////////////////////////////////}}} - #ifdef __cplusplus } #endif diff --git a/settings.c b/settings.c index f26e28b35a..37128bb3a6 100644 --- a/settings.c +++ b/settings.c @@ -131,6 +131,7 @@ static void set_defaults(void) g_settings.rewind_buffer_size = rewind_buffer_size; g_settings.rewind_granularity = rewind_granularity; g_settings.pause_nonactive = pause_nonactive; + g_settings.autosave_interval = autosave_interval; assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1)); assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2)); @@ -330,6 +331,7 @@ static void parse_config_file(void) CONFIG_GET_INT(rewind_granularity, "rewind_granularity"); CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive"); + CONFIG_GET_INT(autosave_interval, "autosave_interval"); read_keybinds(conf); diff --git a/ssnes.c b/ssnes.c index ac1f65ff56..bbaa4c78c0 100644 --- a/ssnes.c +++ b/ssnes.c @@ -734,6 +734,25 @@ static void deinit_movie(void) bsv_movie_free(g_extern.bsv_movie); } +static void init_autosave(void) +{ + if (g_settings.autosave_interval > 0) + { + g_extern.autosave = autosave_new(g_extern.savefile_name_srm, + psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM), + psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM), + g_settings.autosave_interval); + if (!g_extern.autosave) + SSNES_WARN("Could not initialize autosave.\n"); + } +} + +static void deinit_autosave(void) +{ + if (g_extern.autosave) + autosave_free(g_extern.autosave); +} + static void fill_pathnames(void) { if (!g_extern.bsv_movie_playback) @@ -1069,6 +1088,7 @@ int main(int argc, char *argv[]) init_recording(); #endif + init_autosave(); // Main loop for(;;) @@ -1083,7 +1103,15 @@ int main(int argc, char *argv[]) // Run libsnes for one frame. if (!g_extern.is_paused) + { + if (g_extern.autosave) + autosave_lock(g_extern.autosave); + psnes_run(); + + if (g_extern.autosave) + autosave_unlock(g_extern.autosave); + } else { input_poll(); @@ -1099,6 +1127,8 @@ int main(int argc, char *argv[]) } } + deinit_autosave(); + #ifdef HAVE_FFMPEG deinit_recording(); #endif diff --git a/ssnes.cfg b/ssnes.cfg index 8dbf70698c..4e6de705bb 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -214,3 +214,7 @@ # Pause gameplay when window focus is lost. # pause_nonactive = true + +# Autosaves the non-volatile SRAM at a regular interval. This is disabled by default unless set otherwise. +# The interval is measured in seconds. A value of 0 disables autosave. +# autosave_interval =