RetroArch/ssnes.c

2323 lines
67 KiB
C
Raw Normal View History

2011-01-17 19:54:58 +00:00
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
2012-01-08 00:08:18 +00:00
* Copyright (C) 2010-2012 - Hans-Kristian Arntzen
2010-05-28 16:21:33 +00:00
*
* 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 <http://www.gnu.org/licenses/>.
*/
2011-12-24 12:46:12 +00:00
#include "boolean.h"
2011-11-30 16:32:30 +00:00
#include "libsnes.hpp"
2010-05-26 19:27:37 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2011-09-27 13:31:25 +00:00
#include <ctype.h>
2011-10-06 21:43:48 +00:00
#include <errno.h>
2010-05-28 00:45:18 +00:00
#include "driver.h"
#include "file.h"
#include "general.h"
2010-12-30 12:54:49 +00:00
#include "dynamic.h"
2011-12-02 00:34:02 +00:00
#include "audio/utils.h"
2011-01-03 19:46:50 +00:00
#include "record/ffemu.h"
2011-01-31 15:48:42 +00:00
#include "rewind.h"
2011-02-02 11:10:27 +00:00
#include "movie.h"
2011-04-03 20:29:36 +00:00
#include "strl.h"
2011-05-15 15:16:29 +00:00
#include "screenshot.h"
2011-04-17 11:30:59 +00:00
#include "cheats.h"
2011-11-30 10:48:53 +00:00
#include "getopt_ssnes.h"
#if defined(_WIN32) && !defined(_XBOX)
2011-02-05 19:46:58 +00:00
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
2011-12-24 23:59:46 +00:00
#include "msvc/msvc_compat.h"
#elif defined(_XBOX)
#include <xtl.h>
#include "360/msvc_compat.h"
2011-02-05 19:46:58 +00:00
#endif
2011-02-04 21:47:37 +00:00
#ifdef __APPLE__
2011-02-04 22:47:36 +00:00
#include "SDL.h"
// OSX seems to really need -lSDLmain,
// so we include SDL.h here so it can hack our main.
2011-08-26 15:32:04 +00:00
// We want to use -mconsole in Win32, so we need main().
2011-02-04 21:47:37 +00:00
#endif
2010-08-16 16:40:17 +00:00
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
static void set_fast_forward_button(bool new_button_state, bool new_hold_button_state)
2010-08-16 16:40:17 +00:00
{
bool update_sync = false;
2010-08-16 16:40:17 +00:00
static bool old_button_state = false;
static bool old_hold_button_state = false;
2010-08-16 16:40:17 +00:00
static bool syncing_state = false;
2010-08-16 16:40:17 +00:00
if (new_button_state && !old_button_state)
{
syncing_state = !syncing_state;
update_sync = true;
}
else if (old_hold_button_state != new_hold_button_state)
{
syncing_state = new_hold_button_state;
update_sync = true;
}
if (update_sync)
{
2010-12-29 18:18:37 +00:00
if (g_extern.video_active)
2010-08-19 13:56:00 +00:00
driver.video->set_nonblock_state(driver.video_data, syncing_state);
2010-12-29 18:18:37 +00:00
if (g_extern.audio_active)
2010-12-29 18:43:17 +00:00
driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true);
if (syncing_state)
g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size;
else
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
2010-08-16 16:40:17 +00:00
}
2010-08-16 16:40:17 +00:00
old_button_state = new_button_state;
old_hold_button_state = new_hold_button_state;
2010-08-16 16:40:17 +00:00
}
static inline unsigned lines_to_pitch(unsigned height)
{
if (g_extern.system.pitch == 0) // SNES semantics
return ((height == 448) || (height == 478)) ? 1024 : 2048;
else
return g_extern.system.pitch;
}
#ifndef HAVE_SCREENSHOTS_BUILTIN
2011-10-23 10:38:11 +00:00
static void take_screenshot(void)
2011-05-15 15:16:29 +00:00
{
2011-08-16 10:11:44 +00:00
if (!(*g_settings.screenshot_directory))
2011-05-15 15:16:29 +00:00
return;
2011-10-23 10:38:11 +00:00
bool ret = false;
if (g_extern.frame_cache.data)
{
const uint16_t *data = g_extern.frame_cache.data;
unsigned width = g_extern.frame_cache.width;
unsigned height = g_extern.frame_cache.height;
ret = screenshot_dump(g_settings.screenshot_directory,
data,
width, height,
lines_to_pitch(height));
2011-10-23 10:38:11 +00:00
}
2011-05-15 15:16:29 +00:00
const char *msg = NULL;
if (ret)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Taking screenshot.\n");
msg = "Taking screenshot.";
2011-05-15 15:16:29 +00:00
}
else
{
SSNES_WARN("Failed to take screenshot ...\n");
msg = "Failed to take screenshot.";
2011-05-15 15:16:29 +00:00
}
msg_queue_clear(g_extern.msg_queue);
2011-10-23 10:38:11 +00:00
if (g_extern.is_paused)
{
msg_queue_push(g_extern.msg_queue, msg, 1, 1);
2012-01-21 11:12:44 +00:00
ssnes_render_cached_frame();
2011-10-23 10:38:11 +00:00
}
else
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
2011-05-15 15:16:29 +00:00
}
#endif
2011-05-15 15:16:29 +00:00
2010-05-26 19:27:37 +00:00
static inline void adjust_crop(const uint16_t **data, unsigned *height)
{
// Rather SNES specific.
2011-11-09 22:58:14 +00:00
unsigned pixel_pitch = lines_to_pitch(*height) >> 1;
2011-05-05 12:13:12 +00:00
if (g_settings.video.crop_overscan)
{
if (*height == 239)
2011-05-05 12:13:12 +00:00
{
2011-11-09 22:58:14 +00:00
*data += 7 * pixel_pitch; // Skip 7 top scanlines.
*height = 224;
2011-05-05 12:13:12 +00:00
}
else if (*height == 478)
2011-05-05 12:13:12 +00:00
{
2011-11-09 22:58:14 +00:00
*data += 15 * pixel_pitch; // Skip 15 top scanlines.
*height = 448;
2011-05-05 12:13:12 +00:00
}
}
}
// libsnes: 0.065
// Format received is 16-bit 0RRRRRGGGGGBBBBB
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
{
if (!g_extern.video_active)
return;
2011-11-22 16:27:02 +00:00
bool is_dupe = !data;
adjust_crop(&data, &height);
2011-05-05 12:13:12 +00:00
2011-08-11 03:25:31 +00:00
// Slightly messy code,
// but we really need to do processing before blocking on VSync for best possible scheduling.
#ifdef HAVE_FFMPEG
2011-11-22 16:27:02 +00:00
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record || is_dupe))
{
2011-12-24 12:46:12 +00:00
struct ffemu_video_data ffemu_data = {0};
ffemu_data.data = data;
ffemu_data.pitch = lines_to_pitch(height);
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.is_dupe = is_dupe;
ffemu_push_video(g_extern.rec, &ffemu_data);
}
#endif
2011-01-03 19:46:50 +00:00
2011-11-22 16:32:38 +00:00
if (is_dupe)
2011-11-22 16:27:02 +00:00
return;
const char *msg = msg_queue_pull(g_extern.msg_queue);
2011-05-11 15:52:16 +00:00
#ifdef HAVE_DYLIB
if (g_extern.filter.active)
2010-12-30 00:33:40 +00:00
{
2011-03-07 18:12:14 +00:00
unsigned owidth = width;
unsigned oheight = height;
g_extern.filter.psize(&owidth, &oheight);
g_extern.filter.prender(g_extern.filter.colormap, g_extern.filter.buffer,
g_extern.filter.pitch, data, lines_to_pitch(height), width, height);
2011-08-11 03:25:31 +00:00
#ifdef HAVE_FFMPEG
if (g_extern.recording && g_settings.video.post_filter_record)
{
2011-12-24 12:46:12 +00:00
struct ffemu_video_data ffemu_data = {0};
ffemu_data.data = g_extern.filter.buffer;
ffemu_data.pitch = g_extern.filter.pitch;
ffemu_data.width = owidth;
ffemu_data.height = oheight;
2011-08-11 03:25:31 +00:00
ffemu_push_video(g_extern.rec, &ffemu_data);
}
#endif
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
}
else if (!driver.video->frame(driver.video_data, data, width, height, lines_to_pitch(height), msg))
g_extern.video_active = false;
2010-05-29 14:59:57 +00:00
#else
if (!driver.video->frame(driver.video_data, data, width, height, lines_to_pitch(height), msg))
2010-12-29 18:18:37 +00:00
g_extern.video_active = false;
2010-12-29 18:43:17 +00:00
#endif
g_extern.frame_cache.data = data;
g_extern.frame_cache.width = width;
g_extern.frame_cache.height = height;
}
2012-01-21 11:12:44 +00:00
void ssnes_render_cached_frame(void)
{
2011-10-18 17:16:38 +00:00
#ifdef HAVE_FFMPEG
// Cannot allow FFmpeg recording when pushing duped frames.
bool recording = g_extern.recording;
g_extern.recording = false;
2011-10-18 17:16:38 +00:00
#endif
// Not 100% safe, since the library might have
// freed the memory, but no known implementations do this :D
// It would be really stupid at any rate ...
if (g_extern.frame_cache.data)
{
2011-10-20 21:33:15 +00:00
video_frame(g_extern.frame_cache.data,
g_extern.frame_cache.width,
g_extern.frame_cache.height);
}
2011-10-18 17:16:38 +00:00
#ifdef HAVE_FFMPEG
g_extern.recording = recording;
2011-10-18 17:16:38 +00:00
#endif
2010-05-26 19:27:37 +00:00
}
2012-02-14 00:16:37 +00:00
static void readjust_audio_input_rate(void)
{
int avail = driver.audio->write_avail(driver.audio_data);
//fprintf(stderr, "Audio buffer is %u%% full\n",
// (unsigned)(100 - (avail * 100) / g_extern.audio_data.driver_buffer_size));
int half_size = g_extern.audio_data.driver_buffer_size / 2;
int delta_mid = avail - half_size;
double direction = (double)delta_mid / half_size;
double adjust = 1.0 + g_settings.audio.rate_control_delta * direction;
g_extern.audio_data.src_ratio = g_extern.audio_data.orig_src_ratio * adjust;
//fprintf(stderr, "New rate: %lf, Orig rate: %lf\n",
// g_extern.audio_data.src_ratio, g_extern.audio_data.orig_src_ratio);
}
2011-11-26 14:54:58 +00:00
static bool audio_flush(const int16_t *data, size_t samples)
2010-05-26 19:27:37 +00:00
{
2011-06-14 19:35:31 +00:00
#ifdef HAVE_FFMPEG
if (g_extern.recording)
{
2011-12-24 12:46:12 +00:00
struct ffemu_audio_data ffemu_data = {0};
ffemu_data.data = data;
ffemu_data.frames = samples / 2;
2011-06-14 19:35:31 +00:00
ffemu_push_audio(g_extern.rec, &ffemu_data);
}
#endif
2011-10-17 19:30:58 +00:00
if (g_extern.is_paused)
return true;
if (!g_extern.audio_active)
return false;
2011-10-17 19:30:58 +00:00
const float *output_data = NULL;
unsigned output_frames = 0;
audio_convert_s16_to_float(g_extern.audio_data.data, data, samples);
2011-12-24 12:46:12 +00:00
ssnes_dsp_input_t dsp_input = {0};
dsp_input.samples = g_extern.audio_data.data;
dsp_input.frames = samples / 2;
2011-05-13 19:05:28 +00:00
2011-12-24 12:46:12 +00:00
ssnes_dsp_output_t dsp_output = {0};
dsp_output.should_resample = SSNES_TRUE;
2011-05-13 19:05:28 +00:00
if (g_extern.audio_data.dsp_plugin)
g_extern.audio_data.dsp_plugin->process(g_extern.audio_data.dsp_handle, &dsp_output, &dsp_input);
2011-05-13 19:05:28 +00:00
if (dsp_output.should_resample)
{
struct resampler_data src_data = {0};
2011-12-24 12:46:12 +00:00
src_data.data_in = dsp_output.samples ? dsp_output.samples : g_extern.audio_data.data;
src_data.data_out = g_extern.audio_data.outsamples;
src_data.input_frames = dsp_output.samples ? dsp_output.frames : (samples / 2);
2012-02-14 00:16:37 +00:00
if (g_extern.audio_data.rate_control)
readjust_audio_input_rate();
2011-12-24 12:46:12 +00:00
src_data.ratio = g_extern.audio_data.src_ratio;
2011-11-03 22:48:36 +00:00
resampler_process(g_extern.audio_data.source, &src_data);
2011-05-13 19:05:28 +00:00
output_data = g_extern.audio_data.outsamples;
2011-11-03 22:48:36 +00:00
output_frames = src_data.output_frames;
2011-05-13 19:05:28 +00:00
}
else
{
output_data = dsp_output.samples;
output_frames = dsp_output.frames;
}
2011-11-26 14:54:58 +00:00
union
{
float f[0x10000];
int16_t i[0x10000 * sizeof(float) / sizeof(int16_t)];
2011-12-25 18:31:05 +00:00
} static empty_buf; // Const here would require us to statically initialize it, bloating the binary.
2011-11-26 14:54:58 +00:00
2011-05-13 19:05:28 +00:00
if (g_extern.audio_data.use_float)
{
2011-11-26 14:54:58 +00:00
if (driver.audio->write(driver.audio_data,
g_extern.audio_data.mute ? empty_buf.f : output_data, output_frames * sizeof(float) * 2) < 0)
2010-08-16 17:16:03 +00:00
{
2011-05-13 19:05:28 +00:00
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
2011-10-15 11:16:32 +00:00
return false;
}
2011-05-13 19:05:28 +00:00
}
else
{
2011-11-26 14:54:58 +00:00
if (!g_extern.audio_data.mute)
{
audio_convert_float_to_s16(g_extern.audio_data.conv_outsamples,
output_data, output_frames * 2);
}
2010-05-26 20:42:58 +00:00
2011-11-26 14:54:58 +00:00
if (driver.audio->write(driver.audio_data,
g_extern.audio_data.mute ? empty_buf.i : g_extern.audio_data.conv_outsamples,
output_frames * sizeof(int16_t) * 2) < 0)
2011-05-13 19:05:28 +00:00
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
2011-10-15 11:16:32 +00:00
return false;
2011-05-13 19:05:28 +00:00
}
2010-05-26 19:27:37 +00:00
}
2011-05-13 19:05:28 +00:00
2011-10-15 11:16:32 +00:00
return true;
}
2011-10-15 12:33:41 +00:00
static void audio_sample_rewind(uint16_t left, uint16_t right)
{
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] = right;
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] = left;
}
2011-10-15 11:16:32 +00:00
static void audio_sample(uint16_t left, uint16_t right)
{
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr++] = left;
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr++] = right;
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
return;
2011-10-15 12:33:41 +00:00
g_extern.audio_active = audio_flush(g_extern.audio_data.conv_outsamples,
g_extern.audio_data.data_ptr) && g_extern.audio_active;
2011-10-15 12:33:41 +00:00
2011-05-13 19:05:28 +00:00
g_extern.audio_data.data_ptr = 0;
2010-05-26 19:27:37 +00:00
}
2010-05-28 00:45:18 +00:00
static void input_poll(void)
2010-05-26 19:27:37 +00:00
{
2010-05-28 00:45:18 +00:00
driver.input->poll(driver.input_data);
2010-05-26 19:27:37 +00:00
}
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
{
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie && g_extern.bsv.movie_playback)
2011-02-02 11:10:27 +00:00
{
int16_t ret;
2011-11-20 19:19:05 +00:00
if (bsv_movie_get_input(g_extern.bsv.movie, &ret))
2011-02-02 11:10:27 +00:00
return ret;
else
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie_end = true;
2011-02-02 11:10:27 +00:00
}
2011-06-11 18:02:17 +00:00
static const struct snes_keybind *binds[MAX_PLAYERS] = {
g_settings.input.binds[0],
g_settings.input.binds[1],
g_settings.input.binds[2],
g_settings.input.binds[3],
2012-02-04 00:54:19 +00:00
g_settings.input.binds[4],
g_settings.input.binds[5],
g_settings.input.binds[6],
g_settings.input.binds[7],
2011-06-11 18:02:17 +00:00
};
2011-10-16 13:45:04 +00:00
int16_t res = 0;
if (id < SSNES_FIRST_META_KEY)
2011-10-16 13:45:04 +00:00
res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie && !g_extern.bsv.movie_playback)
bsv_movie_set_input(g_extern.bsv.movie, res);
2011-02-02 11:10:27 +00:00
return res;
2010-05-26 19:27:37 +00:00
}
2011-01-07 16:59:53 +00:00
#ifdef _WIN32
2011-08-20 01:28:24 +00:00
#define SSNES_DEFAULT_CONF_PATH_STR "\n\t\tDefaults to ssnes.cfg in same directory as ssnes.exe."
#elif defined(__APPLE__)
2011-06-21 20:37:48 +00:00
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $HOME/.ssnes.cfg."
2011-01-07 16:59:53 +00:00
#else
2011-06-21 20:37:48 +00:00
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg,\n\t\tor $HOME/.ssnes.cfg, if $XDG_CONFIG_HOME is not defined."
2011-01-07 16:59:53 +00:00
#endif
#include "config.features.h"
#define _PSUPP(var, name, desc) printf("\t%s:\n\t\t%s: %s\n", name, desc, _##var##_supp ? "yes" : "no")
static void print_features(void)
{
puts("");
puts("Features:");
_PSUPP(sdl, "SDL", "SDL drivers");
2011-11-30 16:46:58 +00:00
_PSUPP(thread, "Threads", "Threading support");
2011-11-01 17:45:50 +00:00
_PSUPP(opengl, "OpenGL", "OpenGL driver");
2011-06-25 16:11:04 +00:00
_PSUPP(xvideo, "XVideo", "XVideo output");
_PSUPP(alsa, "ALSA", "audio driver");
_PSUPP(oss, "OSS", "audio driver");
_PSUPP(jack, "Jack", "audio driver");
_PSUPP(rsound, "RSound", "audio driver");
_PSUPP(roar, "RoarAudio", "audio driver");
_PSUPP(pulse, "PulseAudio", "audio driver");
2011-12-25 00:59:30 +00:00
_PSUPP(dsound, "DirectSound", "audio driver");
_PSUPP(xaudio, "XAudio2", "audio driver");
_PSUPP(al, "OpenAL", "audio driver");
2011-05-11 15:52:16 +00:00
_PSUPP(dylib, "External", "External filter and driver support");
_PSUPP(cg, "Cg", "Cg pixel shaders");
_PSUPP(xml, "XML", "bSNES XML pixel shaders");
_PSUPP(sdl_image, "SDL_image", "SDL_image image loading");
2011-03-23 22:48:13 +00:00
_PSUPP(fbo, "FBO", "OpenGL render-to-texture (multi-pass shaders)");
_PSUPP(dynamic, "Dynamic", "Dynamic run-time loading of libsnes library");
_PSUPP(ffmpeg, "FFmpeg", "On-the-fly recording of gameplay with libavcodec");
_PSUPP(x264rgb, "x264 RGB", "x264 lossless RGB recording for FFmpeg");
_PSUPP(configfile, "Config file", "Configuration file support");
_PSUPP(freetype, "FreeType", "TTF font rendering with FreeType");
2011-03-19 19:41:07 +00:00
_PSUPP(netplay, "Netplay", "Peer-to-peer netplay");
2011-06-06 18:21:26 +00:00
_PSUPP(python, "Python", "Script support in shaders");
}
#undef _PSUPP
2011-12-25 20:39:58 +00:00
static void print_compiler(FILE *file)
2011-12-25 20:16:48 +00:00
{
2011-12-25 20:39:58 +00:00
fprintf(file, "\nCompiler: ");
2011-12-25 20:16:48 +00:00
#if defined(_MSC_VER)
2011-12-25 20:39:58 +00:00
fprintf(file, "MSVC (%d) %u-bit\n", _MSC_VER, (unsigned)(CHAR_BIT * sizeof(size_t)));
2012-01-11 21:55:07 +00:00
#elif defined(__SNC__)
fprintf(file, "SNC (%d) %u-bit\n",
__SN_VER__, (unsigned)(CHAR_BIT * sizeof(size_t)));
2011-12-25 20:16:48 +00:00
#elif defined(_WIN32) && defined(__GNUC__)
2011-12-25 20:39:58 +00:00
fprintf(file, "MinGW (%d.%d.%d) %u-bit\n",
2011-12-25 20:16:48 +00:00
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, (unsigned)(CHAR_BIT * sizeof(size_t)));
#elif defined(__clang__)
2011-12-25 20:39:58 +00:00
fprintf(file, "Clang/LLVM (%s) %u-bit\n",
2011-12-25 20:16:48 +00:00
__VERSION__, (unsigned)(CHAR_BIT * sizeof(size_t)));
#elif defined(__GNUC__)
2011-12-25 20:39:58 +00:00
fprintf(file, "GCC (%d.%d.%d) %u-bit\n",
2011-12-25 20:16:48 +00:00
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, (unsigned)(CHAR_BIT * sizeof(size_t)));
#else
2011-12-25 20:39:58 +00:00
fprintf(file, "Unknown compiler %u-bit\n",
2011-12-25 20:16:48 +00:00
(unsigned)(CHAR_BIT * sizeof(size_t)));
#endif
2011-12-25 20:39:58 +00:00
fprintf(file, "Built: %s\n", __DATE__);
2011-12-25 20:16:48 +00:00
}
2010-10-01 19:39:15 +00:00
static void print_help(void)
{
2011-02-15 15:42:55 +00:00
puts("===================================================================");
2011-01-24 21:15:49 +00:00
puts("ssnes: Simple Super Nintendo Emulator (libsnes) -- v" PACKAGE_VERSION " --");
2011-12-25 20:39:58 +00:00
print_compiler(stdout);
2011-02-15 15:42:55 +00:00
puts("===================================================================");
2011-02-18 13:49:15 +00:00
puts("Usage: ssnes [rom file] [options...]");
2011-06-21 20:37:48 +00:00
puts("\t-h/--help: Show this help message.");
puts("\t--features: Prints available features compiled into SSNES.");
2011-06-21 20:37:48 +00:00
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin.");
puts("\t-f/--fullscreen: Start SSNES in fullscreen regardless of config settings.");
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
#ifdef HAVE_CONFIGFILE
2011-01-07 16:59:53 +00:00
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
2011-11-15 20:15:12 +00:00
#endif
#ifdef HAVE_DYNAMIC
puts("\t-L/--libsnes: Path to libsnes implementation. Overrides any config setting.");
#endif
puts("\t-g/--gameboy: Path to Gameboy ROM. Load SuperGameBoy as the regular rom.");
puts("\t-b/--bsx: Path to BSX rom. Load BSX BIOS as the regular rom.");
puts("\t-B/--bsxslot: Path to BSX slotted rom. Load BSX BIOS as the regular rom.");
puts("\t--sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom.");
puts("\t--sufamiB: Path to B slot of Sufami Turbo.");
2011-01-10 13:29:00 +00:00
puts("\t-m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2).");
2011-01-10 16:34:26 +00:00
puts("\t\tThis argument can be specified several times to connect more mice.");
puts("\t-N/--nodevice: Disconnects the controller device connected to the emulated SNES (1 or 2).");
puts("\t-p/--scope: Connect a virtual SuperScope into port 2 of the SNES.");
puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES.");
puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES.");
puts("\t-4/--multitap: Connect a multitap to port 2 of the SNES.");
2011-02-02 11:10:27 +00:00
puts("\t-P/--bsvplay: Playback a BSV movie file.");
2011-11-18 17:03:24 +00:00
puts("\t-R/--bsvrecord: Start recording a BSV movie file from the beginning.");
2011-11-20 19:19:05 +00:00
puts("\t-M/--sram-mode: Takes an argument telling how SRAM should be handled in the session.");
2011-11-18 17:03:24 +00:00
puts("\t\t{no,}load-{no,}save describes if SRAM should be loaded, and if SRAM should be saved.");
2011-11-20 19:19:05 +00:00
puts("\t\tDo note that noload-save implies that save files will be deleted and overwritten.");
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
puts("\t-H/--host: Host netplay as player 1.");
puts("\t-C/--connect: Connect to netplay as player 2.");
2011-02-18 13:49:15 +00:00
puts("\t--port: Port used to netplay. Default is 55435.");
2011-02-15 14:32:26 +00:00
puts("\t-F/--frames: Sync frames when using netplay.");
2012-01-11 18:22:18 +00:00
puts("\t--spectate: Netplay will become spectacting mode.");
puts("\t\tHost can live stream the game content to players that connect.");
puts("\t\tHowever, the client will not be able to play. Multiple clients can connect to the host.");
2012-01-21 17:12:42 +00:00
puts("\t--nick: Picks a nickname for use with netplay. Not mandatory.");
2011-03-19 19:41:07 +00:00
#endif
2011-01-07 16:59:53 +00:00
#ifdef HAVE_FFMPEG
2011-10-30 10:24:16 +00:00
puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended.");
puts("\t--size: Overrides output video size when recording with FFmpeg (format: WIDTHxHEIGHT).");
#endif
2011-06-21 20:37:48 +00:00
puts("\t-v/--verbose: Verbose logging.");
2011-04-23 12:47:50 +00:00
puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM.");
2011-08-17 22:24:57 +00:00
puts("\t--bps: Specifies path for BPS patch that will be applied to ROM.");
2011-06-24 11:29:30 +00:00
puts("\t-X/--xml: Specifies path to XML memory map.");
2011-04-23 12:47:50 +00:00
puts("\t-D/--detach: Detach SSNES from the running console. Not relevant for all platforms.\n");
2010-10-01 19:39:15 +00:00
}
static void set_basename(const char *path)
{
strlcpy(g_extern.system.fullpath, path, sizeof(g_extern.system.fullpath));
strlcpy(g_extern.basename, path, sizeof(g_extern.basename));
char *dst = strrchr(g_extern.basename, '.');
if (dst)
*dst = '\0';
}
2011-08-24 13:47:39 +00:00
static void set_paths(const char *path)
{
set_basename(path);
SSNES_LOG("Opening file: \"%s\"\n", path);
g_extern.rom_file = fopen(path, "rb");
if (g_extern.rom_file == NULL)
{
SSNES_ERR("Could not open file: \"%s\"\n", path);
ssnes_fail(1, "set_paths()");
2011-08-24 13:47:39 +00:00
}
if (!g_extern.has_set_save_path)
fill_pathname_noext(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
if (!g_extern.has_set_state_path)
fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
if (path_is_directory(g_extern.savefile_name_srm))
{
fill_pathname_dir(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
SSNES_LOG("Redirecting save file to \"%s\".\n", g_extern.savefile_name_srm);
}
if (path_is_directory(g_extern.savestate_name))
{
fill_pathname_dir(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
SSNES_LOG("Redirecting save state to \"%s\".\n", g_extern.savestate_name);
}
2011-10-28 15:10:58 +00:00
#ifdef HAVE_CONFIGFILE
2011-08-24 13:47:39 +00:00
if (*g_extern.config_path && path_is_directory(g_extern.config_path))
{
fill_pathname_dir(g_extern.config_path, g_extern.basename, ".cfg", sizeof(g_extern.config_path));
SSNES_LOG("Redirecting config file to \"%s\".\n", g_extern.config_path);
if (!path_file_exists(g_extern.config_path))
{
*g_extern.config_path = '\0';
SSNES_LOG("Did not find config file. Using system default.\n");
}
}
2011-10-28 15:10:58 +00:00
#endif
2011-08-24 13:47:39 +00:00
}
static void verify_stdin_paths(void)
{
if (strlen(g_extern.savefile_name_srm) == 0)
{
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
print_help();
ssnes_fail(1, "verify_stdin_paths()");
2011-08-24 13:47:39 +00:00
}
else if (strlen(g_extern.savestate_name) == 0)
{
SSNES_ERR("Need savestate path argument (--savestate) when reading rom from stdin.\n");
print_help();
ssnes_fail(1, "verify_stdin_paths()");
2011-08-24 13:47:39 +00:00
}
if (path_is_directory(g_extern.savefile_name_srm))
{
SSNES_ERR("Cannot specify directory for path argument (--save) when reading from stdin.\n");
print_help();
ssnes_fail(1, "verify_stdin_paths()");
2011-08-24 13:47:39 +00:00
}
else if (path_is_directory(g_extern.savestate_name))
{
SSNES_ERR("Cannot specify directory for path argument (--savestate) when reading from stdin.\n");
print_help();
ssnes_fail(1, "verify_stdin_paths()");
2011-08-24 13:47:39 +00:00
}
2011-10-28 15:10:58 +00:00
#ifdef HAVE_CONFIGFILE
2011-08-24 13:47:39 +00:00
else if (path_is_directory(g_extern.config_path))
{
SSNES_ERR("Cannot specify directory for config file (--config) when reading from stdin.\n");
print_help();
ssnes_fail(1, "verify_stdin_paths()");
2011-08-24 13:47:39 +00:00
}
2011-10-28 15:10:58 +00:00
#endif
2011-08-24 13:47:39 +00:00
}
2010-10-01 19:39:15 +00:00
static void parse_input(int argc, char *argv[])
2010-05-26 19:27:37 +00:00
{
2010-10-01 19:39:15 +00:00
if (argc < 2)
2010-05-26 19:27:37 +00:00
{
2010-10-01 19:39:15 +00:00
print_help();
ssnes_fail(1, "parse_input()");
2010-05-26 19:27:37 +00:00
}
2010-05-29 13:21:30 +00:00
// Make sure we can call parse_input several times ...
optind = 1;
2011-02-18 13:49:15 +00:00
int val = 0;
2012-01-05 20:43:55 +00:00
const struct option opts[] = {
2011-11-15 20:15:12 +00:00
#ifdef HAVE_DYNAMIC
{ "libsnes", 1, NULL, 'L' },
#endif
2010-10-01 19:39:15 +00:00
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
{ "fullscreen", 0, NULL, 'f' },
#ifdef HAVE_FFMPEG
{ "record", 1, NULL, 'r' },
2011-10-06 21:43:48 +00:00
{ "size", 1, &val, 's' },
#endif
2010-10-01 20:10:28 +00:00
{ "verbose", 0, NULL, 'v' },
{ "gameboy", 1, NULL, 'g' },
#ifdef HAVE_CONFIGFILE
2010-12-29 20:12:56 +00:00
{ "config", 0, NULL, 'c' },
#endif
2011-01-10 13:29:00 +00:00
{ "mouse", 1, NULL, 'm' },
2011-06-19 09:11:04 +00:00
{ "nodevice", 1, NULL, 'N' },
{ "scope", 0, NULL, 'p' },
{ "savestate", 1, NULL, 'S' },
{ "bsx", 1, NULL, 'b' },
{ "bsxslot", 1, NULL, 'B' },
{ "justifier", 0, NULL, 'j' },
{ "justifiers", 0, NULL, 'J' },
{ "multitap", 0, NULL, '4' },
{ "sufamiA", 1, NULL, 'Y' },
{ "sufamiB", 1, NULL, 'Z' },
2011-02-02 11:10:27 +00:00
{ "bsvplay", 1, NULL, 'P' },
2011-11-18 17:03:24 +00:00
{ "bsvrecord", 1, NULL, 'R' },
2011-11-20 19:19:05 +00:00
{ "sram-mode", 1, NULL, 'M' },
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
{ "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' },
2011-02-15 14:32:26 +00:00
{ "frames", 1, NULL, 'F' },
2011-02-18 13:49:15 +00:00
{ "port", 1, &val, 'p' },
2012-01-11 18:22:18 +00:00
{ "spectate", 0, &val, 'S' },
2012-01-21 17:12:42 +00:00
{ "nick", 1, &val, 'N' },
#endif
{ "ups", 1, NULL, 'U' },
2011-08-17 22:24:57 +00:00
{ "bps", 1, &val, 'B' },
2011-06-24 11:29:30 +00:00
{ "xml", 1, NULL, 'X' },
2011-04-23 12:47:50 +00:00
{ "detach", 0, NULL, 'D' },
{ "features", 0, &val, 'f' },
2010-10-01 19:39:15 +00:00
{ NULL, 0, NULL, 0 }
};
#ifdef HAVE_FFMPEG
#define FFMPEG_RECORD_ARG "r:"
#else
#define FFMPEG_RECORD_ARG
#endif
#ifdef HAVE_CONFIGFILE
#define CONFIG_FILE_ARG "c:"
#else
#define CONFIG_FILE_ARG
#endif
2011-11-15 20:15:12 +00:00
#ifdef HAVE_DYNAMIC
#define DYNAMIC_ARG "L:"
#else
#define DYNAMIC_ARG
#endif
#ifdef HAVE_NETPLAY
#define NETPLAY_ARG "HC:F:"
#else
#define NETPLAY_ARG
#endif
2012-01-05 20:43:55 +00:00
const char *optstring = "hs:fvS:m:p4jJg:b:B:Y:Z:P:R:M:U:DN:X:" NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
2011-10-15 10:56:48 +00:00
for (;;)
2010-05-29 13:21:30 +00:00
{
2011-02-18 13:49:15 +00:00
val = 0;
2011-11-30 10:48:53 +00:00
int c = getopt_long(argc, argv, optstring, opts, NULL);
2011-01-10 13:29:00 +00:00
int port;
2010-10-01 19:39:15 +00:00
if (c == -1)
break;
switch (c)
{
case 'h':
print_help();
exit(0);
case '4':
g_extern.has_multitap = true;
break;
case 'j':
g_extern.has_justifier = true;
break;
case 'J':
g_extern.has_justifiers = true;
break;
2010-10-01 19:39:15 +00:00
case 's':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm));
g_extern.has_set_save_path = true;
break;
case 'f':
g_extern.force_fullscreen = true;
break;
case 'g':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path));
g_extern.game_type = SSNES_CART_SGB;
break;
case 'b':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path));
g_extern.game_type = SSNES_CART_BSX;
break;
case 'B':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path));
g_extern.game_type = SSNES_CART_BSX_SLOTTED;
break;
case 'Y':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]));
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'Z':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]));
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'S':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name));
g_extern.has_set_state_path = true;
2010-10-01 19:39:15 +00:00
break;
2010-10-01 20:10:28 +00:00
case 'v':
2010-12-29 18:43:17 +00:00
g_extern.verbose = true;
2010-10-01 20:10:28 +00:00
break;
2011-01-10 06:58:11 +00:00
case 'm':
2011-01-10 13:29:00 +00:00
port = strtol(optarg, NULL, 0);
if (port < 1 || port > 2)
{
SSNES_ERR("Connect mouse to port 1 or 2.\n");
print_help();
ssnes_fail(1, "parse_input()");
2011-01-10 13:29:00 +00:00
}
g_extern.has_mouse[port - 1] = true;
2011-01-10 06:58:11 +00:00
break;
2011-06-19 09:11:04 +00:00
case 'N':
port = strtol(optarg, NULL, 0);
if (port < 1 || port > 2)
{
SSNES_ERR("Disconnected device from port 1 or 2.\n");
print_help();
ssnes_fail(1, "parse_input()");
2011-06-19 09:11:04 +00:00
}
g_extern.disconnect_device[port - 1] = true;
break;
2011-01-10 06:58:11 +00:00
case 'p':
g_extern.has_scope[1] = true;
2011-01-10 06:58:11 +00:00
break;
#ifdef HAVE_CONFIGFILE
2010-12-29 20:12:56 +00:00
case 'c':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.config_path, optarg, sizeof(g_extern.config_path));
2010-12-29 20:12:56 +00:00
break;
#endif
2010-12-29 20:12:56 +00:00
#ifdef HAVE_FFMPEG
case 'r':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.record_path, optarg, sizeof(g_extern.record_path));
g_extern.recording = true;
break;
#endif
2011-11-15 20:15:12 +00:00
#ifdef HAVE_DYNAMIC
case 'L':
strlcpy(g_settings.libsnes, optarg, sizeof(g_settings.libsnes));
break;
#endif
2011-02-02 11:10:27 +00:00
case 'P':
2011-11-20 19:19:05 +00:00
case 'R':
strlcpy(g_extern.bsv.movie_start_path, optarg,
sizeof(g_extern.bsv.movie_start_path));
g_extern.bsv.movie_start_playback = c == 'P';
g_extern.bsv.movie_start_recording = c == 'R';
2011-11-18 17:03:24 +00:00
break;
2011-11-20 19:19:05 +00:00
case 'M':
2011-11-18 17:03:24 +00:00
if (strcmp(optarg, "noload-nosave") == 0)
{
2011-11-20 19:19:05 +00:00
g_extern.sram_load_disable = true;
g_extern.sram_save_disable = true;
2011-11-18 17:03:24 +00:00
}
else if (strcmp(optarg, "noload-save") == 0)
2011-11-20 19:19:05 +00:00
g_extern.sram_load_disable = true;
2011-11-18 17:03:24 +00:00
else if (strcmp(optarg, "load-nosave") == 0)
2011-11-20 19:19:05 +00:00
g_extern.sram_save_disable = true;
else if (strcmp(optarg, "load-save") != 0)
2011-11-18 17:03:24 +00:00
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Invalid argument in --sram-mode.\n");
2011-11-18 17:03:24 +00:00
print_help();
ssnes_fail(1, "parse_input()");
2011-11-18 17:03:24 +00:00
}
2011-02-02 11:10:27 +00:00
break;
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
case 'H':
g_extern.netplay_enable = true;
break;
case 'C':
g_extern.netplay_enable = true;
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.netplay_server, optarg, sizeof(g_extern.netplay_server));
2011-02-13 15:40:24 +00:00
break;
2011-02-15 14:32:26 +00:00
case 'F':
g_extern.netplay_sync_frames = strtol(optarg, NULL, 0);
2011-11-20 19:19:05 +00:00
if (g_extern.netplay_sync_frames > 16)
g_extern.netplay_sync_frames = 16;
2011-02-15 14:32:26 +00:00
break;
#endif
2011-02-15 14:32:26 +00:00
case 'U':
2011-04-03 20:29:36 +00:00
strlcpy(g_extern.ups_name, optarg, sizeof(g_extern.ups_name));
2011-08-17 22:24:57 +00:00
g_extern.ups_pref = true;
break;
2011-04-23 12:47:50 +00:00
2011-06-24 11:29:30 +00:00
case 'X':
strlcpy(g_extern.xml_name, optarg, sizeof(g_extern.xml_name));
break;
2011-04-23 12:47:50 +00:00
case 'D':
#if defined(_WIN32) && !defined(_XBOX)
2011-04-23 12:47:50 +00:00
FreeConsole();
#endif
break;
2011-02-18 13:49:15 +00:00
case 0:
switch (val)
{
#ifdef HAVE_NETPLAY
2011-02-18 13:49:15 +00:00
case 'p':
2011-08-24 13:47:39 +00:00
g_extern.netplay_port = strtoul(optarg, NULL, 0);
2011-02-18 13:49:15 +00:00
break;
2012-01-11 18:22:18 +00:00
case 'S':
g_extern.netplay_is_spectate = true;
break;
2012-01-21 17:12:42 +00:00
case 'N':
strlcpy(g_extern.netplay_nick, optarg, sizeof(g_extern.netplay_nick));
break;
#endif
2011-10-06 21:43:48 +00:00
2011-08-17 22:24:57 +00:00
case 'B':
strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name));
g_extern.bps_pref = true;
break;
2011-10-06 21:43:48 +00:00
#ifdef HAVE_FFMPEG
case 's':
{
errno = 0;
char *ptr;
g_extern.record_width = strtoul(optarg, &ptr, 0);
if ((*ptr != 'x') || errno)
2011-10-06 21:43:48 +00:00
{
SSNES_ERR("Wrong format for --size.\n");
print_help();
ssnes_fail(1, "parse_input()");
2011-10-06 21:43:48 +00:00
}
ptr++;
g_extern.record_height = strtoul(ptr, &ptr, 0);
if ((*ptr != '\0') || errno)
{
SSNES_ERR("Wrong format for --size.\n");
print_help();
ssnes_fail(1, "parse_input()");
2011-10-06 21:43:48 +00:00
}
break;
}
#endif
case 'f':
print_features();
exit(0);
2011-10-06 21:43:48 +00:00
2011-02-18 13:49:15 +00:00
default:
break;
}
break;
2010-10-01 19:39:15 +00:00
case '?':
print_help();
ssnes_fail(1, "parse_input()");
2010-10-01 19:39:15 +00:00
default:
2010-10-01 20:10:28 +00:00
SSNES_ERR("Error parsing arguments.\n");
ssnes_fail(1, "parse_input()");
2010-10-01 19:39:15 +00:00
}
2010-05-29 13:21:30 +00:00
}
2010-10-01 19:39:15 +00:00
if (optind < argc)
2011-08-24 13:47:39 +00:00
set_paths(argv[optind]);
else
verify_stdin_paths();
2010-10-01 19:39:15 +00:00
}
2010-08-15 08:02:04 +00:00
2011-01-10 13:29:00 +00:00
// TODO: Add rest of the controllers.
static void init_controllers(void)
{
if (g_extern.has_justifier)
2011-01-10 13:29:00 +00:00
{
SSNES_LOG("Connecting Justifier to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIER);
}
else if (g_extern.has_justifiers)
{
SSNES_LOG("Connecting Justifiers to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIERS);
}
else if (g_extern.has_multitap)
{
2011-06-19 09:11:04 +00:00
SSNES_LOG("Connecting Multitap to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP);
}
else
{
2011-06-19 09:11:04 +00:00
for (unsigned i = 0; i < 2; i++)
2011-01-10 13:29:00 +00:00
{
2011-06-19 09:11:04 +00:00
if (g_extern.disconnect_device[i])
{
SSNES_LOG("Disconnecting device from port %u.\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_NONE);
}
else if (g_extern.has_mouse[i])
{
2011-06-19 09:11:04 +00:00
SSNES_LOG("Connecting mouse to port %u.\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE);
}
else if (g_extern.has_scope[i])
{
2011-06-19 09:11:04 +00:00
SSNES_LOG("Connecting scope to port %u.\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_SUPER_SCOPE);
}
2011-01-10 13:29:00 +00:00
}
}
}
static inline void load_save_files(void)
2011-01-12 18:09:24 +00:00
{
switch (g_extern.game_type)
2011-01-12 18:09:24 +00:00
{
case SSNES_CART_NORMAL:
2011-01-18 14:34:37 +00:00
load_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_ram_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
break;
2011-02-11 12:44:31 +00:00
case SSNES_CART_SGB:
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_GAME_BOY_RAM);
save_ram_file(g_extern.savefile_name_rtc, SNES_MEMORY_GAME_BOY_RTC);
break;
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
2011-01-18 14:34:37 +00:00
load_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
load_ram_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
break;
case SSNES_CART_SUFAMI:
2011-01-18 14:34:37 +00:00
load_ram_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
load_ram_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
break;
default:
break;
2011-01-12 18:09:24 +00:00
}
}
static inline void save_files(void)
2011-01-12 18:09:24 +00:00
{
switch (g_extern.game_type)
2011-01-12 18:09:24 +00:00
{
case SSNES_CART_NORMAL:
SSNES_LOG("Saving regular SRAM.\n");
2011-01-18 14:34:37 +00:00
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_ram_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
break;
2011-01-12 18:09:24 +00:00
2011-02-11 12:44:31 +00:00
case SSNES_CART_SGB:
SSNES_LOG("Saving Gameboy SRAM.\n");
2011-02-11 12:44:31 +00:00
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_GAME_BOY_RAM);
save_ram_file(g_extern.savefile_name_rtc, SNES_MEMORY_GAME_BOY_RTC);
break;
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
SSNES_LOG("Saving BSX (P)RAM.\n");
2011-01-18 14:34:37 +00:00
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
save_ram_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
break;
case SSNES_CART_SUFAMI:
SSNES_LOG("Saving Sufami turbo A/B RAM.\n");
2011-01-18 14:34:37 +00:00
save_ram_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
save_ram_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
break;
2011-01-12 18:09:24 +00:00
default:
break;
}
}
#ifdef HAVE_FFMPEG
static void init_recording(void)
{
2011-11-05 12:34:41 +00:00
if (!g_extern.recording)
return;
2011-08-11 03:25:31 +00:00
// Canonical values.
double fps = psnes_get_region() == SNES_REGION_NTSC ? 60.00 : 50.00;
double samplerate = 32000.0;
if (g_extern.system.timing_set)
{
fps = g_extern.system.timing.fps;
samplerate = g_extern.system.timing.sample_rate;
2011-12-24 12:46:12 +00:00
SSNES_LOG("Custom timing given: FPS: %.4f, Sample rate: %.4f\n", (float)fps, (float)samplerate);
}
2011-12-24 12:46:12 +00:00
struct ffemu_params params = {0};
params.out_width = g_extern.system.geom.base_width;
params.out_height = g_extern.system.geom.base_height;
params.fb_width = g_extern.system.geom.max_width;
params.fb_height = g_extern.system.geom.max_height;
params.channels = 2;
params.filename = g_extern.record_path;
params.fps = fps;
params.samplerate = samplerate;
params.rgb32 = false;
2011-08-11 03:25:31 +00:00
2011-11-05 12:34:41 +00:00
if (g_extern.record_width || g_extern.record_height)
{
params.out_width = g_extern.record_width;
params.out_height = g_extern.record_height;
}
else if (g_settings.video.hires_record)
{
params.out_width *= 2;
params.out_height *= 2;
}
2011-11-05 12:34:41 +00:00
if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f))
params.aspect_ratio = g_settings.video.aspect_ratio;
else
params.aspect_ratio = (float)params.out_width / params.out_height;
2011-08-11 03:25:31 +00:00
2011-11-05 12:34:41 +00:00
if (g_settings.video.post_filter_record && g_extern.filter.active)
{
g_extern.filter.psize(&params.out_width, &params.out_height);
params.rgb32 = true;
unsigned max_width = params.fb_width;
unsigned max_height = params.fb_height;
g_extern.filter.psize(&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
SSNES_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u 32-bit: %s)\n", g_extern.record_path, params.out_width, params.out_height, params.fb_width, params.fb_height, params.rgb32 ? "yes" : "no");
g_extern.rec = ffemu_new(&params);
if (!g_extern.rec)
{
SSNES_ERR("Failed to start FFmpeg recording.\n");
g_extern.recording = false;
}
}
static void deinit_recording(void)
{
if (g_extern.recording)
{
ffemu_finalize(g_extern.rec);
ffemu_free(g_extern.rec);
}
}
#endif
static void init_msg_queue(void)
{
2011-12-25 11:47:47 +00:00
ssnes_assert(g_extern.msg_queue = msg_queue_new(8));
}
static void deinit_msg_queue(void)
{
if (g_extern.msg_queue)
msg_queue_free(g_extern.msg_queue);
}
2011-04-17 11:30:59 +00:00
#ifdef HAVE_XML
static void init_cheats(void)
{
if (*g_settings.cheat_database)
g_extern.cheat = cheat_manager_new(g_settings.cheat_database);
}
static void deinit_cheats(void)
{
if (g_extern.cheat)
cheat_manager_free(g_extern.cheat);
}
#endif
2011-01-31 15:48:42 +00:00
static void init_rewind(void)
{
if (!g_settings.rewind_enable)
return;
g_extern.state_size = psnes_serialize_size();
// Make sure we allocate at least 4-byte multiple.
size_t aligned_state_size = (g_extern.state_size + 3) & ~3;
g_extern.state_buf = calloc(1, aligned_state_size);
if (!g_extern.state_buf)
2011-01-31 17:06:57 +00:00
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to allocate memory for rewind buffer.\n");
return;
2011-01-31 17:06:57 +00:00
}
if (!psnes_serialize((uint8_t*)g_extern.state_buf, g_extern.state_size))
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to perform initial serialization for rewind.\n");
free(g_extern.state_buf);
g_extern.state_buf = NULL;
return;
}
SSNES_LOG("Initing rewind buffer with size: %u MB\n", (unsigned)(g_settings.rewind_buffer_size / 1000000));
g_extern.state_manager = state_manager_new(aligned_state_size, g_settings.rewind_buffer_size, g_extern.state_buf);
if (!g_extern.state_manager)
2012-02-11 20:11:36 +00:00
SSNES_WARN("Failed to init rewind buffer. Rewinding will be disabled.\n");
2011-01-31 15:48:42 +00:00
}
static void deinit_rewind(void)
{
if (g_extern.state_manager)
state_manager_free(g_extern.state_manager);
if (g_extern.state_buf)
free(g_extern.state_buf);
}
2011-02-02 11:10:27 +00:00
static void init_movie(void)
{
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie_start_playback)
2011-02-02 11:45:56 +00:00
{
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie = bsv_movie_init(g_extern.bsv.movie_start_path, SSNES_MOVIE_PLAYBACK);
if (!g_extern.bsv.movie)
2011-02-02 11:45:56 +00:00
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to load movie file: \"%s\".\n", g_extern.bsv.movie_start_path);
ssnes_fail(1, "init_movie()");
2011-02-02 11:45:56 +00:00
}
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie_playback = true;
msg_queue_push(g_extern.msg_queue, "Starting movie playback.", 2, 180);
2012-02-11 20:11:36 +00:00
SSNES_LOG("Starting movie playback.\n");
g_settings.rewind_granularity = 1;
2011-02-02 11:45:56 +00:00
}
2011-11-20 19:19:05 +00:00
else if (g_extern.bsv.movie_start_recording)
2011-11-18 17:03:24 +00:00
{
2012-01-02 12:32:25 +00:00
char msg[PATH_MAX];
snprintf(msg, sizeof(msg), "Starting movie record to \"%s\".",
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie_start_path);
2011-11-19 00:33:21 +00:00
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie = bsv_movie_init(g_extern.bsv.movie_start_path, SSNES_MOVIE_RECORD);
2011-11-18 17:03:24 +00:00
msg_queue_clear(g_extern.msg_queue);
2011-11-20 19:19:05 +00:00
msg_queue_push(g_extern.msg_queue,
g_extern.bsv.movie ? msg : "Failed to start movie record.", 1, 180);
2011-11-18 17:03:24 +00:00
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Starting movie record to \"%s\".\n", g_extern.bsv.movie_start_path);
g_settings.rewind_granularity = 1;
}
2011-11-18 17:03:24 +00:00
else
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to start movie record.\n");
2011-11-18 17:03:24 +00:00
}
2011-02-02 11:10:27 +00:00
}
static void deinit_movie(void)
{
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie)
bsv_movie_free(g_extern.bsv.movie);
2011-02-02 11:10:27 +00:00
}
2011-02-18 13:49:15 +00:00
#define SSNES_DEFAULT_PORT 55435
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
static void init_netplay(void)
{
2011-11-03 20:05:12 +00:00
if (!g_extern.netplay_enable)
return;
2011-12-24 12:46:12 +00:00
struct snes_callbacks cbs = {0};
cbs.frame_cb = video_frame;
cbs.sample_cb = audio_sample;
cbs.state_cb = input_state;
2011-11-03 20:05:12 +00:00
if (*g_extern.netplay_server)
2011-02-13 15:40:24 +00:00
{
2011-11-03 20:05:12 +00:00
SSNES_LOG("Connecting to netplay host...\n");
g_extern.netplay_is_client = true;
}
else
SSNES_LOG("Waiting for client...\n");
2011-02-13 16:45:14 +00:00
2011-11-03 20:05:12 +00:00
g_extern.netplay = netplay_new(g_extern.netplay_is_client ? g_extern.netplay_server : NULL,
g_extern.netplay_port ? g_extern.netplay_port : SSNES_DEFAULT_PORT,
2012-01-21 17:12:42 +00:00
g_extern.netplay_sync_frames, &cbs, g_extern.netplay_is_spectate,
g_extern.netplay_nick);
2011-11-03 20:05:12 +00:00
if (!g_extern.netplay)
{
g_extern.netplay_is_client = false;
SSNES_WARN("Failed to init netplay ...\n");
2011-02-13 16:45:14 +00:00
2011-11-03 20:05:12 +00:00
if (g_extern.msg_queue)
2011-02-13 16:45:14 +00:00
{
2011-11-03 20:05:12 +00:00
msg_queue_push(g_extern.msg_queue,
"Failed to init netplay ...",
0, 180);
2011-02-13 16:45:14 +00:00
}
2011-02-13 15:40:24 +00:00
}
}
static void deinit_netplay(void)
{
if (g_extern.netplay)
netplay_free(g_extern.netplay);
}
2011-03-19 19:41:07 +00:00
#endif
2011-02-13 15:40:24 +00:00
2012-01-11 18:22:18 +00:00
static void init_libsnes_cbs(void)
{
#ifdef HAVE_NETPLAY
if (g_extern.netplay)
{
psnes_set_video_refresh(g_extern.netplay_is_spectate ?
video_frame : video_frame_net);
psnes_set_audio_sample(g_extern.netplay_is_spectate ?
audio_sample : audio_sample_net);
psnes_set_input_state(g_extern.netplay_is_spectate ?
(g_extern.netplay_is_client ? input_state_spectate_client : input_state_spectate)
: input_state_net);
}
else
{
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_state(input_state);
}
#else
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_state(input_state);
#endif
psnes_set_input_poll(input_poll);
}
2011-02-10 20:16:59 +00:00
static void init_autosave(void)
{
2011-11-30 16:46:58 +00:00
#ifdef HAVE_THREADS
2011-02-11 12:44:31 +00:00
int ram_types[2] = {-1, -1};
const char *ram_paths[2] = {NULL, NULL};
switch (g_extern.game_type)
{
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
ram_types[0] = SNES_MEMORY_BSX_RAM;
ram_types[1] = SNES_MEMORY_BSX_PRAM;
ram_paths[0] = g_extern.savefile_name_srm;
ram_paths[1] = g_extern.savefile_name_psrm;
break;
case SSNES_CART_SUFAMI:
ram_types[0] = SNES_MEMORY_SUFAMI_TURBO_A_RAM;
ram_types[1] = SNES_MEMORY_SUFAMI_TURBO_B_RAM;
ram_paths[0] = g_extern.savefile_name_asrm;
ram_paths[1] = g_extern.savefile_name_bsrm;
break;
case SSNES_CART_SGB:
ram_types[0] = SNES_MEMORY_GAME_BOY_RAM;
ram_types[1] = SNES_MEMORY_GAME_BOY_RTC;
ram_paths[0] = g_extern.savefile_name_srm;
ram_paths[1] = g_extern.savefile_name_rtc;
break;
default:
ram_types[0] = SNES_MEMORY_CARTRIDGE_RAM;
ram_types[1] = SNES_MEMORY_CARTRIDGE_RTC;
ram_paths[0] = g_extern.savefile_name_srm;
ram_paths[1] = g_extern.savefile_name_rtc;
}
2011-02-10 20:16:59 +00:00
if (g_settings.autosave_interval > 0)
{
2011-02-11 12:44:31 +00:00
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (ram_paths[i] && strlen(ram_paths[i]) > 0 && psnes_get_memory_size(ram_types[i]) > 0)
{
g_extern.autosave[i] = autosave_new(ram_paths[i],
psnes_get_memory_data(ram_types[i]),
psnes_get_memory_size(ram_types[i]),
g_settings.autosave_interval);
if (!g_extern.autosave[i])
SSNES_WARN("Could not initialize autosave.\n");
}
}
2011-02-10 20:16:59 +00:00
}
2011-11-30 16:46:58 +00:00
#endif
2011-02-10 20:16:59 +00:00
}
static void deinit_autosave(void)
{
2011-11-30 16:46:58 +00:00
#ifdef HAVE_THREADS
2011-02-11 12:44:31 +00:00
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (g_extern.autosave[i])
autosave_free(g_extern.autosave[i]);
}
2011-11-30 16:46:58 +00:00
#endif
2011-02-11 12:44:31 +00:00
}
2011-09-27 13:31:25 +00:00
static void set_savestate_auto_index(void)
{
if (!g_settings.savestate_auto_index)
return;
2012-01-02 12:32:25 +00:00
char state_path[PATH_MAX];
2011-09-27 13:31:25 +00:00
strlcpy(state_path, g_extern.savestate_name, sizeof(state_path));
char *split = strrchr(state_path, '/');
if (!split)
split = strrchr(state_path, '\\');
const char *base = state_path;
const char *dir = state_path;
if (split)
{
*split = '\0';
base = split + 1;
}
unsigned max_index = 0;
char **dir_list = dir_list_new(dir, NULL);
if (!dir_list)
return;
unsigned index = 0;
const char *dir_elem;
while ((dir_elem = dir_list[index++]))
{
if (!strstr(dir_elem, base))
continue;
const char *end = dir_elem + strlen(dir_elem);
while ((end != dir_elem) && isdigit(end[-1])) end--;
unsigned index = strtoul(end, NULL, 0);
if (index > max_index)
max_index = index;
}
dir_list_free(dir_list);
g_extern.state_slot = max_index;
SSNES_LOG("Found last state slot: #%u\n", g_extern.state_slot);
}
static void fill_pathnames(void)
{
switch (g_extern.game_type)
{
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
// BSX PSRM
if (!g_extern.has_set_save_path)
2012-01-06 19:32:30 +00:00
{
fill_pathname(g_extern.savefile_name_srm,
g_extern.bsx_rom_path, ".srm", sizeof(g_extern.savefile_name_srm));
}
fill_pathname(g_extern.savefile_name_psrm,
g_extern.savefile_name_srm, ".psrm", sizeof(g_extern.savefile_name_psrm));
if (!g_extern.has_set_state_path)
2012-01-06 19:32:30 +00:00
{
fill_pathname(g_extern.savestate_name,
g_extern.bsx_rom_path, ".state", sizeof(g_extern.savestate_name));
}
break;
case SSNES_CART_SUFAMI:
2012-01-06 19:32:30 +00:00
if (g_extern.has_set_save_path && *g_extern.sufami_rom_path[0] && *g_extern.sufami_rom_path[1])
SSNES_WARN("Sufami Turbo SRAM paths will be inferred from their respective paths to avoid conflicts.\n");
// SUFAMI ARAM
2012-01-06 19:32:30 +00:00
fill_pathname(g_extern.savefile_name_asrm,
g_extern.sufami_rom_path[0], ".srm", sizeof(g_extern.savefile_name_asrm));
// SUFAMI BRAM
2012-01-06 19:32:30 +00:00
fill_pathname(g_extern.savefile_name_bsrm,
g_extern.sufami_rom_path[1], ".srm", sizeof(g_extern.savefile_name_bsrm));
if (!g_extern.has_set_state_path)
{
fill_pathname(g_extern.savestate_name,
*g_extern.sufami_rom_path[0] ?
g_extern.sufami_rom_path[0] : g_extern.sufami_rom_path[1],
".state", sizeof(g_extern.savestate_name));
}
break;
case SSNES_CART_SGB:
if (!g_extern.has_set_save_path)
2012-01-06 19:32:30 +00:00
{
fill_pathname(g_extern.savefile_name_srm,
g_extern.gb_rom_path, ".srm", sizeof(g_extern.savefile_name_srm));
}
if (!g_extern.has_set_state_path)
2012-01-06 19:32:30 +00:00
{
fill_pathname(g_extern.savestate_name,
g_extern.gb_rom_path, ".state", sizeof(g_extern.savestate_name));
}
fill_pathname(g_extern.savefile_name_rtc,
g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc));
break;
default:
// Infer .rtc save path from save ram path.
2012-01-06 19:32:30 +00:00
fill_pathname(g_extern.savefile_name_rtc,
g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc));
}
2011-11-20 19:19:05 +00:00
fill_pathname(g_extern.bsv.movie_path, g_extern.savefile_name_srm, "", sizeof(g_extern.bsv.movie_path));
2011-08-17 22:05:56 +00:00
if (*g_extern.basename)
{
if (!(*g_extern.ups_name))
fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
if (!(*g_extern.bps_name))
fill_pathname_noext(g_extern.bps_name, g_extern.basename, ".bps", sizeof(g_extern.bps_name));
2011-06-24 11:29:30 +00:00
2011-08-17 22:05:56 +00:00
if (!(*g_extern.xml_name))
fill_pathname_noext(g_extern.xml_name, g_extern.basename, ".xml", sizeof(g_extern.xml_name));
2011-10-24 19:34:30 +00:00
if (!(*g_settings.screenshot_directory))
{
strlcpy(g_settings.screenshot_directory, g_extern.basename, sizeof(g_settings.screenshot_directory));
char *dir = strrchr(g_settings.screenshot_directory, '/');
if (!dir)
dir = strrchr(g_settings.screenshot_directory, '\\');
if (dir)
*dir = '\0';
}
2011-08-17 22:05:56 +00:00
}
}
void ssnes_load_state(void)
{
char load_path[PATH_MAX];
if (g_extern.state_slot > 0)
snprintf(load_path, sizeof(load_path), "%s%u", g_extern.savestate_name, g_extern.state_slot);
else
snprintf(load_path, sizeof(load_path), "%s", g_extern.savestate_name);
char msg[512];
if (load_state(load_path))
snprintf(msg, sizeof(msg), "Loaded state from slot #%u.", g_extern.state_slot);
else
snprintf(msg, sizeof(msg), "Failed to load state from \"%s\".", load_path);
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, msg, 2, 180);
}
void ssnes_save_state(void)
{
if (g_settings.savestate_auto_index)
g_extern.state_slot++;
char save_path[PATH_MAX];
if (g_extern.state_slot > 0)
snprintf(save_path, sizeof(save_path), "%s%u", g_extern.savestate_name, g_extern.state_slot);
else
snprintf(save_path, sizeof(save_path), "%s", g_extern.savestate_name);
char msg[512];
if (save_state(save_path))
snprintf(msg, sizeof(msg), "Saved state to slot #%u.", g_extern.state_slot);
else
snprintf(msg, sizeof(msg), "Failed to save state to \"%s\".", save_path);
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, msg, 2, 180);
}
2011-01-23 22:09:54 +00:00
// Save or load state here.
static void check_savestates(void)
{
static bool old_should_savestate = false;
bool should_savestate = driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY);
if (should_savestate && !old_should_savestate)
ssnes_save_state();
2011-01-23 22:09:54 +00:00
old_should_savestate = should_savestate;
static bool old_should_loadstate = false;
bool should_loadstate = driver.input->key_pressed(driver.input_data, SSNES_LOAD_STATE_KEY);
if (!should_savestate && should_loadstate && !old_should_loadstate)
ssnes_load_state();
2011-01-23 22:09:54 +00:00
old_should_loadstate = should_loadstate;
}
static bool check_fullscreen(void)
2011-01-23 22:09:54 +00:00
{
// If we go fullscreen we drop all drivers and reinit to be safe.
static bool was_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY);
bool toggle = pressed && !was_pressed;
if (toggle)
2011-01-23 22:09:54 +00:00
{
g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
// Poll input to avoid possibly stale data to corrupt things.
if (driver.input)
driver.input->poll(driver.input_data);
2011-01-23 22:09:54 +00:00
}
was_pressed = pressed;
return toggle;
2011-01-23 22:09:54 +00:00
}
void ssnes_state_slot_increase(void)
{
g_extern.state_slot++;
if (g_extern.msg_queue)
msg_queue_clear(g_extern.msg_queue);
char msg[256];
snprintf(msg, sizeof(msg), "Save state/movie slot: %u", g_extern.state_slot);
if (g_extern.msg_queue)
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
void ssnes_state_slot_decrease(void)
{
if (g_extern.state_slot > 0)
g_extern.state_slot--;
if (g_extern.msg_queue)
msg_queue_clear(g_extern.msg_queue);
char msg[256];
snprintf(msg, sizeof(msg), "Save state/movie slot: %u", g_extern.state_slot);
if (g_extern.msg_queue)
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
2011-01-23 22:09:54 +00:00
static void check_stateslots(void)
{
// Save state slots
static bool old_should_slot_increase = false;
bool should_slot_increase = driver.input->key_pressed(driver.input_data, SSNES_STATE_SLOT_PLUS);
if (should_slot_increase && !old_should_slot_increase)
ssnes_state_slot_increase();
2011-01-23 22:09:54 +00:00
old_should_slot_increase = should_slot_increase;
static bool old_should_slot_decrease = false;
bool should_slot_decrease = driver.input->key_pressed(driver.input_data, SSNES_STATE_SLOT_MINUS);
if (should_slot_decrease && !old_should_slot_decrease)
ssnes_state_slot_decrease();
2011-01-23 22:09:54 +00:00
old_should_slot_decrease = should_slot_decrease;
}
static void check_input_rate(void)
{
bool display = false;
if (driver.input->key_pressed(driver.input_data, SSNES_AUDIO_INPUT_RATE_PLUS))
{
g_settings.audio.in_rate += g_settings.audio.rate_step;
display = true;
}
else if (driver.input->key_pressed(driver.input_data, SSNES_AUDIO_INPUT_RATE_MINUS))
{
g_settings.audio.in_rate -= g_settings.audio.rate_step;
display = true;
}
if (display)
{
char msg[256];
snprintf(msg, sizeof(msg), "Audio input rate: %.2f Hz", g_settings.audio.in_rate);
msg_queue_clear(g_extern.msg_queue);
2012-01-25 22:03:02 +00:00
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
2011-10-15 14:16:13 +00:00
g_extern.audio_data.src_ratio =
(double)g_settings.audio.out_rate / g_settings.audio.in_rate;
}
}
2011-10-15 12:33:41 +00:00
static inline void flush_rewind_audio(void)
{
if (g_extern.frame_is_reverse) // We just rewound. Flush rewind audio buffer.
2011-10-15 12:33:41 +00:00
{
g_extern.audio_active = audio_flush(g_extern.audio_data.rewind_buf + g_extern.audio_data.rewind_ptr,
g_extern.audio_data.rewind_size - g_extern.audio_data.rewind_ptr) && g_extern.audio_active;
2011-10-15 12:33:41 +00:00
}
}
static inline void setup_rewind_audio(void)
{
// Push audio ready to be played.
g_extern.audio_data.rewind_ptr = g_extern.audio_data.rewind_size;
for (unsigned i = 0; i < g_extern.audio_data.data_ptr; i += 2)
{
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] =
g_extern.audio_data.conv_outsamples[i + 1];
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] =
g_extern.audio_data.conv_outsamples[i + 0];
}
g_extern.audio_data.data_ptr = 0;
}
2011-01-31 15:48:42 +00:00
static void check_rewind(void)
{
2011-10-15 12:33:41 +00:00
flush_rewind_audio();
g_extern.frame_is_reverse = false;
2011-10-15 12:33:41 +00:00
static bool first = true;
if (first)
{
first = false;
return;
}
2011-01-31 15:48:42 +00:00
if (!g_extern.state_manager)
return;
if (driver.input->key_pressed(driver.input_data, SSNES_REWIND))
{
2011-01-31 17:49:50 +00:00
msg_queue_clear(g_extern.msg_queue);
2011-01-31 15:48:42 +00:00
void *buf;
if (state_manager_pop(g_extern.state_manager, &buf))
{
g_extern.frame_is_reverse = true;
2011-10-15 12:33:41 +00:00
setup_rewind_audio();
msg_queue_push(g_extern.msg_queue, "Rewinding.", 0, g_extern.is_paused ? 1 : 30);
psnes_unserialize((uint8_t*)buf, g_extern.state_size);
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie)
bsv_movie_frame_rewind(g_extern.bsv.movie);
}
2011-01-31 17:49:50 +00:00
else
msg_queue_push(g_extern.msg_queue, "Reached end of rewind buffer.", 0, 30);
2011-01-31 15:48:42 +00:00
}
else
{
static unsigned cnt = 0;
cnt = (cnt + 1) % (g_settings.rewind_granularity ? g_settings.rewind_granularity : 1); // Avoid possible SIGFPE.
2011-11-20 19:19:05 +00:00
if (cnt == 0 || g_extern.bsv.movie)
{
psnes_serialize((uint8_t*)g_extern.state_buf, g_extern.state_size);
state_manager_push(g_extern.state_manager, g_extern.state_buf);
}
2011-01-31 15:48:42 +00:00
}
2011-10-15 12:33:41 +00:00
psnes_set_audio_sample(g_extern.frame_is_reverse ? audio_sample_rewind : audio_sample);
2011-01-31 15:48:42 +00:00
}
2011-02-02 11:45:56 +00:00
static void check_movie_record(void)
{
static bool old_button = false;
bool new_button;
if ((new_button = driver.input->key_pressed(driver.input_data, SSNES_MOVIE_RECORD_TOGGLE)) && !old_button)
{
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie)
2011-02-02 11:45:56 +00:00
{
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, "Stopping movie record.", 2, 180);
2012-02-11 20:11:36 +00:00
SSNES_LOG("Stopping movie record.\n");
2011-11-20 19:19:05 +00:00
bsv_movie_free(g_extern.bsv.movie);
g_extern.bsv.movie = NULL;
2011-02-02 11:45:56 +00:00
}
else
{
g_settings.rewind_granularity = 1;
2011-11-20 19:19:05 +00:00
2012-01-02 12:32:25 +00:00
char path[PATH_MAX];
2011-02-02 12:37:01 +00:00
if (g_extern.state_slot > 0)
2011-11-20 19:19:05 +00:00
{
snprintf(path, sizeof(path), "%s%u.bsv",
g_extern.bsv.movie_path, g_extern.state_slot);
}
2011-02-02 12:37:01 +00:00
else
2011-11-20 19:19:05 +00:00
{
snprintf(path, sizeof(path), "%s.bsv",
g_extern.bsv.movie_path);
}
2011-02-02 12:37:01 +00:00
2012-01-02 12:32:25 +00:00
char msg[PATH_MAX];
snprintf(msg, sizeof(msg), "Starting movie record to \"%s\".", path);
2011-11-19 00:33:21 +00:00
2011-11-20 19:19:05 +00:00
g_extern.bsv.movie = bsv_movie_init(path, SSNES_MOVIE_RECORD);
2011-02-02 11:45:56 +00:00
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, g_extern.bsv.movie ? msg : "Failed to start movie record.", 1, 180);
2011-02-02 11:45:56 +00:00
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie)
2012-02-11 20:11:36 +00:00
SSNES_LOG("Starting movie record to \"%s\".\n", path);
2011-02-02 11:45:56 +00:00
else
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to start movie record.\n");
2011-02-02 11:45:56 +00:00
}
}
old_button = new_button;
}
static void check_movie_playback(void)
{
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie_end)
{
msg_queue_push(g_extern.msg_queue, "Movie playback ended.", 1, 180);
2012-02-11 20:11:36 +00:00
SSNES_LOG("Movie playback ended.\n");
2011-11-20 19:19:05 +00:00
bsv_movie_free(g_extern.bsv.movie);
g_extern.bsv.movie = NULL;
g_extern.bsv.movie_end = false;
g_extern.bsv.movie_playback = false;
}
}
2011-02-05 19:46:58 +00:00
static void check_pause(void)
{
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_PAUSE_TOGGLE);
2011-02-05 20:45:44 +00:00
2011-10-18 19:57:28 +00:00
// FRAMEADVANCE will set us into pause mode.
new_state |= !g_extern.is_paused && driver.input->key_pressed(driver.input_data, SSNES_FRAMEADVANCE);
2011-02-05 20:45:44 +00:00
static bool old_focus = true;
bool focus = true;
if (g_settings.pause_nonactive)
focus = driver.video->focus(driver.video_data);
if (focus && new_state && !old_state)
2011-02-05 19:46:58 +00:00
{
g_extern.is_paused = !g_extern.is_paused;
if (g_extern.is_paused)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Paused.\n");
2011-02-05 19:46:58 +00:00
if (driver.audio_data)
driver.audio->stop(driver.audio_data);
}
else
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Unpaused.\n");
2011-02-05 19:46:58 +00:00
if (driver.audio_data)
{
if (!driver.audio->start(driver.audio_data))
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to resume audio driver. Will continue without audio.\n");
g_extern.audio_active = false;
}
}
2011-02-05 19:46:58 +00:00
}
}
2011-02-05 20:45:44 +00:00
else if (focus && !old_focus)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Unpaused.\n");
2011-02-05 20:45:44 +00:00
g_extern.is_paused = false;
if (driver.audio_data)
{
if (!driver.audio->start(driver.audio_data))
{
2012-02-11 20:11:36 +00:00
SSNES_ERR("Failed to resume audio driver. Will continue without audio.\n");
g_extern.audio_active = false;
}
}
2011-02-05 20:45:44 +00:00
}
else if (!focus && old_focus)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Paused.\n");
2011-02-05 20:45:44 +00:00
g_extern.is_paused = true;
if (driver.audio_data)
driver.audio->stop(driver.audio_data);
}
2011-02-05 19:46:58 +00:00
2011-02-05 20:45:44 +00:00
old_focus = focus;
2011-02-05 19:46:58 +00:00
old_state = new_state;
}
2011-10-17 19:30:58 +00:00
static void check_oneshot(void)
{
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_FRAMEADVANCE);
2011-10-18 19:57:28 +00:00
g_extern.is_oneshot = (new_state && !old_state);
2011-10-17 19:30:58 +00:00
old_state = new_state;
2011-10-18 19:57:28 +00:00
// Rewind buttons works like FRAMEREWIND when paused. We will one-shot in that case.
static bool old_rewind_state = false;
bool new_rewind_state = driver.input->key_pressed(driver.input_data, SSNES_REWIND);
g_extern.is_oneshot |= new_rewind_state && !old_rewind_state;
old_rewind_state = new_rewind_state;
2011-10-17 19:30:58 +00:00
}
void ssnes_game_reset(void)
{
2012-02-11 20:11:36 +00:00
SSNES_LOG("Resetting game.\n");
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, "Reset.", 1, 120);
psnes_reset();
init_controllers(); // bSNES since v073r01 resets controllers to JOYPAD after a reset, so just enforce it here.
}
static void check_reset(void)
{
2011-10-29 11:35:01 +00:00
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_RESET);
if (new_state && !old_state)
ssnes_game_reset();
2011-10-29 11:35:01 +00:00
old_state = new_state;
}
2011-03-29 16:28:31 +00:00
#ifdef HAVE_XML
static void check_shader_dir(void)
{
static bool old_pressed_next = false;
2011-04-17 14:53:19 +00:00
static bool old_pressed_prev = false;
2011-03-29 16:28:31 +00:00
if (!g_extern.shader_dir.elems || !driver.video->xml_shader)
return;
bool should_apply = false;
bool pressed_next = driver.input->key_pressed(driver.input_data, SSNES_SHADER_NEXT);
bool pressed_prev = driver.input->key_pressed(driver.input_data, SSNES_SHADER_PREV);
if (pressed_next && !old_pressed_next)
{
should_apply = true;
g_extern.shader_dir.ptr = (g_extern.shader_dir.ptr + 1) % g_extern.shader_dir.size;
}
else if (pressed_prev && !old_pressed_prev)
{
should_apply = true;
if (g_extern.shader_dir.ptr == 0)
g_extern.shader_dir.ptr = g_extern.shader_dir.size - 1;
else
g_extern.shader_dir.ptr--;
}
if (should_apply)
{
const char *shader = g_extern.shader_dir.elems[g_extern.shader_dir.ptr];
2011-04-03 20:29:36 +00:00
strlcpy(g_settings.video.bsnes_shader_path, shader, sizeof(g_settings.video.bsnes_shader_path));
g_settings.video.shader_type = SSNES_SHADER_BSNES;
2011-03-29 16:28:31 +00:00
msg_queue_clear(g_extern.msg_queue);
char msg[512];
snprintf(msg, sizeof(msg), "XML shader #%u: \"%s\"", (unsigned)g_extern.shader_dir.ptr, shader);
msg_queue_push(g_extern.msg_queue, msg, 1, 120);
SSNES_LOG("Applying shader \"%s\"\n", shader);
if (!driver.video->xml_shader(driver.video_data, shader))
2012-02-11 20:11:36 +00:00
SSNES_WARN("Failed to apply shader.\n");
2011-03-29 16:28:31 +00:00
}
old_pressed_next = pressed_next;
old_pressed_prev = pressed_prev;
}
2011-04-17 14:53:19 +00:00
static void check_cheats(void)
{
if (!g_extern.cheat)
return;
static bool old_pressed_prev = false;
static bool old_pressed_next = false;
static bool old_pressed_toggle = false;
bool pressed_next = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_PLUS);
bool pressed_prev = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_MINUS);
bool pressed_toggle = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_TOGGLE);
if (pressed_next && !old_pressed_next)
cheat_manager_index_next(g_extern.cheat);
else if (pressed_prev && !old_pressed_prev)
cheat_manager_index_prev(g_extern.cheat);
else if (pressed_toggle && !old_pressed_toggle)
cheat_manager_toggle(g_extern.cheat);
old_pressed_prev = pressed_prev;
old_pressed_next = pressed_next;
old_pressed_toggle = pressed_toggle;
}
2011-03-29 16:28:31 +00:00
#endif
#ifndef HAVE_SCREENSHOTS_BUILTIN
2011-05-15 15:16:29 +00:00
static void check_screenshot(void)
{
static bool old_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_SCREENSHOT);
2011-10-23 10:38:11 +00:00
if (pressed && !old_pressed)
take_screenshot();
2011-05-15 15:16:29 +00:00
old_pressed = pressed;
}
#endif
2011-05-15 15:16:29 +00:00
#ifdef HAVE_DYLIB
static void check_dsp_config(void)
{
if (!g_extern.audio_data.dsp_plugin || !g_extern.audio_data.dsp_plugin->config)
return;
static bool old_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_DSP_CONFIG);
if (pressed && !old_pressed)
g_extern.audio_data.dsp_plugin->config(g_extern.audio_data.dsp_handle);
old_pressed = pressed;
}
#endif
2011-11-26 14:54:58 +00:00
static void check_mute(void)
{
if (!g_extern.audio_active)
return;
static bool old_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_MUTE);
if (pressed && !old_pressed)
{
g_extern.audio_data.mute = !g_extern.audio_data.mute;
const char *msg = g_extern.audio_data.mute ? "Audio muted." : "Audio unmuted.";
2011-11-26 14:54:58 +00:00
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
old_pressed = pressed;
}
2012-01-21 13:24:38 +00:00
#ifdef HAVE_NETPLAY
static void check_netplay_flip(void)
{
static bool old_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_NETPLAY_FLIP);
if (pressed && !old_pressed)
netplay_flip_players(g_extern.netplay);
old_pressed = pressed;
}
#endif
2011-01-23 22:09:54 +00:00
static void do_state_checks(void)
{
#ifndef HAVE_SCREENSHOTS_BUILTIN
2011-10-23 10:38:11 +00:00
check_screenshot();
#endif
2011-11-26 14:54:58 +00:00
check_mute();
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
if (!g_extern.netplay)
{
#endif
2011-02-13 15:40:24 +00:00
check_pause();
2011-10-17 19:30:58 +00:00
check_oneshot();
if (check_fullscreen() && g_extern.is_paused)
2012-01-21 11:12:44 +00:00
ssnes_render_cached_frame();
2011-02-05 19:46:58 +00:00
if (g_extern.is_paused && !g_extern.is_oneshot)
return;
set_fast_forward_button(
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY),
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_HOLD_KEY));
2011-01-23 22:09:54 +00:00
2011-11-20 19:19:05 +00:00
if (!g_extern.bsv.movie)
2011-02-13 15:40:24 +00:00
{
check_stateslots();
check_savestates();
}
2011-10-18 19:57:28 +00:00
check_rewind();
2011-11-20 19:19:05 +00:00
if (g_extern.bsv.movie_playback)
check_movie_playback();
else
2011-02-13 15:40:24 +00:00
check_movie_record();
2011-10-18 19:57:28 +00:00
2011-03-29 16:28:31 +00:00
#ifdef HAVE_XML
check_shader_dir();
2011-10-18 19:59:37 +00:00
check_cheats();
2011-03-29 16:28:31 +00:00
#endif
#ifdef HAVE_DYLIB
check_dsp_config();
#endif
2011-10-18 19:59:37 +00:00
check_reset();
#ifdef HAVE_NETPLAY
2011-02-02 11:10:27 +00:00
}
else
2012-01-21 13:24:38 +00:00
{
check_netplay_flip();
check_fullscreen();
2012-01-21 13:24:38 +00:00
}
#endif
#ifdef HAVE_DYLIB
// DSP plugin doesn't use variable input rate.
if (!g_extern.audio_data.dsp_plugin)
#endif
check_input_rate();
2011-01-23 22:09:54 +00:00
}
2011-03-17 00:25:44 +00:00
static void fill_title_buf(void)
{
snprintf(g_extern.title_buf, sizeof(g_extern.title_buf), "SSNES : %s", psnes_library_id());
2011-03-17 00:25:44 +00:00
}
2011-08-26 15:32:04 +00:00
static void init_state(void)
{
g_extern.video_active = true;
g_extern.audio_active = true;
g_extern.game_type = SSNES_CART_NORMAL;
}
void ssnes_main_clear_state(void)
2010-10-01 19:39:15 +00:00
{
memset(&g_settings, 0, sizeof(g_settings));
free(g_extern.system.environment);
free(g_extern.system.environment_split);
memset(&g_extern, 0, sizeof(g_extern));
2012-01-21 17:00:07 +00:00
#ifdef SSNES_CONSOLE
memset(&g_console, 0, sizeof(g_console));
#endif
2011-08-26 15:32:04 +00:00
init_state();
}
2011-08-26 15:32:04 +00:00
int ssnes_main_init(int argc, char *argv[])
{
init_state();
2010-12-29 20:12:56 +00:00
parse_input(argc, argv);
2011-12-25 20:39:58 +00:00
int sjlj_ret;
if ((sjlj_ret = setjmp(g_extern.error_sjlj_context)) > 0)
{
SSNES_ERR("Fatal error received in: \"%s\"\n", g_extern.error_string);
return sjlj_ret;
}
g_extern.error_in_init = true;
2011-12-25 20:39:58 +00:00
if (g_extern.verbose)
{
2011-12-25 20:43:00 +00:00
fprintf(stderr, "=== Build =======================================");
2011-12-25 20:39:58 +00:00
print_compiler(stderr);
2011-12-25 20:43:00 +00:00
fprintf(stderr, "=================================================\n");
2011-12-25 20:39:58 +00:00
}
2012-01-28 14:47:02 +00:00
config_load();
init_libsnes_sym();
2011-03-17 00:25:44 +00:00
fill_title_buf();
2010-12-30 12:54:49 +00:00
psnes_init();
if (*g_extern.basename)
psnes_set_cartridge_basename(g_extern.basename);
SSNES_LOG("Version of libsnes API: %u.%u\n",
psnes_library_revision_major(), psnes_library_revision_minor());
2010-10-01 19:39:15 +00:00
fill_pathnames();
2011-09-27 13:31:25 +00:00
set_savestate_auto_index();
g_extern.use_sram = true;
2011-12-24 12:46:12 +00:00
if (!init_rom_file(g_extern.game_type))
goto error;
2010-10-01 19:39:15 +00:00
2011-02-13 17:19:37 +00:00
init_msg_queue();
2011-02-13 16:45:14 +00:00
2011-11-20 19:19:05 +00:00
if (!g_extern.sram_load_disable)
load_save_files();
2011-11-18 17:03:24 +00:00
else
2012-02-11 20:11:36 +00:00
SSNES_LOG("Skipping SRAM load.\n");
2011-11-20 19:19:05 +00:00
init_movie();
2011-02-25 10:47:27 +00:00
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
init_netplay();
2011-03-19 19:41:07 +00:00
#endif
2011-02-13 17:19:37 +00:00
init_drivers();
2010-10-01 19:39:15 +00:00
#ifdef HAVE_NETPLAY
2011-02-25 10:47:27 +00:00
if (!g_extern.netplay)
#endif
2011-02-25 10:47:27 +00:00
init_rewind();
2012-01-11 18:22:18 +00:00
init_libsnes_cbs();
2011-01-10 13:29:00 +00:00
init_controllers();
2011-02-13 16:45:14 +00:00
#ifdef HAVE_FFMPEG
init_recording();
#endif
2011-01-03 19:46:50 +00:00
#ifdef HAVE_NETPLAY
g_extern.use_sram = !g_extern.sram_save_disable && !g_extern.netplay_is_client;
#else
g_extern.use_sram = !g_extern.sram_save_disable;
#endif
2011-11-20 19:19:05 +00:00
if (!g_extern.use_sram)
2012-02-11 20:11:36 +00:00
SSNES_LOG("SRAM will not be saved.\n");
2011-11-18 17:03:24 +00:00
if (g_extern.use_sram)
2011-02-13 16:45:14 +00:00
init_autosave();
#ifdef HAVE_XML
#ifdef HAVE_NETPLAY
if (!g_extern.bsv.movie && !g_extern.netplay)
#else
if (!g_extern.bsv.movie)
#endif
init_cheats();
#endif
g_extern.error_in_init = false;
return 0;
2011-08-13 02:09:08 +00:00
error:
psnes_unload_cartridge();
psnes_term();
uninit_drivers();
uninit_libsnes_sym();
2010-05-26 19:27:37 +00:00
return 1;
}
bool ssnes_main_iterate(void)
{
// DSP plugin GUI events.
if (g_extern.audio_data.dsp_handle && g_extern.audio_data.dsp_plugin->events)
g_extern.audio_data.dsp_plugin->events(g_extern.audio_data.dsp_handle);
// Time to drop?
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
!driver.video->alive(driver.video_data))
return false;
// Checks for stuff like fullscreen, save states, etc.
do_state_checks();
// Run libsnes for one frame.
if (!g_extern.is_paused || g_extern.is_oneshot)
{
2011-11-30 16:46:58 +00:00
#ifdef HAVE_THREADS
lock_autosave();
2011-11-30 16:46:58 +00:00
#endif
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
if (g_extern.netplay)
netplay_pre_frame(g_extern.netplay);
2011-03-19 19:41:07 +00:00
#endif
if (g_extern.bsv.movie)
bsv_movie_set_frame_start(g_extern.bsv.movie);
psnes_run();
if (g_extern.bsv.movie)
bsv_movie_set_frame_end(g_extern.bsv.movie);
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
if (g_extern.netplay)
netplay_post_frame(g_extern.netplay);
2011-03-19 19:41:07 +00:00
#endif
2011-11-30 16:46:58 +00:00
#ifdef HAVE_THREADS
unlock_autosave();
2011-11-30 16:46:58 +00:00
#endif
2010-05-26 19:27:37 +00:00
}
else
{
input_poll();
ssnes_sleep(10);
}
return true;
}
2010-05-26 19:27:37 +00:00
void ssnes_main_deinit(void)
{
2011-03-19 19:41:07 +00:00
#ifdef HAVE_NETPLAY
2011-02-13 15:40:24 +00:00
deinit_netplay();
2011-03-19 19:41:07 +00:00
#endif
2011-02-13 16:45:14 +00:00
if (g_extern.use_sram)
2011-02-13 16:45:14 +00:00
deinit_autosave();
2011-02-10 20:16:59 +00:00
#ifdef HAVE_FFMPEG
deinit_recording();
#endif
2011-01-03 19:46:50 +00:00
if (g_extern.use_sram)
2011-02-02 11:10:27 +00:00
save_files();
2011-02-25 10:47:27 +00:00
#ifdef HAVE_NETPLAY
2011-02-25 10:47:27 +00:00
if (!g_extern.netplay)
#endif
2011-02-25 10:47:27 +00:00
deinit_rewind();
2010-05-26 19:27:37 +00:00
2011-04-17 11:30:59 +00:00
#ifdef HAVE_XML
deinit_cheats();
#endif
2011-02-02 11:10:27 +00:00
deinit_movie();
2011-02-02 11:45:56 +00:00
deinit_msg_queue();
2010-12-30 12:54:49 +00:00
psnes_unload_cartridge();
psnes_term();
2010-05-28 00:45:18 +00:00
uninit_drivers();
uninit_libsnes_sym();
}
2010-05-26 19:27:37 +00:00
int main(int argc, char *argv[])
{
int init_ret;
if ((init_ret = ssnes_main_init(argc, argv))) return init_ret;
while (ssnes_main_iterate());
ssnes_main_deinit();
ssnes_main_clear_state();
return 0;
2010-05-26 19:27:37 +00:00
}
2010-05-26 22:39:56 +00:00