Compare commits

...
83 Commits
Author SHA1 Message Date
Themaister 0735398e64 Updates for 0.3. 2011-02-22 22:24:58 +01:00
Themaister ec2d10e069 Add default path to OSX and check in APPDATA for Win32. 2011-02-22 11:28:28 +01:00
Themaister 4cdb6151e5 Add netplay_client_swap_input. 2011-02-20 12:12:53 +01:00
Themaister e482b1bd18 Fix potential problem. Some cleanup. 2011-02-18 23:51:51 +01:00
Themaister 9be3c13a07 Hrm. This stuff is very error prone. :V 2011-02-18 15:50:57 +01:00
Themaister 8a4bcacf88 hrm... 2011-02-18 14:49:15 +01:00
Themaister 6b9b37b528 Should fix an extreme edge case. 2011-02-18 13:39:31 +01:00
Themaister 8e87c1ff19 Could this be it? :v 2011-02-18 03:42:43 +01:00
Themaister aa648c1a49 Hm... Still borked. Seems hard to fix. 2011-02-18 03:14:32 +01:00
Themaister 056a590671 Hm. More progress, but still not quite right. 2011-02-18 02:16:32 +01:00
Themaister 97f2503794 Some initial work on UDP networking. Still borked. 2011-02-18 02:01:47 +01:00
Themaister 143079efbe Should fix abrupt application termination on Unix when netplay ends. 2011-02-16 18:33:45 +01:00
Themaister 7bcc173c5f Should fix some issues with Win32 netplay. 2011-02-15 17:29:03 +01:00
Themaister 0485f783b3 Updates for Win32. 2011-02-15 16:42:55 +01:00
Themaister f3c07325bd Fix Windows build. 2011-02-15 15:57:59 +01:00
Themaister e8eabb0102 Update docs. 2011-02-15 15:42:29 +01:00
Themaister b73f5e77ff Cleanups, add --frames. 2011-02-15 15:32:26 +01:00
Themaister d07712b308 Woot. looks like it works o.O 2011-02-15 15:10:03 +01:00
Themaister 89ba64d27f Borked, borked... 2011-02-14 22:37:57 +01:00
Themaister 1a6f112d8a More changes to netplay. Still quite broken, but it's not far away! 2011-02-14 20:11:46 +01:00
Themaister 70e8681487 Started to work on a look-ahead algorithm.
Still very broken, but frame-by-frame sync works at least :)
2011-02-14 16:10:53 +01:00
Themaister 17ae0f72df Do not try to poll when we've disconnected. 2011-02-13 21:19:20 +01:00
Themaister 466485e62e Lots more error messages. 2011-02-13 20:24:54 +01:00
Themaister f6ee0845f2 Add some message. 2011-02-13 19:50:45 +01:00
Themaister df1eee1a74 Redundant... 2011-02-13 19:00:27 +01:00
Themaister 46b46ba0b1 Update docs a bit. 2011-02-13 18:47:48 +01:00
Themaister a763848644 Fix some issues with message queues. 2011-02-13 18:19:37 +01:00
Themaister 9ba2d6c9fa Send SRAM to client. 2011-02-13 17:45:14 +01:00
Themaister fd1367e257 Update build for Win32 2011-02-13 16:49:18 +01:00
Themaister 85dbc05ded Some start on netplay. :) 2011-02-13 16:40:24 +01:00
Themaister 1eea7786bc Should fix possible error where game can hang for prolonged time on
exit.
2011-02-11 19:54:42 +01:00
Themaister d039bedad4 Fix up some path handling with srm/states. 2011-02-11 14:27:19 +01:00
Themaister ad6719426c Fix up autosaving a bit. 2011-02-11 13:44:31 +01:00
Themaister 719e4942ce Add autosave possibility. 2011-02-10 21:16:59 +01:00
Themaister ad9a08aea6 Updates for 0.2.2 2011-02-09 23:34:40 +01:00
Themaister 604fe709d5 Re-enable RSound for Win32 2011-02-09 23:04:11 +01:00
Themaister 81e8368099 Update RSound to use callback locks. 2011-02-08 17:50:38 +01:00
Themaister 2137ca8e3f Add a warning message if XDG_CONFIG_HOME is not found. 2011-02-07 15:27:07 +01:00
Themaister 672824539c Argsel. 2011-02-07 11:17:25 +01:00
Themaister 1915be5aea Add libsnes.hpp to the build so dynamic build doesn't require libsnes
per se.
2011-02-07 11:15:32 +01:00
Themaister bdea56370d Typo 2011-02-07 11:14:46 +01:00
Themaister e5e0c33ed6 Fix SDL include path. 2011-02-07 00:22:44 +01:00
Themaister d0c10e7523 Use new callback interface for RSound. Fix when restarting audio driver
fails.
2011-02-06 23:55:17 +01:00
Themaister 91d2cea259 Make the hermite resampling core more robust. 2011-02-06 18:38:04 +01:00
Themaister d9e2072733 Update build for win32. 2011-02-06 13:37:41 +01:00
Themaister 820b124165 Add hermite resampler core :) SRC is disabled by default. 2011-02-06 13:29:48 +01:00
Themaister caf3eb7b05 Update gitignore 2011-02-06 12:57:52 +01:00
Themaister 1b5e63f024 Add hermite resampler. 2011-02-06 12:57:12 +01:00
Themaister 93202f191c Add pausing when window loses focus :p 2011-02-05 21:45:44 +01:00
Themaister 3033fb6791 Pausing support. 2011-02-05 20:46:58 +01:00
Themaister 87589609bf Some clarification. 2011-02-05 14:14:56 +01:00
Themaister 757514242d Remove redundant flush. 2011-02-05 11:32:23 +01:00
Themaister 359d228632 Fix rewind issue. 2011-02-05 11:31:35 +01:00
Themaister 24dda40863 Remove unneeded lib. 2011-02-04 23:53:33 +01:00
Themaister 6c0eebd4bf Remove more stuff and add a comment. 2011-02-04 23:47:36 +01:00
Themaister 31db2a30bb Remove unneeded glue logic. -lSDLmain fixed it :) 2011-02-04 23:45:54 +01:00
Themaister 5e241d79c8 Wut! :D 2011-02-04 23:45:51 +01:00
Themaister 96389ef83b Some testing fixes for OSX. 2011-02-04 22:47:37 +01:00
Themaister af010712f3 Use a more proper blending technique for fonts. 2011-02-04 16:24:09 +01:00
Themaister c2896db558 Fix some sed issues that seem to appear on Mac. 2011-02-04 15:31:05 +01:00
Themaister 57eebebcdd Fix OpenAL build for OSX. 2011-02-04 14:43:14 +01:00
Themaister 9ad9cbd7de Build fixes for OSX. (PPC) 2011-02-04 13:49:53 +01:00
Themaister 1b5adde885 Hrm. 2011-02-02 20:15:00 +01:00
Themaister ca35dd79d2 Add misc bind for movie_record_toggle. 2011-02-02 19:34:44 +01:00
Themaister 491f2e3e76 Update FPS for ffmpeg slightly. 2011-02-02 17:19:13 +01:00
Themaister b6591e21cd Update makefile for win32 as well. 2011-02-02 13:42:08 +01:00
Themaister 67ed89729d Use state slots for movies as well. 2011-02-02 13:37:01 +01:00
Themaister 6033e75dc4 Added some docs 2011-02-02 12:51:17 +01:00
Themaister 5f8993cb83 Movie playback seems to work! :D 2011-02-02 12:45:56 +01:00
Themaister 2446300dd9 Some more logic hooked up. 2011-02-02 12:10:27 +01:00
Themaister b97c45df78 Initial code for movie playback. 2011-02-02 11:47:05 +01:00
Themaister 9034e82298 Add rewind granularity. (Faster, but less accurate rewind.) 2011-02-01 17:30:18 +01:00
Themaister ae395f05e1 Debork config handling. :D 2011-02-01 17:13:26 +01:00
Themaister 0c1d359091 Make clang happy :) 2011-01-31 21:23:07 +01:00
Themaister dd8a9c67c9 Do not set rewind_enable automatically to true. 2011-01-31 19:53:44 +01:00
Themaister de331bcd9e Add rewind bind to ssnes-joyconfig. 2011-01-31 18:49:50 +01:00
Themaister a81d6bc247 Fix up stuff. Alignment is done in client code. 2011-01-31 18:27:21 +01:00
Themaister 47d0b12d40 Configurable rewind stuff. 2011-01-31 18:06:57 +01:00
Themaister 849e9aa734 Do not play audio when rewinding, sound is borked even when shuffling. 2011-01-31 17:24:31 +01:00
Themaister 351e098da1 Basic rewind works! 2011-01-31 16:48:42 +01:00
Themaister c5f825eaa5 Build fixes. 2011-01-31 16:10:40 +01:00
Themaister d0a4102efa Add initial state manager code. 2011-01-31 16:06:46 +01:00
Themaister 3aa4181380 Add possibility to configure misc binds as well. 2011-01-30 20:36:45 +01:00
35 changed files with 2741 additions and 300 deletions

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
*.o
ssnes
/config.h
/config.mk
/tools/ssnes-joyconfig
+22 -3
View File
@@ -2,16 +2,25 @@ include config.mk
TARGET = ssnes tools/ssnes-joyconfig
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o netplay.o
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
HEADERS = $(wildcard */*.h) $(wildcard *.h)
LIBS =
DEFINES = -DHAVE_CONFIG_H
ifneq ($(findstring Darwin,$(shell uname -a)),)
OSX := 1
LIBS += -framework AppKit
else
OSX := 0
endif
ifeq ($(HAVE_SRC), 1)
LIBS += $(SRC_LIBS)
DEFINES += $(SRC_CFLAGS)
else
OBJ += audio/hermite.o
endif
ifeq ($(HAVE_CONFIGFILE), 1)
@@ -36,8 +45,12 @@ ifeq ($(HAVE_ROAR), 1)
endif
ifeq ($(HAVE_AL), 1)
OBJ += audio/openal.o
ifeq ($(OSX),1)
LIBS += -framework OpenAL
else
LIBS += -lopenal
endif
endif
ifeq ($(HAVE_JACK),1)
OBJ += audio/jack.o
LIBS += -ljack
@@ -50,8 +63,13 @@ endif
ifeq ($(HAVE_SDL), 1)
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
LIBS += $(SDL_LIBS) -lGL
DEFINES += $(SDL_CFLAGS)
LIBS += $(SDL_LIBS)
ifeq ($(OSX),1)
LIBS += -framework OpenGL
else
LIBS += -lGL
endif
endif
ifeq ($(HAVE_CG), 1)
@@ -121,7 +139,8 @@ install: $(TARGET)
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1
install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
install -m644 docs/{ssnes,ssnes-joyconfig}.1 $(DESTDIR)$(PREFIX)/share/man/man1
install -m644 docs/ssnes.1 $(DESTDIR)$(PREFIX)/share/man/man1
install -m644 docs/ssnes-joyconfig.1 $(DESTDIR)$(PREFIX)/share/man/man1
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig}
+6 -4
View File
@@ -1,12 +1,12 @@
TARGET = ssnes.exe
JTARGET = ssnes-joyconfig.exe
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o netplay.o
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
CC = gcc
CXX = g++
HAVE_SRC = 1
HAVE_SRC = 0
HAVE_SDL = 1
HAVE_XML = 1
HAVE_FREETYPE = 1
@@ -14,7 +14,7 @@ HAVE_XAUDIO = 1
HAVE_RSOUND = 1
libsnes ?= -lsnes
LIBS =
LIBS = -lws2_32
DEFINES = -I. -DHAVE_CONFIGFILE
LDFLAGS = -L. -static-libgcc -s
@@ -25,6 +25,8 @@ SDL_CFLAGS = -ISDL
ifeq ($(HAVE_SRC), 1)
LIBS += $(SRC_LIBS)
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
else
OBJ += audio/hermite.o
endif
ifeq ($(HAVE_SDL), 1)
@@ -97,7 +99,7 @@ clean:
rm -f tools/*.o
dist: all
zip -r ssnes-win32-0.2.1.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
zip -r ssnes-win32-0.3.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
libs:
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
+1 -1
View File
@@ -18,13 +18,13 @@ SSNES requires these libraries to build:
- [libsnes](http://byuu.org/bsnes/)
- SDL
- libsamplerate
SSNES can utilize these libraries if enabled:
- nvidia-cg-toolkit
- libxml2 (bSNES XML shaders)
- libfreetype2 (TTF font rendering on screen)
- libsamplerate
SSNES needs at least one of these audio driver libraries:
+110
View File
@@ -0,0 +1,110 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
// Hermite resampler based on bsnes' audio library.
#include "hermite.h"
#include <stdlib.h>
#define MAX_CHANS 8
struct hermite_resampler
{
float chan_data[MAX_CHANS][4];
double r_frac;
unsigned chans;
};
static inline float hermite_kernel(float mu1, float a, float b, float c, float d)
{
float mu2, mu3, m0, m1, a0, a1, a2, a3;
mu2 = mu1 * mu1;
mu3 = mu2 * mu1;
m0 = (c - a) * 0.5;
m1 = (d - b) * 0.5;
a0 = +2 * mu3 - 3 * mu2 + 1;
a1 = mu3 - 2 * mu2 + mu1;
a2 = mu3 - mu2;
a3 = -2 * mu3 + 3 * mu2;
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
hermite_resampler_t *hermite_new(unsigned channels)
{
if (channels > MAX_CHANS)
return NULL;
hermite_resampler_t *re = calloc(1, sizeof(*re));
if (!re)
return NULL;
re->chans = channels;
return re;
}
void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
{
double r_step = 1.0 / data->src_ratio;
size_t processed_out = 0;
size_t processed_in = 0;
size_t in_frames = data->input_frames;
size_t out_frames = data->output_frames;
const float *in_data = data->data_in;
float *out_data = data->data_out;
while (processed_in < in_frames && processed_out < out_frames)
{
while (re->r_frac <= 1.0 && processed_out < out_frames)
{
re->r_frac += r_step;
for (unsigned i = 0; i < re->chans; i++)
{
float res = hermite_kernel(re->r_frac,
re->chan_data[i][0], re->chan_data[i][1], re->chan_data[i][2], re->chan_data[i][3]);
*out_data++ = res;
}
processed_out++;
}
if (re->r_frac >= 1.0)
{
re->r_frac -= 1.0;
for (unsigned i = 0; i < re->chans; i++)
{
re->chan_data[i][0] = re->chan_data[i][1];
re->chan_data[i][1] = re->chan_data[i][2];
re->chan_data[i][2] = re->chan_data[i][3];
re->chan_data[i][3] = *in_data++;
}
processed_in++;
}
}
data->input_frames_used = processed_in;
data->output_frames_gen = processed_out;
}
void hermite_free(hermite_resampler_t *re)
{
free(re);
}
+50
View File
@@ -0,0 +1,50 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
// Hermite resampler based on bsnes' audio library.
// Attempts to be similiar to the libsamplerate process interface.
#ifndef __SSNES_HERMITE_H
#define __SSNES_HERMITE_H
#include <stddef.h>
#include <stdbool.h>
typedef struct hermite_resampler hermite_resampler_t;
hermite_resampler_t *hermite_new(unsigned channels);
struct hermite_data
{
const float *data_in;
float *data_out;
size_t input_frames;
size_t output_frames;
size_t input_frames_used;
size_t output_frames_gen;
bool end_of_input; // Just used to clone the SRC API.
double src_ratio;
};
void hermite_process(hermite_resampler_t *re, struct hermite_data *data);
void hermite_free(hermite_resampler_t *re);
#endif
+7
View File
@@ -17,8 +17,15 @@
#include "driver.h"
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include <time.h>
#include <string.h>
+77 -30
View File
@@ -19,15 +19,41 @@
#include "driver.h"
#include <stdlib.h>
#include <rsound.h>
#include "buffer.h"
#include <stdbool.h>
#include "SDL.h"
typedef struct rsd
{
rsound_t *rd;
int latency;
int rate;
int nonblock;
bool nonblock;
volatile bool has_error;
rsound_fifo_buffer_t *buffer;
SDL_mutex *cond_lock;
SDL_cond *cond;
} rsd_t;
static ssize_t audio_cb(void *data, size_t bytes, void *userdata)
{
rsd_t *rsd = userdata;
size_t avail = rsnd_fifo_read_avail(rsd->buffer);
size_t write_size = bytes > avail ? avail : bytes;
rsnd_fifo_read(rsd->buffer, data, write_size);
SDL_CondSignal(rsd->cond);
return write_size;
}
static void err_cb(void *userdata)
{
rsd_t *rsd = userdata;
rsd->has_error = true;
SDL_CondSignal(rsd->cond);
}
static void* __rsd_init(const char* device, int rate, int latency)
{
rsd_t *rsd = calloc(1, sizeof(rsd_t));
@@ -42,17 +68,25 @@ static void* __rsd_init(const char* device, int rate, int latency)
return NULL;
}
rsd->cond_lock = SDL_CreateMutex();
rsd->cond = SDL_CreateCond();
rsd->buffer = rsnd_fifo_new(1024 * 4);
int channels = 2;
int format = RSD_S16_NE;
rsd_set_param(rd, RSD_CHANNELS, &channels);
rsd_set_param(rd, RSD_SAMPLERATE, &rate);
rsd_set_param(rd, RSD_LATENCY, &latency);
if ( device != NULL )
rsd_set_param(rd, RSD_HOST, (void*)device);
rsd_set_param(rd, RSD_FORMAT, &format);
rsd_set_callback(rd, audio_cb, err_cb, 256, rsd);
if ( rsd_start(rd) < 0 )
{
free(rsd);
@@ -60,14 +94,7 @@ static void* __rsd_init(const char* device, int rate, int latency)
return NULL;
}
int min_latency = (rsd_delay_ms(rd) > latency) ? (rsd_delay_ms(rd) * 3 / 2) : latency;
rsd_set_param(rd, RSD_LATENCY, &min_latency);
rsd->rd = rd;
rsd->latency = min_latency;
rsd->rate = rate;
return rsd;
}
@@ -75,26 +102,46 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size)
{
rsd_t *rsd = data;
if ( (rsd_delay_ms(rsd->rd) > rsd->latency || rsd_get_avail(rsd->rd) < size) && rsd->nonblock )
return 0;
if ( size == 0 )
return 0;
rsd_delay_wait(rsd->rd);
if ( rsd_write(rsd->rd, buf, size) == 0 )
if (rsd->has_error)
return -1;
if ( rsd_delay_ms(rsd->rd) < rsd->latency/2 )
if (rsd->nonblock)
{
int ms = rsd->latency/2;
size_t size = (ms * rsd->rate * 4) / 1000;
void *temp = calloc(1, size);
rsd_write(rsd->rd, temp, size);
free(temp);
rsd_callback_lock(rsd->rd);
size_t avail = rsnd_fifo_write_avail(rsd->buffer);
size_t write_amt = avail > size ? size : avail;
rsnd_fifo_write(rsd->buffer, buf, write_amt);
rsd_callback_unlock(rsd->rd);
return write_amt;
}
else
{
size_t written = 0;
while (written < size && !rsd->has_error)
{
rsd_callback_lock(rsd->rd);
size_t avail = rsnd_fifo_write_avail(rsd->buffer);
return size;
if (avail == 0)
{
rsd_callback_unlock(rsd->rd);
if (!rsd->has_error)
{
SDL_mutexP(rsd->cond_lock);
SDL_CondWait(rsd->cond, rsd->cond_lock);
SDL_mutexV(rsd->cond_lock);
}
}
else
{
size_t write_amt = size - written > avail ? avail : size - written;
rsnd_fifo_write(rsd->buffer, (const char*)buf + written, write_amt);
rsd_callback_unlock(rsd->rd);
written += write_amt;
}
}
return written;
}
}
static bool __rsd_stop(void *data)
@@ -126,6 +173,11 @@ static void __rsd_free(void *data)
rsd_stop(rsd->rd);
rsd_free(rsd->rd);
rsnd_fifo_free(rsd->buffer);
SDL_DestroyMutex(rsd->cond_lock);
SDL_DestroyCond(rsd->cond);
free(rsd);
}
@@ -138,9 +190,4 @@ const audio_driver_t audio_rsound = {
.free = __rsd_free,
.ident = "rsound"
};
+144
View File
@@ -0,0 +1,144 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "autosave.h"
#include "SDL.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "general.h"
struct autosave
{
volatile bool quit;
SDL_mutex *lock;
SDL_mutex *cond_lock;
SDL_cond *cond;
SDL_Thread *thread;
void *buffer;
const void *snes_buffer;
const char *path;
size_t bufsize;
unsigned interval;
};
static int autosave_thread(void *data)
{
autosave_t *save = data;
while (!save->quit)
{
autosave_lock(save);
memcpy(save->buffer, save->snes_buffer, save->bufsize);
autosave_unlock(save);
// Should probably deal with this more elegantly.
FILE *file = fopen(save->path, "wb");
if (file)
{
SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path);
bool failed = false;
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
failed |= fflush(file) != 0;
failed |= fclose(file) != 0;
if (failed)
SSNES_WARN("Failed to autosave SRAM! Disk might be full.\n");
}
SDL_mutexP(save->cond_lock);
if (!save->quit)
SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000);
SDL_mutexV(save->cond_lock);
}
return 0;
}
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval)
{
autosave_t *handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
handle->bufsize = size;
handle->interval = interval;
handle->path = path;
handle->buffer = malloc(size);
handle->snes_buffer = data;
if (!handle->buffer)
{
free(handle);
return NULL;
}
handle->lock = SDL_CreateMutex();
handle->cond_lock = SDL_CreateMutex();
handle->cond = SDL_CreateCond();
handle->thread = SDL_CreateThread(autosave_thread, handle);
return handle;
}
void autosave_lock(autosave_t *handle)
{
SDL_mutexP(handle->lock);
}
void autosave_unlock(autosave_t *handle)
{
SDL_mutexV(handle->lock);
}
void autosave_free(autosave_t *handle)
{
SDL_mutexP(handle->cond_lock);
handle->quit = true;
SDL_mutexV(handle->cond_lock);
SDL_CondSignal(handle->cond);
SDL_WaitThread(handle->thread, NULL);
SDL_DestroyMutex(handle->lock);
SDL_DestroyMutex(handle->cond_lock);
SDL_DestroyCond(handle->cond);
free(handle->buffer);
free(handle);
}
void lock_autosave(void)
{
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (g_extern.autosave[i])
autosave_lock(g_extern.autosave[i]);
}
}
void unlock_autosave(void)
{
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (g_extern.autosave[i])
autosave_unlock(g_extern.autosave[i]);
}
}
+33
View File
@@ -0,0 +1,33 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_AUTOSAVE_H
#define __SSNES_AUTOSAVE_H
#include <stddef.h>
typedef struct autosave autosave_t;
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval);
void autosave_lock(autosave_t *handle);
void autosave_unlock(autosave_t *handle);
void autosave_free(autosave_t *handle);
void lock_autosave(void);
void unlock_autosave(void);
#endif
+23 -3
View File
@@ -31,15 +31,13 @@
#endif
#ifdef HAVE_SDL
#include <SDL/SDL.h>
#include "SDL.h"
#else
#error HAVE_SDL is not defined!
#endif
#ifdef HAVE_SRC
#include <samplerate.h>
#else
#error HAVE_SRC is not defined!
#endif
@@ -144,9 +142,28 @@ static const int out_latency = 64;
static const bool audio_sync = true;
// Defines the quality (and cpu reqirements) of samplerate conversion.
#ifdef HAVE_SRC
#define SAMPLERATE_QUALITY SRC_LINEAR
#endif
// Enables use of rewind. This will incur some memory footprint depending on the save state buffer.
// This rewind only works when using bSNES core atm.
static const bool rewind_enable = false;
// The buffer size for the rewind buffer. This needs to be about 15-20MB per minute. Very game dependant.
static const unsigned rewind_buffer_size = 20 << 20; // 20MiB
// How many frames to rewind at a time.
static const unsigned rewind_granularity = 1;
// Pause gameplay when gameplay loses focus.
static const bool pause_nonactive = true;
// Saves non-volatile SRAM at a regular interval. It is measured in seconds. A value of 0 disables autosave.
static const unsigned autosave_interval = 0;
// When being client over netplay, use keybinds for player 1 rather than player 2.
static const bool netplay_client_swap_input = false;
////////////////////
@@ -184,6 +201,9 @@ static const struct snes_keybind snes_keybinds_1[] = {
{ SSNES_STATE_SLOT_PLUS, SDLK_F7, NO_BTN, AXIS_NONE },
{ SSNES_AUDIO_INPUT_RATE_PLUS, SDLK_KP_PLUS, NO_BTN, AXIS_NONE },
{ SSNES_AUDIO_INPUT_RATE_MINUS, SDLK_KP_MINUS, NO_BTN, AXIS_NONE },
{ SSNES_REWIND, SDLK_r, NO_BTN, AXIS_NONE },
{ SSNES_MOVIE_RECORD_TOGGLE, SDLK_o, NO_BTN, AXIS_NONE },
{ SSNES_PAUSE_TOGGLE, SDLK_p, NO_BTN, AXIS_NONE },
{ -1 }
};
+4
View File
@@ -34,6 +34,10 @@ Use config file found in PATH as a base. Old configuration values can be overwri
\fB--output PATH, -o PATH\fR
Writes the final config to a file, rather than \fBstdout\fR. If -i and -o point to the same file, the file found in PATH will simply be updated with the new configs.
.TP
\fB--misc, -m\fR
Also configure some binds that aren't directly related to SNES emulation, such as save states, fullscreen toggling, etc.
.TP
\fB--help, -h\fR
Prints help message.
+23 -1
View File
@@ -47,7 +47,7 @@ Should this not be defined, \fBssnes\fR will look in platform specific paths to
/etc/ssnes.cfg (when installed), or ssnes.cfg in the source tarball serves as a skeleton configuration file.
.IP
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnesrc. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnes.cfg. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
.IP
Windows will look in ssnes.cfg in same folder where ssnes.exe resides.
@@ -100,9 +100,31 @@ Connects a four-way multitap into port 2 of the SNES. This allows for up to 5 pl
\fB--record PATH, -r PATH\fR
Activates video recording of gameplay into PATH. Video recording options can be configured in config file.
.TP
\fB--bsvplay PATH, -P PATH\fR
Play back a movie recorded in the .bsv format (bSNES). Cart ROM and movie file need to correspond.
It also requires to play back with the same libsnes backend that was used for recording.
.TP
\fB--verbose, -v\fR
Activates verbose logging.
.TP
\fB--host, -H\fR
Be the host of netplay. Waits until a player connects. The host will always assume player 1.
.TP
\fB--connect SERVER, -C SERVER\fR
Connect to a host of netplay. Will always assume player 2.
.TP
\fB--frames FRAMES, -F FRAMES\fR
Sync frames to use when using netplay. More frames allow for more latency, but requires more CPU power.
Set FRAMES to 0 to have perfect sync. 0 frames is only suitable for LAN. Defaults to 0.
.TP
\fB--port PORT\fR
Network port used for netplay. This defaults to 55435. This option affects both TCP and UDP.
.SH "SEE ALSO"
\fBssnes-joyconfig\fR(1)
+16 -4
View File
@@ -166,10 +166,16 @@ void init_audio(void)
else
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
#ifdef HAVE_SRC
int err;
g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
if (!g_extern.source)
g_extern.audio_data.source = src_new(g_settings.audio.src_quality, 2, &err);
if (!g_extern.audio_data.source)
g_extern.audio_active = false;
#else
g_extern.audio_data.source = hermite_new(2);
if (!g_extern.audio_data.source)
g_extern.audio_active = false;
#endif
size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ?
g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size;
@@ -192,8 +198,14 @@ void uninit_audio(void)
if ( driver.audio_data && driver.audio )
driver.audio->free(driver.audio_data);
if ( g_extern.source )
src_delete(g_extern.source);
if ( g_extern.audio_data.source )
{
#ifdef HAVE_SRC
src_delete(g_extern.audio_data.source);
#else
hermite_free(g_extern.audio_data.source);
#endif
}
free(g_extern.audio_data.data); g_extern.audio_data.data = NULL;
free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL;
+10 -6
View File
@@ -36,6 +36,9 @@ enum
SSNES_STATE_SLOT_MINUS,
SSNES_AUDIO_INPUT_RATE_PLUS,
SSNES_AUDIO_INPUT_RATE_MINUS,
SSNES_REWIND,
SSNES_MOVIE_RECORD_TOGGLE,
SSNES_PAUSE_TOGGLE,
};
struct snes_keybind
@@ -69,15 +72,15 @@ typedef struct audio_driver
const char *ident;
} audio_driver_t;
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
#define AXIS_NEG(x) (((uint32_t)(x) << 16) | 0xFFFFU)
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
#define AXIS_NONE (0xFFFFFFFFU)
#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
#define AXIS_POS_GET(x) (x & 0xFFFF)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
#define AXIS_NEG_GET(x) (((uint32_t)(x) >> 16) & 0xFFFFU)
#define AXIS_POS_GET(x) ((uint32_t)(x) & 0xFFFFU)
#define AXIS_NONE (0xFFFFFFFFU)
#define NO_BTN 0xFFFF // I hope no joypad will ever have this many buttons ... ;)
#define NO_BTN ((uint16_t)0xFFFFU) // I hope no joypad will ever have this many buttons ... ;)
#define HAT_UP_MASK (1 << 15)
#define HAT_DOWN_MASK (1 << 14)
@@ -107,6 +110,7 @@ typedef struct video_driver
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
// Is the window still active?
bool (*alive)(void *data);
bool (*focus)(void *data); // Does the window have focus?
void (*free)(void* data);
const char *ident;
} video_driver_t;
+3
View File
@@ -22,6 +22,7 @@
#include <libsnes.hpp>
#include <string.h>
#include "dynamic.h"
#include "movie.h"
#ifdef _WIN32
#include <io.h>
@@ -103,6 +104,8 @@ static ssize_t read_rom_file(FILE* file, void** buf)
*buf = rom_buf;
ret = length;
}
g_extern.cart_crc = crc32_calculate(*buf, ret);
return ret;
}
+53 -2
View File
@@ -24,6 +24,10 @@
#include <stdio.h>
#include "record/ffemu.h"
#include "message.h"
#include "rewind.h"
#include "movie.h"
#include "autosave.h"
#include "netplay.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -31,12 +35,16 @@
#ifdef HAVE_SRC
#include <samplerate.h>
#else
#include "audio/hermite.h"
#endif
#define MAX_PLAYERS 5
#define MAX_BINDS 22 // Needs to be increased every time there are new binds added.
#define MAX_BINDS 25 // Needs to be increased every time there are new binds added.
#define SSNES_NO_JOYPAD 0xFFFF
// All config related settings go here.
struct settings
{
struct
@@ -81,9 +89,17 @@ struct settings
struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS];
float axis_threshold;
unsigned joypad_map[MAX_PLAYERS];
bool netplay_client_swap_input;
} input;
char libsnes[256];
bool rewind_enable;
unsigned rewind_buffer_size;
unsigned rewind_granularity;
bool pause_nonactive;
unsigned autosave_interval;
};
enum ssnes_game_type
@@ -95,10 +111,10 @@ enum ssnes_game_type
SSNES_CART_SUFAMI,
};
// All run-time- / command line flag-related globals go here.
struct global
{
bool verbose;
SRC_STATE *source;
bool audio_active;
bool video_active;
@@ -110,10 +126,13 @@ struct global
FILE *rom_file;
enum ssnes_game_type game_type;
uint32_t cart_crc;
char gb_rom_path[256];
char bsx_rom_path[256];
char sufami_rom_path[2][256];
bool has_set_save_path;
bool has_set_state_path;
#ifdef HAVE_CONFIGFILE
char config_path[256];
@@ -131,6 +150,12 @@ struct global
struct
{
#ifdef HAVE_SRC
SRC_STATE *source;
#else
hermite_resampler_t *source;
#endif
float *data;
size_t data_ptr;
size_t chunk_size;
@@ -145,6 +170,32 @@ struct global
msg_queue_t *msg_queue;
// Rewind support.
state_manager_t *state_manager;
void *state_buf;
bool frame_is_reverse;
// Movie record support
bsv_movie_t *bsv_movie;
char bsv_movie_path[256];
bool bsv_movie_end;
bool bsv_movie_playback;
// Pausing support
bool is_paused;
// Autosave support.
autosave_t *autosave[2];
// Netplay.
netplay_t *netplay;
char netplay_server[256];
bool netplay_enable;
bool netplay_is_client;
unsigned netplay_sync_frames;
uint16_t netplay_port;
// FFmpeg record.
#ifdef HAVE_FFMPEG
ffemu_t *rec;
char record_path[256];
+23 -14
View File
@@ -29,18 +29,21 @@
#include "config.h"
#endif
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#endif
#define NO_SDL_GLEXT
#include "SDL.h"
#include "SDL_opengl.h"
#include "input/ssnes_sdl_input.h"
#define GL_GLEXT_PROTOTYPES
#include <GL/glext.h>
#ifndef _WIN32
#include <GL/glx.h>
#endif
#ifdef HAVE_CG
#include "shader_cg.h"
#endif
@@ -234,7 +237,7 @@ static void gl_render_msg(gl_t *gl, const char *msg)
// Need blending.
// Using fixed function pipeline here since we cannot guarantee presence of shaders (would be kinda overkill anyways).
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
struct font_output_list out;
font_renderer_msg(gl->font, msg, &out);
@@ -259,7 +262,7 @@ static void gl_render_msg(gl_t *gl, const char *msg)
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(head->pitch));
glPixelStorei(GL_UNPACK_ROW_LENGTH, head->pitch);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, head->width, head->height, 0, GL_LUMINANCE,
0, GL_INTENSITY8, head->width, head->height, 0, GL_LUMINANCE,
GL_UNSIGNED_BYTE, head->output);
head = head->next;
@@ -398,7 +401,6 @@ static bool gl_frame(void *data, const uint16_t* frame, unsigned width, unsigned
gl_render_msg(gl, msg);
show_fps();
glFlush();
SDL_GL_SwapBuffers();
return true;
@@ -424,12 +426,12 @@ static void gl_set_nonblock_state(void *data, bool state)
SSNES_LOG("GL VSync => %s\n", state ? "off" : "on");
#ifdef _WIN32
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) wglGetProcAddress("wglSwapIntervalEXT");
if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) SDL_GL_GetProcAddress("wglSwapIntervalEXT");
if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1);
#else
static int (*glx_swap_interval)(int) = NULL;
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI");
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA");
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalSGI");
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalMESA");
if (glx_swap_interval) glx_swap_interval(state ? 0 : 1);
#endif
}
@@ -485,7 +487,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
glEnable(GL_TEXTURE_2D);
glDisable(GL_DITHER);
glDisable(GL_DEPTH_TEST);
glColor3f(1, 1, 1);
glColor4f(1, 1, 1, 1);
glClearColor(0, 0, 0, 0);
SDL_WM_SetCaption("SSNES", NULL);
@@ -551,11 +553,18 @@ static bool gl_alive(void *data)
return !gl->quitting;
}
static bool gl_focus(void *data)
{
(void)data;
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
}
const video_driver_t video_gl = {
.init = gl_init,
.frame = gl_frame,
.alive = gl_alive,
.set_nonblock_state = gl_set_nonblock_state,
.focus = gl_focus,
.free = gl_free,
.ident = "gl"
};
+10 -4
View File
@@ -23,17 +23,23 @@
#include <string.h>
#include "general.h"
#define NO_SDL_GLEXT
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#endif
#define NO_SDL_GLEXT
#include "SDL.h"
#include "SDL_opengl.h"
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/glext.h>
#include "gl_common.h"
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
+1 -1
View File
@@ -322,7 +322,7 @@ static void sdl_input_poll(void *data)
*sdl->should_resize = true;
}
break;
default:
break;
}
Executable
+133
View File
@@ -0,0 +1,133 @@
#ifndef LIBSNES_HPP
#define LIBSNES_HPP
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SNES_PORT_1 0
#define SNES_PORT_2 1
#define SNES_DEVICE_NONE 0
#define SNES_DEVICE_JOYPAD 1
#define SNES_DEVICE_MULTITAP 2
#define SNES_DEVICE_MOUSE 3
#define SNES_DEVICE_SUPER_SCOPE 4
#define SNES_DEVICE_JUSTIFIER 5
#define SNES_DEVICE_JUSTIFIERS 6
#define SNES_DEVICE_ID_JOYPAD_B 0
#define SNES_DEVICE_ID_JOYPAD_Y 1
#define SNES_DEVICE_ID_JOYPAD_SELECT 2
#define SNES_DEVICE_ID_JOYPAD_START 3
#define SNES_DEVICE_ID_JOYPAD_UP 4
#define SNES_DEVICE_ID_JOYPAD_DOWN 5
#define SNES_DEVICE_ID_JOYPAD_LEFT 6
#define SNES_DEVICE_ID_JOYPAD_RIGHT 7
#define SNES_DEVICE_ID_JOYPAD_A 8
#define SNES_DEVICE_ID_JOYPAD_X 9
#define SNES_DEVICE_ID_JOYPAD_L 10
#define SNES_DEVICE_ID_JOYPAD_R 11
#define SNES_DEVICE_ID_MOUSE_X 0
#define SNES_DEVICE_ID_MOUSE_Y 1
#define SNES_DEVICE_ID_MOUSE_LEFT 2
#define SNES_DEVICE_ID_MOUSE_RIGHT 3
#define SNES_DEVICE_ID_SUPER_SCOPE_X 0
#define SNES_DEVICE_ID_SUPER_SCOPE_Y 1
#define SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER 2
#define SNES_DEVICE_ID_SUPER_SCOPE_CURSOR 3
#define SNES_DEVICE_ID_SUPER_SCOPE_TURBO 4
#define SNES_DEVICE_ID_SUPER_SCOPE_PAUSE 5
#define SNES_DEVICE_ID_JUSTIFIER_X 0
#define SNES_DEVICE_ID_JUSTIFIER_Y 1
#define SNES_DEVICE_ID_JUSTIFIER_TRIGGER 2
#define SNES_DEVICE_ID_JUSTIFIER_START 3
#define SNES_REGION_NTSC 0
#define SNES_REGION_PAL 1
#define SNES_MEMORY_CARTRIDGE_RAM 0
#define SNES_MEMORY_CARTRIDGE_RTC 1
#define SNES_MEMORY_BSX_RAM 2
#define SNES_MEMORY_BSX_PRAM 3
#define SNES_MEMORY_SUFAMI_TURBO_A_RAM 4
#define SNES_MEMORY_SUFAMI_TURBO_B_RAM 5
#define SNES_MEMORY_GAME_BOY_RAM 6
#define SNES_MEMORY_GAME_BOY_RTC 7
#define SNES_MEMORY_WRAM 100
#define SNES_MEMORY_APURAM 101
#define SNES_MEMORY_VRAM 102
#define SNES_MEMORY_OAM 103
#define SNES_MEMORY_CGRAM 104
typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height);
typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right);
typedef void (*snes_input_poll_t)(void);
typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id);
unsigned snes_library_revision_major(void);
unsigned snes_library_revision_minor(void);
void snes_set_video_refresh(snes_video_refresh_t);
void snes_set_audio_sample(snes_audio_sample_t);
void snes_set_input_poll(snes_input_poll_t);
void snes_set_input_state(snes_input_state_t);
void snes_set_controller_port_device(bool port, unsigned device);
void snes_set_cartridge_basename(const char *basename);
void snes_init(void);
void snes_term(void);
void snes_power(void);
void snes_reset(void);
void snes_run(void);
unsigned snes_serialize_size(void);
bool snes_serialize(uint8_t *data, unsigned size);
bool snes_unserialize(const uint8_t *data, unsigned size);
void snes_cheat_reset(void);
void snes_cheat_set(unsigned index, bool enabled, const char *code);
bool snes_load_cartridge_normal(
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size
);
bool snes_load_cartridge_bsx_slotted(
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size
);
bool snes_load_cartridge_bsx(
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size
);
bool snes_load_cartridge_sufami_turbo(
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
const char *sta_xml, const uint8_t *sta_data, unsigned sta_size,
const char *stb_xml, const uint8_t *stb_data, unsigned stb_size
);
bool snes_load_cartridge_super_game_boy(
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
const char *dmg_xml, const uint8_t *dmg_data, unsigned dmg_size
);
void snes_unload_cartridge(void);
bool snes_get_region(void);
uint8_t* snes_get_memory_data(unsigned id);
unsigned snes_get_memory_size(unsigned id);
#ifdef __cplusplus
}
#endif
#endif
+248
View File
@@ -0,0 +1,248 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "movie.h"
#include <stdio.h>
#include <stdlib.h>
#include "general.h"
#include "dynamic.h"
// CRC32 implementation taken from BSNES source :)
static const uint32_t crc32_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
static inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input)
{
return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff];
}
uint32_t crc32_calculate(const uint8_t *data, unsigned length)
{
uint32_t crc32 = ~0;
for(unsigned i = 0; i < length; i++)
crc32 = crc32_adjust(crc32, data[i]);
return ~crc32;
}
struct bsv_movie
{
FILE *file;
uint8_t *state;
};
#define BSV_MAGIC 0x42535631
#define MAGIC_INDEX 0
#define SERIALIZER_INDEX 1
#define CRC_INDEX 2
#define STATE_SIZE_INDEX 3
static inline uint8_t is_little_endian(void)
{
union
{
uint16_t u16;
uint8_t u8[2];
} u;
u.u16 = 1;
return u.u8[0];
}
// Convert to big-endian if needed
static inline uint32_t swap_if_big32(uint32_t val)
{
if (is_little_endian()) // Little-endian
return val;
else
return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
}
static inline uint16_t swap_if_big16(uint16_t val)
{
if (is_little_endian())
return val;
else
return (val >> 8) | (val << 8);
}
static bool init_playback(bsv_movie_t *handle, const char *path)
{
handle->file = fopen(path, "rb");
if (!handle->file)
{
SSNES_ERR("Couldn't open BSV file \"%s\" for playback.\n", path);
return false;
}
uint32_t header[4] = {0};
if (fread(header, sizeof(uint32_t), 4, handle->file) != 4)
{
SSNES_ERR("Couldn't read movie header!\n");
return false;
}
if (swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
{
SSNES_ERR("Movie file is not a valid BSV1 file!\n");
return false;
}
if (swap_if_big32(header[CRC_INDEX]) != g_extern.cart_crc)
{
SSNES_ERR("Cart CRC32s differ! Cannot play back!\n");
return false;
}
uint32_t state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
handle->state = malloc(state_size);
if (!handle->state)
return false;
if (fread(handle->state, 1, state_size, handle->file) != state_size)
{
SSNES_ERR("Couldn't read state from movie.\n");
return false;
}
if (psnes_serialize_size() != state_size)
{
SSNES_ERR("Movie format seems to have a different serializer version. Cannot continue.\n");
exit(1);
}
// Unserialize to start playback.
psnes_unserialize(handle->state, state_size);
return true;
}
static bool init_record(bsv_movie_t *handle, const char *path)
{
handle->file = fopen(path, "wb");
if (!handle->file)
{
SSNES_ERR("Couldn't open BSV \"%s\" for recording.\n", path);
return false;
}
uint32_t header[4] = {0};
header[MAGIC_INDEX] = swap_if_big32(BSV_MAGIC);
//header[SERIALIZER_INDEX] = 0; // TODO: Fix this.
header[CRC_INDEX] = swap_if_big32(g_extern.cart_crc);
uint32_t state_size = psnes_serialize_size();
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
fwrite(header, 4, sizeof(uint32_t), handle->file);
handle->state = malloc(state_size);
if (!handle->state)
return false;
psnes_serialize(handle->state, state_size);
fwrite(handle->state, 1, state_size, handle->file);
return true;
}
void bsv_movie_free(bsv_movie_t *handle)
{
if (handle)
{
if (handle->file)
fclose(handle->file);
free(handle->state);
free(handle);
}
}
bool bsv_movie_get_input(bsv_movie_t *handle, int16_t *input)
{
if (fread(input, sizeof(int16_t), 1, handle->file) != 1)
return false;
*input = swap_if_big16(*input);
return true;
}
void bsv_movie_set_input(bsv_movie_t *handle, int16_t input)
{
fwrite(&input, sizeof(int16_t), 1, handle->file);
}
bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type)
{
bsv_movie_t *handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
if (type == SSNES_MOVIE_PLAYBACK)
{
if (!init_playback(handle, path))
goto error;
}
else if (!init_record(handle, path))
goto error;
return handle;
error:
bsv_movie_free(handle);
return NULL;
}
+48
View File
@@ -0,0 +1,48 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_MOVIE_H
#define __SSNES_MOVIE_H
#include <stdint.h>
#include <stdbool.h>
typedef struct bsv_movie bsv_movie_t;
enum ssnes_movie_type
{
SSNES_MOVIE_PLAYBACK,
SSNES_MOVIE_RECORD,
};
bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type);
// Playback
bool bsv_movie_get_input(bsv_movie_t *handle, int16_t *input);
// Recording
void bsv_movie_set_input(bsv_movie_t *handle, int16_t input);
void bsv_movie_free(bsv_movie_t *handle);
uint32_t crc32_calculate(const uint8_t *data, unsigned length);
#endif
+685
View File
@@ -0,0 +1,685 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "netplay.h"
#include "general.h"
#include "dynamic.h"
#include "autosave.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <assert.h>
#ifdef _WIN32
#define _WIN32_WINNT 0x0501
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
// Woohoo, Winsock has headers from the STONE AGE! :D
#define close(x) closesocket(x)
#define CONST_CAST (const char*)
#define NONCONST_CAST (char*)
#else
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#define CONST_CAST
#define NONCONST_CAST
#endif
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
struct delta_frame
{
uint8_t *state;
uint16_t real_input_state;
uint16_t simulated_input_state;
bool is_simulated;
uint16_t self_state;
bool used_real;
};
#define UDP_FRAME_PACKETS 8
struct netplay
{
struct snes_callbacks cbs;
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
int udp_fd; // UDP connection for game state updates.
unsigned port; // Which port is governed by netplay?
bool has_connection;
struct delta_frame *buffer;
size_t buffer_size;
size_t self_ptr; // Ptr where we are now.
size_t other_ptr; // Points to the last reliable state that self ever had.
size_t read_ptr; // Ptr to where we are reading. Generally, other_ptr <= read_ptr <= self_ptr.
size_t tmp_ptr; // A temporary pointer used on replay.
size_t state_size;
size_t is_replay; // Are we replaying old frames?
bool can_poll; // We don't want to poll several times on a frame.
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
uint32_t frame_count;
uint32_t read_frame_count;
struct addrinfo *addr;
struct sockaddr_storage their_addr;
bool has_client_addr;
};
void input_poll_net(void)
{
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
{
netplay_callbacks(g_extern.netplay)->poll_cb();
netplay_poll(g_extern.netplay);
}
}
void video_frame_net(const uint16_t *data, unsigned width, unsigned height)
{
if (!netplay_should_skip(g_extern.netplay))
netplay_callbacks(g_extern.netplay)->frame_cb(data, width, height);
}
void audio_sample_net(uint16_t left, uint16_t right)
{
if (!netplay_should_skip(g_extern.netplay))
netplay_callbacks(g_extern.netplay)->sample_cb(left, right);
}
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
{
if (netplay_is_alive(g_extern.netplay))
return netplay_input_state(g_extern.netplay, port, device, index, id);
else
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
}
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
{
struct addrinfo hints, *res = NULL;
memset(&hints, 0, sizeof(hints));
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
hints.ai_family = AF_INET;
#else
hints.ai_family = AF_UNSPEC;
#endif
hints.ai_socktype = SOCK_STREAM;
if (!server)
hints.ai_flags = AI_PASSIVE;
char port_buf[16];
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
if (getaddrinfo(server, port_buf, &hints, &res) < 0)
return false;
if (!res)
return false;
handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (handle->fd < 0)
{
SSNES_ERR("Failed to init socket...\n");
if (res)
freeaddrinfo(res);
return false;
}
if (server)
{
if (connect(handle->fd, res->ai_addr, res->ai_addrlen) < 0)
{
SSNES_ERR("Failed to connect to server.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
}
else
{
int yes = 1;
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0)
{
SSNES_ERR("Failed to bind socket.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
int new_fd = accept(handle->fd, NULL, NULL);
if (new_fd < 0)
{
SSNES_ERR("Failed to accept socket.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
close(handle->fd);
handle->fd = new_fd;
}
freeaddrinfo(res);
return true;
}
static bool init_udp_socket(netplay_t *handle, const char *server, uint16_t port)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
hints.ai_family = AF_INET;
#else
hints.ai_family = AF_UNSPEC;
#endif
hints.ai_socktype = SOCK_DGRAM;
if (!server)
hints.ai_flags = AI_PASSIVE;
char port_buf[16];
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
if (getaddrinfo(server, port_buf, &hints, &handle->addr) < 0)
return false;
if (!handle->addr)
return false;
handle->udp_fd = socket(handle->addr->ai_family, handle->addr->ai_socktype, handle->addr->ai_protocol);
if (handle->udp_fd < 0)
{
SSNES_ERR("Failed to init socket...\n");
return false;
}
if (!server)
{
// Note sure if we have to do this for UDP, but hey :)
int yes = 1;
setsockopt(handle->udp_fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
if (bind(handle->udp_fd, handle->addr->ai_addr, handle->addr->ai_addrlen) < 0)
{
SSNES_ERR("Failed to bind socket.\n");
close(handle->udp_fd);
}
freeaddrinfo(handle->addr);
handle->addr = NULL;
}
return true;
}
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
WSACleanup();
return false;
}
#else
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
#endif
if (!init_tcp_socket(handle, server, port))
return false;
if (!init_udp_socket(handle, server, port))
return false;
return true;
}
bool netplay_can_poll(netplay_t *handle)
{
return handle->can_poll;
}
static bool send_info(netplay_t *handle)
{
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
return false;
// Get SRAM data from Player 1 :)
uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
while (sram_size > 0)
{
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
if (ret <= 0)
{
SSNES_ERR("Failed to receive SRAM data from host.\n");
return false;
}
sram += ret;
sram_size -= ret;
}
return true;
}
static bool get_info(netplay_t *handle)
{
uint32_t header[3];
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
{
SSNES_ERR("Failed to receive header from client.\n");
return false;
}
if (g_extern.cart_crc != ntohl(header[0]))
{
SSNES_ERR("Cart CRC32s differ! Cannot use different games!\n");
return false;
}
if (psnes_serialize_size() != ntohl(header[1]))
{
SSNES_ERR("Serialization sizes differ, make sure you're using exact same libsnes implementations!\n");
return false;
}
if (psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM) != ntohl(header[2]))
{
SSNES_ERR("Cartridge SRAM sizes do not correspond!\n");
return false;
}
// Send SRAM data to our Player 2 :)
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
while (sram_size > 0)
{
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
if (ret <= 0)
{
SSNES_ERR("Failed to send SRAM data to client.\n");
return false;
}
sram += ret;
sram_size -= ret;
}
return true;
}
static void init_buffers(netplay_t *handle)
{
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
handle->state_size = psnes_serialize_size();
for (unsigned i = 0; i < handle->buffer_size; i++)
{
handle->buffer[i].state = malloc(handle->state_size);
handle->buffer[i].is_simulated = true;
}
}
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
{
netplay_t *handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
handle->cbs = *cb;
handle->port = server ? 0 : 1;
if (!init_socket(handle, server, port))
{
free(handle);
return NULL;
}
if (server)
{
if (!send_info(handle))
{
close(handle->fd);
free(handle);
return NULL;
}
}
else
{
if (!get_info(handle))
{
close(handle->fd);
free(handle);
return NULL;
}
}
handle->buffer_size = frames + 1;
init_buffers(handle);
handle->has_connection = true;
return handle;
}
bool netplay_is_alive(netplay_t *handle)
{
return handle->has_connection;
}
static bool send_chunk(netplay_t *handle)
{
const struct sockaddr *addr = NULL;
if (handle->addr)
addr = handle->addr->ai_addr;
else if (handle->has_client_addr)
addr = (const struct sockaddr*)&handle->their_addr;
if (addr)
{
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
}
return true;
}
#define MAX_RETRIES 16
static int poll_input(netplay_t *handle, bool block)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(handle->udp_fd, &fds);
struct timeval tv = {
.tv_sec = 0,
.tv_usec = block ? 500000 : 0
};
int i = 0;
do
{
if (select(handle->udp_fd + 1, &fds, NULL, NULL, &tv) < 0)
return -1;
if (FD_ISSET(handle->udp_fd, &fds))
return 1;
if (block && !send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
} while (i++ < MAX_RETRIES && block);
if (block)
return -1;
return 0;
}
// Grab our own input state and send this over the network.
static bool get_self_input_state(netplay_t *handle)
{
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
uint32_t state = 0;
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
{
snes_input_state_t cb = handle->cbs.state_cb;
for (int i = 0; i <= 11; i++)
{
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
state |= tmp ? 1 << i : 0;
}
}
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
if (!send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
ptr->self_state = state;
handle->self_ptr = NEXT_PTR(handle->self_ptr);
return true;
}
// TODO: Somewhat better prediction. :P
static void simulate_input(netplay_t *handle)
{
size_t ptr = PREV_PTR(handle->self_ptr);
size_t prev = PREV_PTR(handle->read_ptr);
handle->buffer[ptr].simulated_input_state = handle->buffer[prev].real_input_state;
handle->buffer[ptr].is_simulated = true;
handle->buffer[ptr].used_real = false;
}
static void parse_packet(netplay_t *handle, uint32_t *buffer, unsigned size)
{
for (unsigned i = 0; i < size * 2; i++)
buffer[i] = ntohl(buffer[i]);
for (unsigned i = 0; i < size && handle->read_frame_count <= handle->frame_count; i++)
{
uint32_t frame = buffer[2 * i];
uint32_t state = buffer[2 * i + 1];
if (frame == handle->read_frame_count)
{
handle->buffer[handle->read_ptr].is_simulated = false;
handle->buffer[handle->read_ptr].real_input_state = state;
handle->read_ptr = NEXT_PTR(handle->read_ptr);
handle->read_frame_count++;
}
}
}
static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
{
socklen_t addrlen = sizeof(handle->their_addr);
if (recvfrom(handle->udp_fd, NONCONST_CAST buffer, size, 0, (struct sockaddr*)&handle->their_addr, &addrlen) != size)
return false;
handle->has_client_addr = true;
return true;
}
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
bool netplay_poll(netplay_t *handle)
{
if (!handle->has_connection)
return false;
handle->can_poll = false;
if (!get_self_input_state(handle))
return false;
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
if (handle->frame_count == 0)
{
handle->buffer[0].used_real = true;
handle->buffer[0].is_simulated = false;
handle->buffer[0].real_input_state = 0;
handle->read_ptr = NEXT_PTR(handle->read_ptr);
handle->read_frame_count++;
return true;
}
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
if (handle->buffer_size > 1)
{
assert(handle->other_ptr != handle->self_ptr);
assert(handle->read_ptr != handle->self_ptr);
}
assert(handle->other_ptr == handle->read_ptr);
// We might have reached the end of the buffer, where we simply have to block.
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
if (res == -1)
{
handle->has_connection = false;
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
return false;
}
if (res == 1)
{
uint32_t first_read = handle->read_frame_count;
do
{
uint32_t buffer[UDP_FRAME_PACKETS * 2];
if (!receive_data(handle, buffer, sizeof(buffer)))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
} while ((handle->read_frame_count <= handle->frame_count) &&
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
(first_read == handle->read_frame_count)) == 1);
}
else
{
// Cannot allow this. Should not happen though.
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
return false;
}
}
if (handle->read_ptr != handle->self_ptr)
simulate_input(handle);
else
handle->buffer[PREV_PTR(handle->self_ptr)].used_real = true;
return true;
}
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
{
uint16_t input_state = 0;
size_t ptr = 0;
if (handle->is_replay)
ptr = handle->tmp_ptr;
else
ptr = PREV_PTR(handle->self_ptr);
if ((port ? 1 : 0) == handle->port)
{
if (handle->buffer[ptr].is_simulated)
input_state = handle->buffer[ptr].simulated_input_state;
else
input_state = handle->buffer[ptr].real_input_state;
}
else
input_state = handle->buffer[ptr].self_state;
return ((1 << id) & input_state) ? 1 : 0;
}
void netplay_free(netplay_t *handle)
{
close(handle->fd);
close(handle->udp_fd);
for (unsigned i = 0; i < handle->buffer_size; i++)
free(handle->buffer[i].state);
free(handle->buffer);
if (handle->addr)
freeaddrinfo(handle->addr);
free(handle);
}
const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
{
return &handle->cbs;
}
bool netplay_should_skip(netplay_t *handle)
{
return handle->is_replay && handle->has_connection;
}
void netplay_pre_frame(netplay_t *handle)
{
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
handle->can_poll = true;
}
// Here we check if we have new input and replay from recorded input.
void netplay_post_frame(netplay_t *handle)
{
handle->frame_count++;
// Nothing to do...
if (handle->other_ptr == handle->read_ptr)
return;
// Skip ahead if we predicted correctly. Skip until our simulation failed.
while (handle->other_ptr != handle->read_ptr)
{
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
break;
handle->other_ptr = NEXT_PTR(handle->other_ptr);
}
if (handle->other_ptr != handle->read_ptr)
{
// Replay frames
handle->is_replay = true;
handle->tmp_ptr = handle->other_ptr;
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
while (handle->tmp_ptr != handle->self_ptr)
{
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
lock_autosave();
psnes_run();
unlock_autosave();
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
}
handle->other_ptr = handle->read_ptr;
handle->is_replay = false;
}
}
+61
View File
@@ -0,0 +1,61 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_NETPLAY_H
#define __SSNES_NETPLAY_H
#include <stdint.h>
#include <stdbool.h>
#include <libsnes.hpp>
void input_poll_net(void);
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id);
void video_frame_net(const uint16_t *data, unsigned width, unsigned height);
void audio_sample_net(uint16_t left, uint16_t right);
typedef struct netplay netplay_t;
struct snes_callbacks
{
snes_video_refresh_t frame_cb;
snes_audio_sample_t sample_cb;
snes_input_poll_t poll_cb;
snes_input_state_t state_cb;
};
// Creates a new netplay handle. A NULL host means we're hosting (player 1). :)
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb);
void netplay_free(netplay_t *handle);
// Call this before running snes_run()
void netplay_pre_frame(netplay_t *handle);
// Call this after running snes_run()
void netplay_post_frame(netplay_t *handle);
// Checks if input port/index is controlled by netplay or not.
bool netplay_is_alive(netplay_t *handle);
bool netplay_poll(netplay_t *handle);
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id);
// If we're fast-forward replaying to resync, check if we should actually show frame.
bool netplay_should_skip(netplay_t *handle);
bool netplay_can_poll(netplay_t *handle);
const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
#endif
+8 -3
View File
@@ -17,8 +17,14 @@ fi
check_lib ALSA -lasound snd_pcm_open
check_header OSS sys/soundcard.h
check_lib AL -lopenal alcOpenDevice
check_lib RSOUND -lrsound rsd_init
if [ "$OS" = "Darwin" ]; then
check_lib AL "-framework OpenAL" alcOpenDevice
else
check_lib AL -lopenal alcOpenDevice
fi
check_pkgconf RSOUND rsound 1.1
check_lib ROAR -lroar roar_vs_new
check_lib JACK -ljack jack_client_open
check_pkgconf PULSE libpulse
@@ -40,7 +46,6 @@ if [ $HAVE_FFMPEG != no ]; then
fi
check_pkgconf SRC samplerate
check_critical SRC "Cannot find libsamplerate."
check_lib DYNAMIC -ldl dlopen
+2 -1
View File
@@ -1,7 +1,7 @@
. qb/qb.params.sh
PACKAGE_NAME=ssnes
PACKAGE_VERSION=0.2.1
PACKAGE_VERSION=0.3
# Adds a command line opt to ./configure --help
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
@@ -11,6 +11,7 @@ add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
add_command_line_enable FFMPEG "Enable FFmpeg recording support" no
add_command_line_enable FILTER "Enable CPU filter support" no
add_command_line_enable SRC "Enable libsamplerate support" no
add_command_line_enable CONFIGFILE "Disable support for config file" yes
add_command_line_enable CG "Enable Cg shader support" auto
add_command_line_enable XML "Enable bSNES-style XML shader support" auto
+2 -2
View File
@@ -6,7 +6,7 @@ TEMP_EXE=.tmp
echo -n "Checking operating system ... "
OS="Win32" # whatever ;D
unamestr="`uname -o`"
unamestr="`uname -a`"
if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then
OS="Linux"
elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then
@@ -50,7 +50,7 @@ if [ "$USE_LANG_CXX" = "yes" ]; then
CXX=`which g++ c++ 2> /dev/null | grep ^/ | head -n 1`
fi
if [ -z $CXX ]; then
echo "Could not find C compiler in path. Exiting ..."
echo "Could not find C++ compiler in path. Exiting ..."
exit 1
fi
+1 -1
View File
@@ -95,7 +95,7 @@ parse_input()
case "$1" in
--prefix=*)
prefix="`echo $1 | sed -e 's|^--prefix=\(\S\S*\)$|\1|' -e 's|\(\S\S*\)/$|\1|'`"
prefix="`echo $1 | sed -e 's|^--prefix=||' -e 's|^\(.*\)/$|\1|'`"
if [ "$prefix" != "$1" ]; then
PREFIX="$prefix"
+162
View File
@@ -0,0 +1,162 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "rewind.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
//#include <stdio.h>
struct state_manager
{
uint64_t *buffer;
size_t buf_size;
uint32_t *tmp_state;
uint32_t *scratch_buf;
size_t top_ptr;
size_t bottom_ptr;
size_t state_size;
};
state_manager_t *state_manager_new(size_t state_size, size_t buffer_size, void *init_buffer)
{
if (buffer_size <= state_size * 2 + 1) // Need a sufficient buffer size.
return NULL;
state_manager_t *state = calloc(1, sizeof(*state));
if (!state)
return NULL;
assert(state_size % 4 == 0); // We need 4-byte aligned state_size to avoid having to enforce this with unneeded memcpy's!
state->top_ptr = 1;
state->state_size = (state_size + 3) >> 2; // Multiple of 4.
state->buf_size = (buffer_size + 7) >> 3; // Multiple of 8.
if (!(state->buffer = calloc(1, state->buf_size * sizeof(uint64_t))))
goto error;
if (!(state->tmp_state = calloc(1, state->state_size * sizeof(uint32_t))))
goto error;
memcpy(state->tmp_state, init_buffer, state_size);
return state;
error:
if (state)
{
free(state->buffer);
free(state->tmp_state);
free(state);
}
return NULL;
}
void state_manager_free(state_manager_t *state)
{
free(state->buffer);
free(state->tmp_state);
free(state);
}
bool state_manager_pop(state_manager_t *state, void **data)
{
*data = state->tmp_state;
if (state->top_ptr == 0)
state->top_ptr = state->buf_size - 1;
else
state->top_ptr--;
if (state->top_ptr == state->bottom_ptr) // Our stack is completely empty... :v
{
state->top_ptr = (state->top_ptr + 1) % state->buf_size;
return false;
}
while (state->buffer[state->top_ptr])
{
// Apply the xor patch.
uint32_t addr = state->buffer[state->top_ptr] >> 32;
uint32_t xor = state->buffer[state->top_ptr] & 0xFFFFFFFFU;
state->tmp_state[addr] ^= xor;
if (state->top_ptr == 0)
state->top_ptr = state->buf_size - 1;
else
state->top_ptr--;
}
if (state->top_ptr == state->bottom_ptr) // Our stack is completely empty... :v
{
state->top_ptr = (state->top_ptr + 1) % state->buf_size;
return true;
}
return true;
}
static void reassign_bottom(state_manager_t *state)
{
state->bottom_ptr = (state->top_ptr + 1) % state->buf_size;
while (state->buffer[state->bottom_ptr]) // Skip ahead until we find the first 0 (boundary for state delta).
state->bottom_ptr = (state->bottom_ptr + 1) % state->buf_size;
}
static void generate_delta(state_manager_t *state, const void *data)
{
//unsigned patch_size = 0;
bool crossed = false;
const uint32_t *old_state = state->tmp_state;
const uint32_t *new_state = data;
state->buffer[state->top_ptr++] = 0; // For each separate delta, we have a 0 value sentinel in between.
if (state->top_ptr == state->bottom_ptr) // Check if top_ptr and bottom_ptr crossed eachother, which means we need to delete old cruft.
crossed = true;
for (uint64_t i = 0; i < state->state_size; i++)
{
uint64_t xor = old_state[i] ^ new_state[i];
// If the data differs (xor != 0), we push that xor on the stack with index and xor. This can be reversed by reapplying the xor.
// This, if states don't really differ much, we'll save lots of space :) Hopefully this will work really well with save states.
if (xor)
{
//patch_size++;
state->buffer[state->top_ptr] = (i << 32) | xor;
state->top_ptr = (state->top_ptr + 1) % state->buf_size;
if (state->top_ptr == state->bottom_ptr)
crossed = true;
}
}
if (crossed)
reassign_bottom(state);
//fprintf(stderr, "DELTA SIZE: %u, ORIG SIZE: %u\n", (unsigned)patch_size << 3, (unsigned)state->state_size << 2);
}
bool state_manager_push(state_manager_t *state, const void *data)
{
generate_delta(state, data);
memcpy(state->tmp_state, data, state->state_size * sizeof(uint32_t));
return true;
}
+33
View File
@@ -0,0 +1,33 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_REWIND_H
#define __SSNES_REWIND_H
#include <stddef.h>
#include <stdbool.h>
typedef struct state_manager state_manager_t;
// Always pass in at least 4-byte aligned data and sizes!
state_manager_t *state_manager_new(size_t state_size, size_t buffer_size, void *init_buffer);
void state_manager_free(state_manager_t *state);
bool state_manager_pop(state_manager_t *state, void **data);
bool state_manager_push(state_manager_t *state, const void *data);
#endif
+142 -140
View File
@@ -122,7 +122,16 @@ static void set_defaults(void)
strncpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device));
g_settings.audio.latency = out_latency;
g_settings.audio.sync = audio_sync;
#ifdef HAVE_SRC
g_settings.audio.src_quality = SAMPLERATE_QUALITY;
#endif
g_settings.rewind_enable = rewind_enable;
g_settings.rewind_buffer_size = rewind_buffer_size;
g_settings.rewind_granularity = rewind_granularity;
g_settings.pause_nonactive = pause_nonactive;
g_settings.autosave_interval = autosave_interval;
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
@@ -136,8 +145,10 @@ static void set_defaults(void)
memcpy(g_settings.input.binds[4], snes_keybinds_5, sizeof(snes_keybinds_5));
g_settings.input.axis_threshold = AXIS_THRESHOLD;
g_settings.input.netplay_client_swap_input = netplay_client_swap_input;
for (int i = 0; i < MAX_PLAYERS; i++)
g_settings.input.joypad_map[i] = i;
}
#ifdef HAVE_CONFIGFILE
@@ -154,6 +165,80 @@ void parse_config(void)
#endif
}
static config_file_t *open_default_config_file(void)
{
config_file_t *conf = NULL;
#ifdef _WIN32
// Just do something for now.
conf = config_file_new("ssnes.cfg");
if (!conf)
{
const char *appdata = getenv("APPDATA");
if (appdata)
{
char conf_path[strlen(appdata) + strlen("/ssnes.cfg ")];
strcpy(conf_path, appdata);
strcat(conf_path, "/ssnes.cfg");
conf = config_file_new(conf_path);
}
}
#elif defined(__APPLE__)
const char *home = getenv("HOME");
if (home)
{
char conf_path[strlen(home) + strlen("/.ssnes.cfg ")];
strcpy(conf_path, home);
strcat(conf_path, "/.ssnes.cfg");
conf = config_file_new(conf_path);
}
if (!conf)
conf = config_file_new("/etc/ssnes.cfg");
#else
const char *xdg = getenv("XDG_CONFIG_HOME");
if (!xdg)
SSNES_WARN("XDG_CONFIG_HOME is not defined. Will look for config in $HOME/.ssnesrc ...\n");
const char *home = getenv("HOME");
if (xdg)
{
char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")];
strcpy(conf_path, xdg);
strcat(conf_path, "/ssnes/ssnes.cfg");
conf = config_file_new(conf_path);
}
else if (home)
{
char conf_path[strlen(home) + strlen("/.ssnes.cfg ")];
strcpy(conf_path, home);
strcat(conf_path, "/.ssnes.cfg");
conf = config_file_new(conf_path);
}
// Try this as a last chance...
if (!conf)
conf = config_file_new("/etc/ssnes.cfg");
#endif
return conf;
}
// Macros to ease config getting.
#define CONFIG_GET_BOOL(var, key) if (config_get_bool(conf, key, &tmp_bool)) \
g_settings.var = tmp_bool
#define CONFIG_GET_INT(var, key) if (config_get_int(conf, key, &tmp_int)) \
g_settings.var = tmp_int
#define CONFIG_GET_DOUBLE(var, key) if (config_get_double(conf, key, &tmp_double)) \
g_settings.var = tmp_double
#define CONFIG_GET_STRING(var, key) do { \
if (config_get_string(conf, key, &tmp_str)) \
{ \
strncpy(g_settings.var, tmp_str, sizeof(g_settings.var) - 1); \
free(tmp_str); \
} \
} while(0)
#ifdef HAVE_CONFIGFILE
static void parse_config_file(void)
{
@@ -169,32 +254,7 @@ static void parse_config_file(void)
}
}
else
{
#ifdef _WIN32
// Just do something for now.
conf = config_file_new("ssnes.cfg");
#else
const char *xdg = getenv("XDG_CONFIG_HOME");
const char *home = getenv("HOME");
if (xdg)
{
char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")];
strcpy(conf_path, xdg);
strcat(conf_path, "/ssnes/ssnes.cfg");
conf = config_file_new(conf_path);
}
else if (home)
{
char conf_path[strlen(home) + strlen("/.ssnesrc ")];
strcpy(conf_path, home);
strcat(conf_path, "/.ssnesrc");
conf = config_file_new(conf_path);
}
// Try this as a last chance...
if (!conf)
conf = config_file_new("/etc/ssnes.cfg");
#endif
}
conf = open_default_config_file();
if (conf == NULL)
return;
@@ -207,63 +267,26 @@ static void parse_config_file(void)
bool tmp_bool;
char *tmp_str;
// Video settings.
if (config_get_double(conf, "video_xscale", &tmp_double))
g_settings.video.xscale = tmp_double;
CONFIG_GET_DOUBLE(video.xscale, "video_xscale");
CONFIG_GET_DOUBLE(video.yscale, "video_yscale");
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
CONFIG_GET_BOOL(video.vsync, "video_vsync");
CONFIG_GET_BOOL(video.smooth, "video_smooth");
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
CONFIG_GET_DOUBLE(video.aspect_ratio, "video_aspect_ratio");
if (config_get_double(conf, "video_yscale", &tmp_double))
g_settings.video.yscale = tmp_double;
if (config_get_int(conf, "video_fullscreen_x", &tmp_int))
g_settings.video.fullscreen_x = tmp_int;
if (config_get_int(conf, "video_fullscreen_y", &tmp_int))
g_settings.video.fullscreen_y = tmp_int;
if (config_get_bool(conf, "video_fullscreen", &tmp_bool))
g_settings.video.fullscreen = tmp_bool;
if (config_get_bool(conf, "video_vsync", &tmp_bool))
g_settings.video.vsync = tmp_bool;
if (config_get_bool(conf, "video_smooth", &tmp_bool))
g_settings.video.smooth = tmp_bool;
if (config_get_bool(conf, "video_force_aspect", &tmp_bool))
g_settings.video.force_aspect = tmp_bool;
if (config_get_double(conf, "video_aspect_ratio", &tmp_double))
g_settings.video.aspect_ratio = tmp_double;
if (config_get_string(conf, "video_cg_shader", &tmp_str))
{
strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1);
free(tmp_str);
}
if (config_get_string(conf, "video_bsnes_shader", &tmp_str))
{
strncpy(g_settings.video.bsnes_shader_path, tmp_str, sizeof(g_settings.video.bsnes_shader_path) - 1);
free(tmp_str);
}
CONFIG_GET_STRING(video.cg_shader_path, "video_cg_shader");
CONFIG_GET_STRING(video.bsnes_shader_path, "video_bsnes_shader");
#ifdef HAVE_FREETYPE
if (config_get_string(conf, "video_font_path", &tmp_str))
{
strncpy(g_settings.video.font_path, tmp_str, sizeof(g_settings.video.font_path) - 1);
free(tmp_str);
}
if (config_get_int(conf, "video_font_size", &tmp_int))
g_settings.video.font_size = tmp_int;
if (config_get_double(conf, "video_message_pos_x", &tmp_double))
g_settings.video.msg_pos_x = tmp_double;
if (config_get_double(conf, "video_message_pos_y", &tmp_double))
g_settings.video.msg_pos_y = tmp_double;
CONFIG_GET_STRING(video.font_path, "video_font_path");
CONFIG_GET_INT(video.font_size, "video_font_size");
CONFIG_GET_DOUBLE(video.msg_pos_x, "video_message_pos_x");
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
#endif
#ifdef HAVE_FILTER
if (config_get_string(conf, "video_filter", &tmp_str))
{
@@ -297,51 +320,25 @@ static void parse_config_file(void)
}
#endif
// Input Settings.
if (config_get_double(conf, "input_axis_threshold", &tmp_double))
g_settings.input.axis_threshold = tmp_double;
// Joypad mapping.
if (config_get_int(conf, "input_player1_joypad_index", &tmp_int))
g_settings.input.joypad_map[0] = tmp_int;
if (config_get_int(conf, "input_player2_joypad_index", &tmp_int))
g_settings.input.joypad_map[1] = tmp_int;
if (config_get_int(conf, "input_player3_joypad_index", &tmp_int))
g_settings.input.joypad_map[2] = tmp_int;
if (config_get_int(conf, "input_player4_joypad_index", &tmp_int))
g_settings.input.joypad_map[3] = tmp_int;
if (config_get_int(conf, "input_player5_joypad_index", &tmp_int))
g_settings.input.joypad_map[4] = tmp_int;
CONFIG_GET_DOUBLE(input.axis_threshold, "input_axis_threshold");
CONFIG_GET_BOOL(input.netplay_client_swap_input, "netplay_client_swap_input");
CONFIG_GET_INT(input.joypad_map[0], "input_player1_joypad_index");
CONFIG_GET_INT(input.joypad_map[1], "input_player2_joypad_index");
CONFIG_GET_INT(input.joypad_map[2], "input_player3_joypad_index");
CONFIG_GET_INT(input.joypad_map[3], "input_player4_joypad_index");
CONFIG_GET_INT(input.joypad_map[4], "input_player5_joypad_index");
// Audio settings.
if (config_get_bool(conf, "audio_enable", &tmp_bool))
g_settings.audio.enable = tmp_bool;
if (config_get_int(conf, "audio_out_rate", &tmp_int))
g_settings.audio.out_rate = tmp_int;
if (config_get_double(conf, "audio_in_rate", &tmp_double))
g_settings.audio.in_rate = tmp_double;
if (config_get_double(conf, "audio_rate_step", &tmp_double))
g_settings.audio.rate_step = tmp_double;
if (config_get_string(conf, "audio_device", &tmp_str))
{
strncpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device) - 1);
free(tmp_str);
}
if (config_get_int(conf, "audio_latency", &tmp_int))
g_settings.audio.latency = tmp_int;
if (config_get_bool(conf, "audio_sync", &tmp_bool))
g_settings.audio.sync = tmp_bool;
CONFIG_GET_BOOL(audio.enable, "audio_enable");
CONFIG_GET_INT(audio.out_rate, "audio_out_rate");
CONFIG_GET_DOUBLE(audio.in_rate, "audio_in_rate");
CONFIG_GET_DOUBLE(audio.rate_step, "audio_rate_step");
CONFIG_GET_STRING(audio.device, "audio_device");
CONFIG_GET_INT(audio.latency, "audio_latency");
CONFIG_GET_BOOL(audio.sync, "audio_sync");
#ifdef HAVE_SRC
if (config_get_int(conf, "audio_src_quality", &tmp_int))
{
int quals[] = { SRC_ZERO_ORDER_HOLD, SRC_LINEAR, SRC_SINC_FASTEST,
@@ -350,27 +347,22 @@ static void parse_config_file(void)
if (tmp_int > 0 && tmp_int < 6)
g_settings.audio.src_quality = quals[tmp_int];
}
#endif
if (config_get_string(conf, "video_driver", &tmp_str))
{
strncpy(g_settings.video.driver, tmp_str, sizeof(g_settings.video.driver) - 1);
free(tmp_str);
}
if (config_get_string(conf, "audio_driver", &tmp_str))
{
strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1);
free(tmp_str);
}
if (config_get_string(conf, "input_driver", &tmp_str))
{
strncpy(g_settings.input.driver, tmp_str, sizeof(g_settings.input.driver) - 1);
free(tmp_str);
}
if (config_get_string(conf, "libsnes_path", &tmp_str))
{
strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1);
free(tmp_str);
}
CONFIG_GET_STRING(video.driver, "video_driver");
CONFIG_GET_STRING(audio.driver, "audio_driver");
CONFIG_GET_STRING(input.driver, "input_driver");
CONFIG_GET_STRING(libsnes, "libsnes_path");
CONFIG_GET_BOOL(rewind_enable, "rewind_enable");
if (config_get_int(conf, "rewind_buffer_size", &tmp_int))
g_settings.rewind_buffer_size = tmp_int * 1000000;
CONFIG_GET_INT(rewind_granularity, "rewind_granularity");
CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive");
CONFIG_GET_INT(autosave_interval, "autosave_interval");
read_keybinds(conf);
@@ -411,6 +403,8 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
DECLARE_BIND(rate_step_up, SSNES_AUDIO_INPUT_RATE_PLUS)
DECLARE_BIND(rate_step_down, SSNES_AUDIO_INPUT_RATE_MINUS)
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
},
{
DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -434,6 +428,8 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
DECLARE_BIND(rate_step_up, SSNES_AUDIO_INPUT_RATE_PLUS)
DECLARE_BIND(rate_step_down, SSNES_AUDIO_INPUT_RATE_MINUS)
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
},
{
DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -457,6 +453,8 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
DECLARE_BIND(rate_step_up, SSNES_AUDIO_INPUT_RATE_PLUS)
DECLARE_BIND(rate_step_down, SSNES_AUDIO_INPUT_RATE_MINUS)
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
},
{
DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -480,6 +478,8 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
DECLARE_BIND(rate_step_up, SSNES_AUDIO_INPUT_RATE_PLUS)
DECLARE_BIND(rate_step_down, SSNES_AUDIO_INPUT_RATE_MINUS)
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
},
{
DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -503,6 +503,8 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
DECLARE_BIND(rate_step_up, SSNES_AUDIO_INPUT_RATE_PLUS)
DECLARE_BIND(rate_step_down, SSNES_AUDIO_INPUT_RATE_MINUS)
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
},
};
+516 -68
View File
@@ -22,17 +22,34 @@
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <time.h>
#include "driver.h"
#include "file.h"
#include "hqflt/filters.h"
#include "general.h"
#include "dynamic.h"
#include "record/ffemu.h"
#include "rewind.h"
#include "movie.h"
#include "netplay.h"
#include <assert.h>
#ifdef HAVE_SRC
#include <samplerate.h>
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifdef __APPLE__
#include "SDL.h"
// OSX seems to really need -lSDLmain,
// so we include SDL.h here so it can hack our main.
// I had issues including this in Win32 for some reason. :)
#endif
struct global g_extern = {
.video_active = true,
.audio_active = true,
@@ -64,10 +81,10 @@ static void set_fast_forward_button(bool new_button_state)
static inline void process_frame (uint16_t * restrict out, const uint16_t * restrict in, unsigned width, unsigned height)
{
int pitch = 1024;
if ( height == 448 || height == 478 )
if (height == 448 || height == 478)
pitch = 512;
for ( int y = 0; y < height; y++ )
for (int y = 0; y < height; y++)
{
const uint16_t *src = in + y * pitch;
uint16_t *dst = out + y * width;
@@ -163,22 +180,33 @@ static void audio_sample(uint16_t left, uint16_t right)
}
#endif
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;
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;
if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size)
{
SRC_DATA src_data;
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;
src_data.end_of_input = 0;
src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
if (g_extern.frame_is_reverse) // Disable fucked up audio when rewinding...
memset(g_extern.audio_data.data, 0, g_extern.audio_data.chunk_size * sizeof(float));
src_process(g_extern.source, &src_data);
#ifdef HAVE_SRC
SRC_DATA src_data = {
#else
struct hermite_data src_data = {
#endif
.data_in = g_extern.audio_data.data,
.data_out = g_extern.audio_data.outsamples,
.input_frames = g_extern.audio_data.chunk_size / 2,
.output_frames = g_extern.audio_data.chunk_size * 8,
.end_of_input = 0,
.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate,
};
#ifdef HAVE_SRC
src_process(g_extern.audio_data.source, &src_data);
#else
hermite_process(g_extern.audio_data.source, &src_data);
#endif
if (g_extern.audio_data.use_float)
{
if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0)
@@ -189,7 +217,11 @@ static void audio_sample(uint16_t left, uint16_t right)
}
else
{
src_float_to_short_array(g_extern.audio_data.outsamples, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * 2);
for (unsigned i = 0; i < src_data.output_frames_gen * 2; i++)
{
int32_t val = g_extern.audio_data.outsamples[i] * 0x8000;
g_extern.audio_data.conv_outsamples[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
}
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0)
{
@@ -209,11 +241,27 @@ static void input_poll(void)
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
{
if (g_extern.bsv_movie && g_extern.bsv_movie_playback)
{
int16_t ret;
if (bsv_movie_get_input(g_extern.bsv_movie, &ret))
return ret;
else
{
g_extern.bsv_movie_end = true;
return 0;
}
}
const struct snes_keybind *binds[MAX_PLAYERS];
for (int i = 0; i < MAX_PLAYERS; i++)
binds[i] = g_settings.input.binds[i];
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
int16_t res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
if (g_extern.bsv_movie && !g_extern.bsv_movie_playback)
bsv_movie_set_input(g_extern.bsv_movie, res);
return res;
}
static void fill_pathname(char *out_path, char *in_path, const char *replace)
@@ -228,20 +276,16 @@ static void fill_pathname(char *out_path, char *in_path, const char *replace)
strcat(out_path, replace);
}
#ifdef HAVE_FFMPEG
#define FFMPEG_HELP_QUARK " | -r/--record "
#else
#define FFMPEG_HELP_QUARK
#endif
#ifdef _WIN32
#define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe"
#elif defined(__APPLE__)
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $HOME/.ssnes.cfg"
#else
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"
#endif
#ifdef _WIN32
#define PACKAGE_VERSION "0.2.1"
#define PACKAGE_VERSION "0.3"
#endif
#include "config.features.h"
@@ -273,10 +317,10 @@ static void print_features(void)
static void print_help(void)
{
puts("=============================================================");
puts("===================================================================");
puts("ssnes: Simple Super Nintendo Emulator (libsnes) -- v" PACKAGE_VERSION " --");
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 "]");
puts("===================================================================");
puts("Usage: ssnes [rom file] [options...]");
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.");
@@ -294,6 +338,11 @@ static void print_help(void)
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.");
puts("\t-P/--bsvplay: Playback a BSV movie file.");
puts("\t-H/--host: Host netplay as player 1.");
puts("\t-C/--connect: Connect to netplay as player 2.");
puts("\t--port: Port used to netplay. Default is 55435.");
puts("\t-F/--frames: Sync frames when using netplay.");
#ifdef HAVE_FFMPEG
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
@@ -303,6 +352,16 @@ static void print_help(void)
print_features();
}
static void set_basename(const char *path)
{
char tmp[strlen(path) + 1];
strcpy(tmp, path);
char *dst = strrchr(tmp, '.');
if (dst)
*dst = '\0';
strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1);
}
static void parse_input(int argc, char *argv[])
{
if (argc < 2)
@@ -311,6 +370,8 @@ static void parse_input(int argc, char *argv[])
exit(1);
}
int val = 0;
struct option opts[] = {
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
@@ -332,6 +393,11 @@ static void parse_input(int argc, char *argv[])
{ "multitap", 0, NULL, '4' },
{ "sufamiA", 1, NULL, 'Y' },
{ "sufamiB", 1, NULL, 'Z' },
{ "bsvplay", 1, NULL, 'P' },
{ "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' },
{ "frames", 1, NULL, 'F' },
{ "port", 1, &val, 'p' },
{ NULL, 0, NULL, 0 }
};
@@ -349,9 +415,10 @@ static void parse_input(int argc, char *argv[])
#define CONFIG_FILE_ARG
#endif
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:F:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
for(;;)
{
val = 0;
int c = getopt_long(argc, argv, optstring, opts, &option_index);
int port;
@@ -378,6 +445,7 @@ static void parse_input(int argc, char *argv[])
case 's':
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1);
g_extern.has_set_save_path = true;
break;
case 'g':
@@ -407,6 +475,7 @@ static void parse_input(int argc, char *argv[])
case 'S':
strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1);
g_extern.has_set_state_path = true;
break;
case 'v':
@@ -441,6 +510,37 @@ static void parse_input(int argc, char *argv[])
break;
#endif
case 'P':
strncpy(g_extern.bsv_movie_path, optarg, sizeof(g_extern.bsv_movie_path) - 1);
g_extern.bsv_movie_playback = true;
break;
case 'H':
g_extern.netplay_enable = true;
break;
case 'C':
g_extern.netplay_enable = true;
strncpy(g_extern.netplay_server, optarg, sizeof(g_extern.netplay_server) - 1);
break;
case 'F':
g_extern.netplay_sync_frames = strtol(optarg, NULL, 0);
if (g_extern.netplay_sync_frames > 32)
g_extern.netplay_sync_frames = 32;
break;
case 0:
switch (val)
{
case 'p':
g_extern.netplay_port = strtol(optarg, NULL, 0);
break;
default:
break;
}
break;
case '?':
print_help();
exit(1);
@@ -453,13 +553,8 @@ static void parse_input(int argc, char *argv[])
if (optind < argc)
{
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);
set_basename(argv[optind]);
SSNES_LOG("Opening file: \"%s\"\n", argv[optind]);
g_extern.rom_file = fopen(argv[optind], "rb");
if (g_extern.rom_file == NULL)
@@ -468,18 +563,10 @@ static void parse_input(int argc, char *argv[])
exit(1);
}
// strl* would be nice :D
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);
}
if (!g_extern.has_set_save_path)
fill_pathname(g_extern.savefile_name_srm, g_extern.basename, ".srm");
if (!g_extern.has_set_state_path)
fill_pathname(g_extern.savestate_name, g_extern.basename, ".state");
}
else if (strlen(g_extern.savefile_name_srm) == 0)
{
@@ -536,11 +623,15 @@ static inline void load_save_files(void)
switch (g_extern.game_type)
{
case SSNES_CART_NORMAL:
case SSNES_CART_SGB:
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_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:
load_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
@@ -562,11 +653,15 @@ static inline void save_files(void)
switch (g_extern.game_type)
{
case SSNES_CART_NORMAL:
case SSNES_CART_SGB:
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;
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:
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
@@ -590,8 +685,8 @@ 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_rational ntsc_fps = {60000, 1000};
struct ffemu_rational pal_fps = {50000, 1000};
struct ffemu_params params = {
.vcodec = FFEMU_VIDEO_H264,
.acodec = FFEMU_AUDIO_VORBIS,
@@ -599,7 +694,7 @@ static void init_recording(void)
.out_width = 512,
.out_height = 448,
.channels = 2,
.samplerate = 32040,
.samplerate = 32000,
.filename = g_extern.record_path,
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
.aspect_ratio = 4.0/3
@@ -636,6 +731,148 @@ static void deinit_msg_queue(void)
msg_queue_free(g_extern.msg_queue);
}
static void init_rewind(void)
{
if (g_settings.rewind_enable)
{
size_t serial_size = psnes_serialize_size();
g_extern.state_buf = malloc((serial_size + 3) & ~3); // Make sure we allocate at least 4-byte multiple.
psnes_serialize(g_extern.state_buf, serial_size);
SSNES_LOG("Initing rewind buffer with size: %u MB\n", (unsigned)g_settings.rewind_buffer_size / 1000000);
g_extern.state_manager = state_manager_new((serial_size + 3) & ~3, g_settings.rewind_buffer_size, g_extern.state_buf);
if (!g_extern.state_manager)
SSNES_WARN("Failed to init rewind buffer. Rewinding will be disabled!\n");
}
}
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);
}
static void init_movie(void)
{
if (g_extern.bsv_movie_playback)
{
g_extern.bsv_movie = bsv_movie_init(g_extern.bsv_movie_path, SSNES_MOVIE_PLAYBACK);
if (!g_extern.bsv_movie)
{
SSNES_ERR("Failed to load movie file: \"%s\"!\n", g_extern.bsv_movie_path);
exit(1);
}
msg_queue_push(g_extern.msg_queue, "Starting movie playback!", 2, 180);
SSNES_LOG("Starting movie playback!\n");
}
}
static void deinit_movie(void)
{
if (g_extern.bsv_movie)
bsv_movie_free(g_extern.bsv_movie);
}
#define SSNES_DEFAULT_PORT 55435
static void init_netplay(void)
{
if (g_extern.netplay_enable)
{
struct snes_callbacks cbs = {
.frame_cb = video_frame,
.sample_cb = audio_sample,
.poll_cb = input_poll,
.state_cb = input_state
};
if (strlen(g_extern.netplay_server) > 0)
{
SSNES_LOG("Connecting to netplay host...\n");
g_extern.netplay_is_client = true;
}
else
SSNES_LOG("Waiting for client...\n");
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, g_extern.netplay_sync_frames, &cbs);
if (!g_extern.netplay)
{
g_extern.netplay_is_client = false;
SSNES_WARN("Failed to init netplay...\n");
}
}
}
static void deinit_netplay(void)
{
if (g_extern.netplay)
netplay_free(g_extern.netplay);
}
static void init_autosave(void)
{
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;
}
if (g_settings.autosave_interval > 0)
{
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");
}
}
}
}
static void deinit_autosave(void)
{
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]);
}
}
static void fill_pathnames(void)
{
switch (g_extern.game_type)
@@ -643,6 +880,10 @@ static void fill_pathnames(void)
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
// BSX PSRM
if (!g_extern.has_set_save_path)
fill_pathname(g_extern.savefile_name_srm, g_extern.bsx_rom_path, ".srm");
if (!g_extern.has_set_state_path)
fill_pathname(g_extern.savestate_name, g_extern.bsx_rom_path, ".state");
fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm");
break;
@@ -653,10 +894,21 @@ static void fill_pathnames(void)
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm");
break;
case SSNES_CART_SGB:
if (!g_extern.has_set_save_path)
fill_pathname(g_extern.savefile_name_srm, g_extern.gb_rom_path, ".srm");
if (!g_extern.has_set_state_path)
fill_pathname(g_extern.savestate_name, g_extern.gb_rom_path, ".state");
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
break;
default:
// Infer .rtc save path from save ram path.
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
}
if (!g_extern.bsv_movie_playback)
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "");
}
// Save or load state here.
@@ -667,7 +919,12 @@ static void check_savestates(void)
if (should_savestate && !old_should_savestate)
{
char save_path[strlen(g_extern.savestate_name) * 2];
snprintf(save_path, sizeof(save_path), g_extern.state_slot > 0 ? "%s%u" : "%s", g_extern.savestate_name, g_extern.state_slot);
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);
if(!save_state(save_path))
{
msg_queue_clear(g_extern.msg_queue);
@@ -688,7 +945,11 @@ static void check_savestates(void)
if (!should_savestate && should_loadstate && !old_should_loadstate)
{
char load_path[strlen(g_extern.savestate_name) * 2];
snprintf(load_path, sizeof(load_path), g_extern.state_slot ? "%s%u" : "%s", g_extern.savestate_name, g_extern.state_slot);
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);
if(!load_state(load_path))
{
@@ -727,7 +988,7 @@ static void check_stateslots(void)
g_extern.state_slot++;
msg_queue_clear(g_extern.msg_queue);
char msg[256];
snprintf(msg, sizeof(msg), "Save state slot: %u", g_extern.state_slot);
snprintf(msg, sizeof(msg), "Save state/movie slot: %u", g_extern.state_slot);
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
@@ -741,7 +1002,7 @@ static void check_stateslots(void)
g_extern.state_slot--;
msg_queue_clear(g_extern.msg_queue);
char msg[256];
snprintf(msg, sizeof(msg), "Save state slot: %u", g_extern.state_slot);
snprintf(msg, sizeof(msg), "Save state/movie slot: %u", g_extern.state_slot);
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
@@ -773,12 +1034,153 @@ static void check_input_rate(void)
}
}
static void check_rewind(void)
{
g_extern.frame_is_reverse = false;
if (!g_extern.state_manager)
return;
if (driver.input->key_pressed(driver.input_data, SSNES_REWIND))
{
msg_queue_clear(g_extern.msg_queue);
void *buf;
if (state_manager_pop(g_extern.state_manager, &buf))
{
msg_queue_push(g_extern.msg_queue, "Rewinding!", 0, 30);
psnes_unserialize(buf, psnes_serialize_size());
g_extern.frame_is_reverse = true;
}
else
msg_queue_push(g_extern.msg_queue, "Reached end of rewind buffer!", 0, 30);
}
else
{
static unsigned cnt = 0;
cnt = (cnt + 1) % (g_settings.rewind_granularity ? g_settings.rewind_granularity : 1); // Avoid possible SIGFPE.
if (cnt == 0)
{
psnes_serialize(g_extern.state_buf, psnes_serialize_size());
state_manager_push(g_extern.state_manager, g_extern.state_buf);
}
}
}
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)
{
if (g_extern.bsv_movie)
{
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, "Stopping movie record!", 2, 180);
SSNES_LOG("Stopping movie record!\n");
bsv_movie_free(g_extern.bsv_movie);
g_extern.bsv_movie = NULL;
}
else
{
char path[512];
if (g_extern.state_slot > 0)
snprintf(path, sizeof(path), "%s%d.bsv", g_extern.bsv_movie_path, g_extern.state_slot);
else
snprintf(path, sizeof(path), "%s.bsv", g_extern.bsv_movie_path);
g_extern.bsv_movie = bsv_movie_init(path, SSNES_MOVIE_RECORD);
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, g_extern.bsv_movie ? "Starting movie record!" : "Failed to start movie record!", 1, 180);
if (g_extern.bsv_movie)
SSNES_LOG("Starting movie record!\n");
else
SSNES_ERR("Failed to start movie record!\n");
}
}
old_button = new_button;
}
static void check_pause(void)
{
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_PAUSE_TOGGLE);
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)
{
g_extern.is_paused = !g_extern.is_paused;
if (g_extern.is_paused)
{
SSNES_LOG("Paused!\n");
if (driver.audio_data)
driver.audio->stop(driver.audio_data);
}
else
{
SSNES_LOG("Unpaused!\n");
if (driver.audio_data)
{
if (!driver.audio->start(driver.audio_data))
{
SSNES_ERR("Failed to resume audio driver! Will continue without audio!\n");
g_extern.audio_active = false;
}
}
}
}
else if (focus && !old_focus)
{
SSNES_LOG("Unpaused!\n");
g_extern.is_paused = false;
if (driver.audio_data)
{
if (!driver.audio->start(driver.audio_data))
{
SSNES_ERR("Failed to resume audio driver! Will continue without audio!\n");
g_extern.audio_active = false;
}
}
}
else if (!focus && old_focus)
{
SSNES_LOG("Paused!\n");
g_extern.is_paused = true;
if (driver.audio_data)
driver.audio->stop(driver.audio_data);
}
old_focus = focus;
old_state = new_state;
}
static void do_state_checks(void)
{
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
if (!g_extern.netplay)
{
check_pause();
if (g_extern.is_paused)
return;
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
if (!g_extern.bsv_movie)
{
check_stateslots();
check_savestates();
check_rewind();
}
if (!g_extern.bsv_movie_playback)
check_movie_record();
}
check_stateslots();
check_savestates();
check_fullscreen();
check_input_rate();
}
@@ -801,44 +1203,90 @@ int main(int argc, char *argv[])
if (!init_rom_file(g_extern.game_type))
goto error;
init_msg_queue();
init_movie();
if (!g_extern.bsv_movie)
{
load_save_files();
init_rewind();
}
init_netplay();
init_drivers();
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_poll(input_poll);
psnes_set_input_state(input_state);
psnes_set_video_refresh(g_extern.netplay ? video_frame_net : video_frame);
psnes_set_audio_sample(g_extern.netplay ? audio_sample_net : audio_sample);
psnes_set_input_poll(g_extern.netplay ? input_poll_net : input_poll);
psnes_set_input_state(g_extern.netplay ? input_state_net : input_state);
init_controllers();
load_save_files();
#ifdef HAVE_FFMPEG
init_recording();
#endif
init_msg_queue();
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
init_autosave();
// Main loop
for(;;)
{
// Time to drop?
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
!driver.video->alive(driver.video_data))
!driver.video->alive(driver.video_data) || g_extern.bsv_movie_end)
break;
// Checks for stuff like fullscreen, save states, etc.
do_state_checks();
// Run libsnes for one frame.
psnes_run();
if (!g_extern.is_paused)
{
lock_autosave();
if (g_extern.netplay)
netplay_pre_frame(g_extern.netplay);
psnes_run();
if (g_extern.netplay)
netplay_post_frame(g_extern.netplay);
unlock_autosave();
}
else
{
input_poll();
#ifdef _WIN32
Sleep(10);
#else
struct timespec tv = {
.tv_sec = 0,
.tv_nsec = 10000000
};
nanosleep(&tv, NULL);
#endif
}
}
deinit_msg_queue();
deinit_netplay();
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
deinit_autosave();
#ifdef HAVE_FFMPEG
deinit_recording();
#endif
save_files();
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
{
deinit_rewind();
save_files();
}
deinit_movie();
deinit_msg_queue();
psnes_unload_cartridge();
psnes_term();
+31
View File
@@ -38,6 +38,7 @@
# video_filter = ntsc
# Path to a TTF font used for rendering messages. This path must be defined to enable fonts.
# Do note that the _full_ path of the font is necessary!
# video_font_path =
# Size of the TTF font rendered.
@@ -190,3 +191,33 @@
# Decrease/increase input sample rate on the fly. Amount to decrease/increase is defined by audio_rate_step.
# input_rate_step_up = kp_plus
# input_rate_step_down = kp_minus
# Enable rewinding. This will take a performance hit when playing, so it is disabled by default.
# Do note that rewinding will only work properly when using bSNES libsnes core atm.
# rewind_enable = false
# Rewinding buffer size in megabytes. Bigger rewinding buffer means you can rewind longer.
# The buffer should be approx. 20MB per minute of buffer time.
# rewind_buffer_size = 20
# Rewind granularity. When rewinding defined number of frames, you can rewind several frames at a time, increasing the rewinding speed.
# rewind_granularity = 1
# Hold button down to rewind. Rewinding must be enabled.
# input_rewind = r
# Toggle between recording and not.
# input_movie_record_toggle = o
# Toggle between paused and non-paused state
# input_pause_toggle = p
# Pause gameplay when window focus is lost.
# pause_nonactive = true
# Autosaves the non-volatile SRAM at a regular interval. This is disabled by default unless set otherwise.
# The interval is measured in seconds. A value of 0 disables autosave.
# autosave_interval =
# When being client over netplay, use keybinds for player 1.
# netplay_client_swap_input = false
+51 -12
View File
@@ -27,6 +27,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdbool.h>
#include "general.h"
@@ -34,6 +35,7 @@ static int g_player = 1;
static int g_joypad = 0;
static char *g_in_path = NULL;
static char *g_out_path = NULL;
static bool g_use_misc = false;
static void print_help(void)
{
@@ -47,6 +49,7 @@ static void print_help(void)
puts("-i/--input: Input file to configure with. Binds will be added on or overwritten.");
puts("\tIf not selected, an empty config will be used as a base.");
puts("-o/--output: Output file to write to. If not selected, config file will be dumped to stdout.");
puts("-m/--misc: Also configure various keybinds that are not directly SNES related. These configurations are for player 1 only.");
puts("-h/--help: This help.");
}
@@ -55,9 +58,13 @@ struct bind
char *keystr;
char *confbtn[MAX_PLAYERS];
char *confaxis[MAX_PLAYERS];
bool is_misc;
};
#define BIND(x, k) { x, { "input_player1_" #k "_btn", "input_player2_" #k "_btn", "input_player3_" #k "_btn", "input_player4_" #k "_btn", "input_player5_" #k "_btn" }, {"input_player1_" #k "_axis", "input_player2_" #k "_axis", "input_player3_" #k "_axis", "input_player4_" #k "_axis", "input_player5_" #k "_axis"}},
#define BIND(x, k) { x, { "input_player1_" #k "_btn", "input_player2_" #k "_btn", "input_player3_" #k "_btn", "input_player4_" #k "_btn", "input_player5_" #k "_btn" }, {"input_player1_" #k "_axis", "input_player2_" #k "_axis", "input_player3_" #k "_axis", "input_player4_" #k "_axis", "input_player5_" #k "_axis"}, false},
#define MISC_BIND(x, k) { x, { "input_" #k "_btn" }, { "input_" #k "_axis" }, true},
static struct bind binds[] = {
BIND("A button (right)", a)
BIND("B button (down)", b)
@@ -71,6 +78,19 @@ static struct bind binds[] = {
BIND("Up D-pad", up)
BIND("Right D-pad", right)
BIND("Down D-pad", down)
MISC_BIND("Save state", save_state)
MISC_BIND("Load state", load_state)
MISC_BIND("Exit emulator", exit_emulator)
MISC_BIND("Toggle fullscreen", toggle_fullscreen)
MISC_BIND("Save state slot increase", state_slot_increase)
MISC_BIND("Save state slot decrease", state_slot_decrease)
MISC_BIND("Toggle fast forward", toggle_fast_forward)
MISC_BIND("Audio input rate step up", rate_step_up)
MISC_BIND("Audio input rate step down", rate_step_down)
MISC_BIND("Rewind", rewind)
MISC_BIND("Movie recording toggle", movie_record_toggle)
MISC_BIND("Pause", pause_toggle)
};
void get_binds(config_file_t *conf, int player, int joypad)
@@ -95,20 +115,21 @@ void get_binds(config_file_t *conf, int player, int joypad)
exit(1);
}
SDL_JoystickUpdate();
int last_axis = 0xFF;
int last_pos = 0;
int num_axes = SDL_JoystickNumAxes(joystick);
int initial_axes[num_axes];
SDL_PumpEvents();
SDL_JoystickUpdate();
for (int i = 0; i < num_axes; i++)
initial_axes[i] = SDL_JoystickGetAxis(joystick, i);
fprintf(stderr, "Configuring binds for player #%d on joypad #%d (%s)\n", player + 1, joypad, SDL_JoystickName(joypad));
fprintf(stderr, "Press Ctrl-C to exit early.\n");
fprintf(stderr, "\n");
for (unsigned i = 0; i < sizeof(binds) / sizeof(struct bind); i++)
for (unsigned i = 0; i < sizeof(binds) / sizeof(struct bind) && (g_use_misc || !binds[i].is_misc) ; i++)
{
fprintf(stderr, "%s\n", binds[i].keystr);
@@ -116,6 +137,7 @@ void get_binds(config_file_t *conf, int player, int joypad)
SDL_Event event;
int value;
const char *quark;
unsigned player_index = binds[i].is_misc ? 0 : player;
while (SDL_WaitEvent(&event) && !done)
{
@@ -124,24 +146,36 @@ void get_binds(config_file_t *conf, int player, int joypad)
case SDL_JOYBUTTONDOWN:
fprintf(stderr, "\tJoybutton pressed: %d\n", (int)event.jbutton.button);
done = true;
config_set_int(conf, binds[i].confbtn[player], event.jbutton.button);
config_set_int(conf, binds[i].confbtn[player_index], event.jbutton.button);
break;
case SDL_JOYAXISMOTION:
if (abs(event.jaxis.value) > 20000 &&
abs((int)event.jaxis.value - initial_axes[event.jaxis.axis]) > 20000 &&
event.jaxis.axis != last_axis)
if ( // This is starting to look like Lisp! :D
(abs((int)event.jaxis.value - initial_axes[event.jaxis.axis]) > 20000) &&
(
(event.jaxis.axis != last_axis) ||
(
(abs(event.jaxis.value) > 20000) &&
(abs((int)event.jaxis.value - last_pos) > 20000)
)
)
)
{
last_axis = event.jaxis.axis;
last_pos = event.jaxis.value;
fprintf(stderr, "\tJoyaxis moved: Axis %d, Value %d\n", (int)event.jaxis.axis, (int)event.jaxis.value);
done = true;
char buf[8];
snprintf(buf, sizeof(buf), event.jaxis.value > 0 ? "+%d" : "-%d", event.jaxis.axis);
config_set_string(conf, binds[i].confaxis[player], buf);
config_set_string(conf, binds[i].confaxis[player_index], buf);
}
break;
case SDL_KEYDOWN:
fprintf(stderr, ":V\n");
break;
case SDL_JOYHATMOTION:
value = event.jhat.value;
if (value & SDL_HAT_UP)
@@ -160,7 +194,7 @@ void get_binds(config_file_t *conf, int player, int joypad)
done = true;
char buf[16];
snprintf(buf, sizeof(buf), "h%d%s", event.jhat.hat, quark);
config_set_string(conf, binds[i].confbtn[player], buf);
config_set_string(conf, binds[i].confbtn[player_index], buf);
break;
@@ -180,13 +214,14 @@ end:
static void parse_input(int argc, char *argv[])
{
char optstring[] = "i:o:p:j:h";
char optstring[] = "i:o:p:j:hm";
struct option opts[] = {
{ "input", 1, NULL, 'i' },
{ "output", 1, NULL, 'o' },
{ "player", 1, NULL, 'p' },
{ "joypad", 1, NULL, 'j' },
{ "help", 0, NULL, 'h' },
{ "misc", 0, NULL, 'm' },
{ NULL, 0, NULL, 0 }
};
@@ -211,6 +246,10 @@ static void parse_input(int argc, char *argv[])
g_out_path = strdup(optarg);
break;
case 'm':
g_use_misc = true;
break;
case 'j':
g_joypad = strtol(optarg, NULL, 0);
if (g_joypad < 0)