RetroArch/ssnes.c

726 lines
22 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.
2010-05-28 16:21:33 +00:00
* Copyright (C) 2010 - 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 <http://www.gnu.org/licenses/>.
*/
2010-05-27 14:46:22 +00:00
#include <stdbool.h>
2010-05-28 16:07:04 +00:00
#include <libsnes.hpp>
2010-05-26 19:27:37 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2010-10-01 19:39:15 +00:00
#include <getopt.h>
2010-05-28 00:45:18 +00:00
#include "driver.h"
#include "file.h"
2010-12-30 00:33:40 +00:00
#include "hqflt/filters.h"
#include "general.h"
2010-12-30 12:54:49 +00:00
#include "dynamic.h"
2011-01-03 19:46:50 +00:00
#include "record/ffemu.h"
#include <assert.h>
#ifdef HAVE_SRC
#include <samplerate.h>
#endif
2010-12-29 18:18:37 +00:00
struct global g_extern = {
.video_active = true,
.audio_active = true,
.game_type = SSNES_CART_NORMAL,
2010-12-29 18:18:37 +00:00
};
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)
2010-08-16 16:40:17 +00:00
{
static bool old_button_state = false;
static bool syncing_state = false;
if (new_button_state && !old_button_state)
{
syncing_state = !syncing_state;
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
}
old_button_state = new_button_state;
}
2010-12-30 00:33:40 +00:00
#ifdef HAVE_FILTER
2010-06-27 13:46:23 +00:00
static inline void process_frame (uint16_t * restrict out, const uint16_t * restrict in, unsigned width, unsigned height)
{
2010-08-28 21:55:09 +00:00
int pitch = 1024;
if ( height == 448 || height == 478 )
pitch = 512;
2010-06-27 13:46:23 +00:00
for ( int y = 0; y < height; y++ )
{
2010-08-28 21:55:09 +00:00
const uint16_t *src = in + y * pitch;
2010-06-27 13:46:23 +00:00
uint16_t *dst = out + y * width;
memcpy(dst, src, width * sizeof(uint16_t));
}
}
2010-11-08 22:38:32 +00:00
#endif
2010-06-27 13:46:23 +00:00
// libsnes: 0.065
// Format received is 16-bit 0RRRRRGGGGGBBBBB
2010-05-28 00:45:18 +00:00
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
{
2010-12-29 18:18:37 +00:00
if ( !g_extern.video_active )
2010-05-28 00:45:18 +00:00
return;
2010-05-26 19:27:37 +00:00
#ifdef HAVE_FFMPEG
if (g_extern.recording)
{
struct ffemu_video_data ffemu_data = {
.data = data,
.pitch = height == 448 || height == 478 ? 1024 : 2048,
.width = width,
.height = height
};
ffemu_push_video(g_extern.rec, &ffemu_data);
}
#endif
2011-01-03 19:46:50 +00:00
2010-12-30 00:33:40 +00:00
#ifdef HAVE_FILTER
uint16_t output_filter[width * height * 4 * 4];
2010-11-08 22:38:32 +00:00
uint16_t output[width * height];
if (g_settings.video.filter != FILTER_NONE)
process_frame(output, data, width, height);
2010-05-26 19:27:37 +00:00
2010-12-30 00:33:40 +00:00
switch (g_settings.video.filter)
{
case FILTER_HQ2X:
ProcessHQ2x(output, output_filter);
if (!driver.video->frame(driver.video_data, output_filter, width << 1, height << 1, width << 2, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:38:20 +00:00
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
break;
case FILTER_HQ4X:
ProcessHQ4x(output, output_filter);
if (!driver.video->frame(driver.video_data, output_filter, width << 2, height << 2, width << 3, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:38:20 +00:00
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
break;
case FILTER_GRAYSCALE:
grayscale_filter(output, width, height);
if (!driver.video->frame(driver.video_data, output, width, height, width << 1, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:38:20 +00:00
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
break;
case FILTER_BLEED:
bleed_filter(output, width, height);
if (!driver.video->frame(driver.video_data, output, width, height, width << 1, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:38:20 +00:00
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
break;
case FILTER_NTSC:
ntsc_filter(output_filter, output, width, height);
if (!driver.video->frame(driver.video_data, output_filter, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:38:20 +00:00
g_extern.video_active = false;
2010-12-30 00:33:40 +00:00
break;
default:
if (!driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048, msg_queue_pull(g_extern.msg_queue)))
2010-12-30 00:33:40 +00:00
g_extern.video_active = false;
}
2010-05-29 14:59:57 +00:00
#else
if (!driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048, msg_queue_pull(g_extern.msg_queue)))
2010-12-29 18:18:37 +00:00
g_extern.video_active = false;
2010-12-29 18:43:17 +00:00
#endif
2010-05-26 19:27:37 +00:00
}
2010-05-28 00:45:18 +00:00
static void audio_sample(uint16_t left, uint16_t right)
2010-05-26 19:27:37 +00:00
{
2010-12-29 18:18:37 +00:00
if ( !g_extern.audio_active )
2010-05-28 00:45:18 +00:00
return;
#ifdef HAVE_FFMPEG
if (g_extern.recording)
{
static int16_t static_data[2];
static_data[0] = left;
static_data[1] = right;
struct ffemu_audio_data ffemu_data = {
.data = static_data,
.frames = 1
};
ffemu_push_audio(g_extern.rec, &ffemu_data);
}
#endif
2011-01-03 19:46:50 +00:00
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&left)/0x8000;
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&right)/0x8000;
2010-05-26 19:27:37 +00:00
if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size)
2010-05-26 20:42:58 +00:00
{
2010-05-26 22:26:11 +00:00
SRC_DATA src_data;
2010-05-26 20:42:58 +00:00
src_data.data_in = g_extern.audio_data.data;
src_data.data_out = g_extern.audio_data.outsamples;
src_data.input_frames = g_extern.audio_data.chunk_size / 2;
src_data.output_frames = g_extern.audio_data.chunk_size * 8;
2010-05-26 22:26:11 +00:00
src_data.end_of_input = 0;
2010-12-29 18:43:17 +00:00
src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
2010-05-26 20:42:58 +00:00
2010-12-29 18:18:37 +00:00
src_process(g_extern.source, &src_data);
2010-05-26 20:42:58 +00:00
if (g_extern.audio_data.use_float)
2010-08-16 17:16:03 +00:00
{
if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
}
}
else
{
src_float_to_short_array(g_extern.audio_data.outsamples, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * 2);
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
}
2010-08-16 17:16:03 +00:00
}
2010-05-26 20:42:58 +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)
{
const struct snes_keybind *binds[MAX_PLAYERS];
for (int i = 0; i < MAX_PLAYERS; i++)
binds[i] = g_settings.input.binds[i];
2010-10-01 18:15:45 +00:00
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
2010-05-26 19:27:37 +00:00
}
2010-08-28 15:20:29 +00:00
static void fill_pathname(char *out_path, char *in_path, const char *replace)
{
char tmp_path[strlen(in_path) + 1];
strcpy(tmp_path, in_path);
char *tok = NULL;
tok = strrchr(tmp_path, '.');
if (tok != NULL)
*tok = '\0';
strcpy(out_path, tmp_path);
strcat(out_path, replace);
}
2010-05-26 19:27:37 +00:00
#ifdef HAVE_FFMPEG
#define FFMPEG_HELP_QUARK " | -r/--record "
#else
#define FFMPEG_HELP_QUARK
#endif
2011-01-07 16:59:53 +00:00
#ifdef _WIN32
#define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe"
#else
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"
#endif
2010-10-01 19:39:15 +00:00
static void print_help(void)
{
2010-10-02 11:54:32 +00:00
puts("=================================================");
2010-10-01 19:39:15 +00:00
puts("ssnes: Simple Super Nintendo Emulator (libsnes)");
2010-10-02 11:54:32 +00:00
puts("=================================================");
puts("Usage: ssnes [rom file] [-h/--help | -c/--config | -v/--verbose | -4/--multitap | -j/--justifier | -J/--justifiers | -S/--savestate | -m/--mouse | -g/--gameboy | -b/--bsx | -B/--bsxslot | --sufamiA | --sufamiB | -p/--scope | -s/--save" FFMPEG_HELP_QUARK "]");
2010-10-01 19:39:15 +00:00
puts("\t-h/--help: Show this help message");
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
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);
#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-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-01-07 16:59:53 +00:00
#ifdef HAVE_FFMPEG
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
#endif
2010-10-01 20:10:28 +00:00
puts("\t-v/--verbose: Verbose logging");
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();
2010-05-26 19:27:37 +00:00
exit(1);
}
2010-05-29 13:21:30 +00:00
2010-10-01 19:39:15 +00:00
struct option opts[] = {
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
#ifdef HAVE_FFMPEG
{ "record", 1, NULL, 'r' },
#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' },
{ "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' },
2010-10-01 19:39:15 +00:00
{ NULL, 0, NULL, 0 }
};
int option_index = 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
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
2010-10-01 19:39:15 +00:00
for(;;)
2010-05-29 13:21:30 +00:00
{
2010-10-01 19:39:15 +00:00
int c = getopt_long(argc, argv, optstring, opts, &option_index);
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':
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1);
break;
case 'g':
strncpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path) - 1);
g_extern.game_type = SSNES_CART_SGB;
break;
case 'b':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
g_extern.game_type = SSNES_CART_BSX;
break;
case 'B':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
g_extern.game_type = SSNES_CART_BSX_SLOTTED;
break;
case 'Y':
strncpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]) - 1);
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'Z':
strncpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]) - 1);
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'S':
strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1);
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();
exit(1);
}
g_extern.has_mouse[port - 1] = true;
2011-01-10 06:58:11 +00:00
break;
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':
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
break;
#endif
2010-12-29 20:12:56 +00:00
#ifdef HAVE_FFMPEG
case 'r':
strncpy(g_extern.record_path, optarg, sizeof(g_extern.record_path) - 1);
g_extern.recording = true;
break;
#endif
2010-10-01 19:39:15 +00:00
case '?':
print_help();
exit(1);
default:
2010-10-01 20:10:28 +00:00
SSNES_ERR("Error parsing arguments.\n");
2010-10-01 19:39:15 +00:00
exit(1);
}
2010-05-29 13:21:30 +00:00
}
2010-10-01 19:39:15 +00:00
if (optind < argc)
{
2010-12-22 14:55:38 +00:00
char tmp[strlen(argv[optind]) + 1];
strcpy(tmp, argv[optind]);
char *dst = strrchr(tmp, '.');
if (dst)
*dst = '\0';
strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1);
2010-12-22 14:55:38 +00:00
2010-10-01 20:10:28 +00:00
SSNES_LOG("Opening file: \"%s\"\n", argv[optind]);
2010-12-29 18:43:17 +00:00
g_extern.rom_file = fopen(argv[optind], "rb");
if (g_extern.rom_file == NULL)
2010-10-01 19:39:15 +00:00
{
SSNES_ERR("Could not open file: \"%s\"\n", argv[optind]);
2010-10-01 19:39:15 +00:00
exit(1);
}
// strl* would be nice :D
2010-12-29 18:43:17 +00:00
if (strlen(g_extern.savefile_name_srm) == 0)
{
strcpy(g_extern.savefile_name_srm, g_extern.basename);
size_t len = strlen(g_extern.savefile_name_srm);
strncat(g_extern.savefile_name_srm, ".srm", sizeof(g_extern.savefile_name_srm) - len - 1);
}
if (strlen(g_extern.savestate_name) == 0)
{
strcpy(g_extern.savestate_name, g_extern.basename);
size_t len = strlen(g_extern.savestate_name);
strncat(g_extern.savestate_name, ".state", sizeof(g_extern.savestate_name) - len - 1);
}
2010-10-01 19:39:15 +00:00
}
2010-12-29 18:43:17 +00:00
else if (strlen(g_extern.savefile_name_srm) == 0)
2010-10-01 20:10:28 +00:00
{
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
print_help();
exit(1);
}
else if (strlen(g_extern.savestate_name) == 0)
{
SSNES_ERR("Need savestate path argument (--savefile) when reading rom from stdin.\n");
2010-10-01 20:10:28 +00:00
print_help();
exit(1);
}
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)
{
SSNES_LOG("Connecting multitap to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP);
}
else
{
for (int i = 0; i < 2; i++)
2011-01-10 13:29:00 +00:00
{
if (g_extern.has_mouse[i])
{
SSNES_LOG("Connecting mouse to port %d\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE);
}
else if (g_extern.has_scope[i])
{
SSNES_LOG("Connecting scope to port %d\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:
case SSNES_CART_SGB:
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;
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:
case SSNES_CART_SGB:
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
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
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:
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)
{
// Hardcode these options at the moment. Should be specificed in the config file later on.
if (g_extern.recording)
{
struct ffemu_rational ntsc_fps = {60000, 1001};
struct ffemu_rational pal_fps = {50000, 1001};
struct ffemu_params params = {
.vcodec = FFEMU_VIDEO_H264,
.acodec = FFEMU_AUDIO_VORBIS,
.rescaler = FFEMU_RESCALER_POINT,
.out_width = 512,
.out_height = 448,
.channels = 2,
.samplerate = 32040,
.filename = g_extern.record_path,
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
.aspect_ratio = 4.0/3
};
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
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)
{
g_extern.msg_queue = msg_queue_new(8);
assert(g_extern.msg_queue);
}
static void deinit_msg_queue(void)
{
if (g_extern.msg_queue)
msg_queue_free(g_extern.msg_queue);
}
static void fill_pathnames(void)
{
switch (g_extern.game_type)
{
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
// BSX PSRM
fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm");
break;
case SSNES_CART_SUFAMI:
// SUFAMI ARAM
fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm");
// SUFAMI BRAM
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm");
break;
default:
// Infer .rtc save path from save ram path.
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
}
}
2010-10-01 19:39:15 +00:00
int main(int argc, char *argv[])
{
2010-12-29 20:12:56 +00:00
parse_input(argc, argv);
2010-12-30 00:33:40 +00:00
parse_config();
2010-12-30 12:54:49 +00:00
init_dlsym();
psnes_init();
if (strlen(g_extern.basename) > 0)
psnes_set_cartridge_basename(g_extern.basename);
2010-12-30 12:54:49 +00:00
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();
if (!init_rom_file(g_extern.game_type))
goto error;
2010-10-01 19:39:15 +00:00
init_drivers();
2010-12-30 12:54:49 +00:00
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_poll(input_poll);
psnes_set_input_state(input_state);
2011-01-10 13:29:00 +00:00
init_controllers();
2011-01-10 06:58:11 +00:00
load_save_files();
2010-05-26 19:27:37 +00:00
#ifdef HAVE_FFMPEG
init_recording();
#endif
2011-01-03 19:46:50 +00:00
init_msg_queue();
// Main loop
2010-05-26 19:27:37 +00:00
for(;;)
{
// Time to drop?
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
2011-01-06 19:01:32 +00:00
!driver.video->alive(driver.video_data))
2010-05-26 19:27:37 +00:00
break;
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
// Save or load state here.
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)
{
2011-01-23 01:48:06 +00:00
msg_queue_push(g_extern.msg_queue, "Saving state! ^_^", 1, 180);
2011-01-18 14:34:37 +00:00
save_state(g_extern.savestate_name);
}
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)
{
2011-01-23 01:48:06 +00:00
msg_queue_push(g_extern.msg_queue, "Loading state! ^_^", 1, 180);
2011-01-18 14:34:37 +00:00
load_state(g_extern.savestate_name);
}
old_should_loadstate = should_loadstate;
2010-05-26 22:39:56 +00:00
// If we go fullscreen we drop all drivers and reinit to be safe.
if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY))
{
2010-12-29 18:18:37 +00:00
g_settings.video.fullscreen = !g_settings.video.fullscreen;
2010-05-28 00:45:18 +00:00
uninit_drivers();
init_drivers();
}
2010-05-26 19:27:37 +00:00
// Run libsnes for one frame.
2010-12-30 12:54:49 +00:00
psnes_run();
2010-05-26 19:27:37 +00:00
}
deinit_msg_queue();
#ifdef HAVE_FFMPEG
deinit_recording();
#endif
2011-01-03 19:46:50 +00:00
save_files();
2010-05-26 19:27:37 +00:00
2010-12-30 12:54:49 +00:00
psnes_unload_cartridge();
psnes_term();
2010-05-28 00:45:18 +00:00
uninit_drivers();
2010-12-30 12:54:49 +00:00
uninit_dlsym();
2010-05-26 19:27:37 +00:00
return 0;
2010-06-27 13:46:23 +00:00
error:
2010-12-30 12:54:49 +00:00
psnes_unload_cartridge();
psnes_term();
2010-06-27 13:46:23 +00:00
uninit_drivers();
2010-12-30 12:54:49 +00:00
uninit_dlsym();
2010-06-27 13:46:23 +00:00
return 1;
2010-05-26 19:27:37 +00:00
}
2010-05-26 22:39:56 +00:00