mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
336adb9f85 | ||
|
|
7053006e77 | ||
|
|
bbf4804d12 | ||
|
|
7c16424ba3 | ||
|
|
de9dc686ac | ||
|
|
069e9b574f | ||
|
|
d5319a879a | ||
|
|
f64880aee1 | ||
|
|
fed1857918 | ||
|
|
9aeb40155e | ||
|
|
e49aeb1071 | ||
|
|
13f8ec6f3b | ||
|
|
f75c6df270 | ||
|
|
60b0afce95 | ||
|
|
f24653768f | ||
|
|
f135972a6b | ||
|
|
42c68e21a2 | ||
|
|
e8865060ca | ||
|
|
b07827a856 | ||
|
|
0735398e64 | ||
|
|
ec2d10e069 | ||
|
|
4cdb6151e5 | ||
|
|
e482b1bd18 | ||
|
|
9be3c13a07 | ||
|
|
8a4bcacf88 | ||
|
|
6b9b37b528 | ||
|
|
8e87c1ff19 | ||
|
|
aa648c1a49 | ||
|
|
056a590671 | ||
|
|
97f2503794 | ||
|
|
143079efbe | ||
|
|
7bcc173c5f | ||
|
|
0485f783b3 | ||
|
|
f3c07325bd | ||
|
|
e8eabb0102 | ||
|
|
b73f5e77ff | ||
|
|
d07712b308 | ||
|
|
89ba64d27f | ||
|
|
1a6f112d8a | ||
|
|
70e8681487 | ||
|
|
17ae0f72df | ||
|
|
466485e62e | ||
|
|
f6ee0845f2 | ||
|
|
df1eee1a74 | ||
|
|
46b46ba0b1 | ||
|
|
a763848644 | ||
|
|
9ba2d6c9fa | ||
|
|
fd1367e257 | ||
|
|
85dbc05ded | ||
|
|
1eea7786bc | ||
|
|
d039bedad4 | ||
|
|
ad6719426c | ||
|
|
719e4942ce | ||
|
|
ad9a08aea6 | ||
|
|
604fe709d5 | ||
|
|
81e8368099 | ||
|
|
2137ca8e3f | ||
|
|
672824539c | ||
|
|
1915be5aea | ||
|
|
bdea56370d | ||
|
|
e5e0c33ed6 | ||
|
|
d0c10e7523 | ||
|
|
91d2cea259 | ||
|
|
d9e2072733 | ||
|
|
820b124165 | ||
|
|
caf3eb7b05 | ||
|
|
1b5e63f024 | ||
|
|
93202f191c | ||
|
|
3033fb6791 | ||
|
|
87589609bf | ||
|
|
757514242d | ||
|
|
359d228632 | ||
|
|
24dda40863 | ||
|
|
6c0eebd4bf | ||
|
|
31db2a30bb | ||
|
|
5e241d79c8 | ||
|
|
96389ef83b | ||
|
|
af010712f3 | ||
|
|
c2896db558 | ||
|
|
57eebebcdd | ||
|
|
9ad9cbd7de | ||
|
|
1b5adde885 | ||
|
|
ca35dd79d2 | ||
|
|
491f2e3e76 | ||
|
|
b6591e21cd | ||
|
|
67ed89729d | ||
|
|
6033e75dc4 | ||
|
|
5f8993cb83 | ||
|
|
2446300dd9 | ||
|
|
b97c45df78 | ||
|
|
9034e82298 | ||
|
|
ae395f05e1 | ||
|
|
0c1d359091 | ||
|
|
dd8a9c67c9 | ||
|
|
de331bcd9e | ||
|
|
a81d6bc247 | ||
|
|
47d0b12d40 | ||
|
|
849e9aa734 | ||
|
|
351e098da1 | ||
|
|
c5f825eaa5 | ||
|
|
d0a4102efa | ||
|
|
3aa4181380 | ||
|
|
43dc30c3a5 | ||
|
|
98e6613df0 | ||
|
|
3ce07891e4 | ||
|
|
2ff47ab7c4 | ||
|
|
8ba639c7d5 | ||
|
|
e0403e4dae | ||
|
|
7776525c96 | ||
|
|
25fe9b7f58 | ||
|
|
fff7aba8ec | ||
|
|
e83f3ed444 | ||
|
|
334f11d5bc | ||
|
|
5bcde44684 | ||
|
|
2459793f9c | ||
|
|
cadd7b9728 | ||
|
|
644f247026 | ||
|
|
8c1ef3051d | ||
|
|
d296e15f26 | ||
|
|
5a5cd46f9b | ||
|
|
20d9345a4e |
No files matched your search
@@ -1,3 +1,5 @@
|
||||
*.o
|
||||
ssnes
|
||||
/config.h
|
||||
/config.mk
|
||||
/tools/ssnes-joyconfig
|
||||
@@ -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,17 +45,31 @@ 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
|
||||
endif
|
||||
ifeq ($(HAVE_PULSE), 1)
|
||||
OBJ += audio/pulse.o
|
||||
LIBS += $(PULSE_LIBS)
|
||||
DEFINES += $(PULSE_CFLAGS)
|
||||
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)
|
||||
@@ -90,7 +113,7 @@ ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
|
||||
CFLAGS += -Wall -O3 -g -std=gnu99 -I.
|
||||
|
||||
all: $(TARGET) config.mk
|
||||
|
||||
@@ -116,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}
|
||||
|
||||
+19
-4
@@ -1,18 +1,20 @@
|
||||
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
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 1
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS =
|
||||
LIBS = -lws2_32
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
@@ -23,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)
|
||||
@@ -31,6 +35,17 @@ ifeq ($(HAVE_SDL), 1)
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XAUDIO), 1)
|
||||
OBJ += audio/xaudio.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
@@ -84,7 +99,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.2.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll freetype6.dll $(JTARGET)
|
||||
zip -r ssnes-win32-0.3.1.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
|
||||
|
||||
@@ -18,15 +18,15 @@ 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 one of these audio driver libraries:
|
||||
SSNES needs at least one of these audio driver libraries:
|
||||
|
||||
- ALSA
|
||||
- OSS
|
||||
@@ -35,6 +35,8 @@ SSNES needs one of these audio driver libraries:
|
||||
- OpenAL
|
||||
- JACK
|
||||
- SDL
|
||||
- XAudio2 (Win32)
|
||||
- PulseAudio
|
||||
|
||||
# Building libsnes
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
===================================================
|
||||
SSNES - Simple Super Nintendo Emulator System
|
||||
===================================================
|
||||
|
||||
SSNES is a simplistic emulator frontend designed for use without any GUI.
|
||||
It is used from command line, but is designed to play well with eventual GUI frontends,
|
||||
as the configuration file format is very simple.
|
||||
|
||||
This allows for great portability and focus on what matters in a frontend, such as video and audio performance.
|
||||
Its core libraries for video and input are OpenGL and SDL, with many different audio library supported.
|
||||
|
||||
On first use you should get accustomed to how SSNES works.
|
||||
|
||||
- SSNES emphasizes simplicity. It has no graphical user interface, and is primarily used through the command line.
|
||||
It is recommended for experienced users.
|
||||
It is possible though to create a GUI which can use SSNES.
|
||||
|
||||
- SSNES isn't dependant on any emulator core, such as bSNES, SNES9x or ZSNES.
|
||||
SSNES only talks to the libsnes interface,
|
||||
which was created initially by bSNES but it has an implementation available for SNES9x as well.
|
||||
|
||||
- SSNES is configured through a config file. In the Win32 distribution, there should be a ssnes.cfg included.
|
||||
This config file serves as a base for your custom configuration file.
|
||||
It is recommended that you go through the config file and alter any configurations you might use.
|
||||
This is a good way to learn about the features SSNES have.
|
||||
It is possible to pick which configuration file you want with the '-c' flag.
|
||||
E.g.: ssnes.exe -c myconfig.cfg rom.sfc.
|
||||
|
||||
- When unsure which command line flags to use, use the command: ssnes.exe --help.
|
||||
|
||||
- When something is not working as expected, try using verbose logging first to see what is happening:
|
||||
E.g.: ssnes.exe -v romfile.sfc
|
||||
If something is not working, and you want to file a bug report,
|
||||
please include console output with the -v flag turned on.
|
||||
|
||||
- SSNES does not support zipped roms directly. It only supports roms with extentions .sfc and .smc.
|
||||
However, SSNES can read roms from stdin,
|
||||
which makes it possible to create wrapper programs that can support every zip format under the sun.
|
||||
This can also be performed by a GUI frontend.
|
||||
+110
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/* 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 "driver.h"
|
||||
#include "general.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
pa_threaded_mainloop *mainloop;
|
||||
pa_context *context;
|
||||
pa_stream *stream;
|
||||
bool nonblock;
|
||||
} pa_t;
|
||||
|
||||
static void __pulse_free(void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
if (pa)
|
||||
{
|
||||
if (pa->mainloop)
|
||||
pa_threaded_mainloop_stop(pa->mainloop);
|
||||
|
||||
if (pa->stream)
|
||||
{
|
||||
pa_stream_disconnect(pa->stream);
|
||||
pa_stream_unref(pa->stream);
|
||||
}
|
||||
|
||||
if (pa->context)
|
||||
{
|
||||
pa_context_disconnect(pa->context);
|
||||
pa_context_unref(pa->context);
|
||||
}
|
||||
|
||||
if (pa->mainloop)
|
||||
pa_threaded_mainloop_free(pa->mainloop);
|
||||
|
||||
free(pa);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
static void context_state_cb(pa_context *c, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_state_cb(pa_stream *s, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_request_cb(pa_stream *s, size_t length, void *data)
|
||||
{
|
||||
(void)length;
|
||||
(void)s;
|
||||
pa_t *pa = data;
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
static void stream_latency_update_cb(pa_stream *s, void *data)
|
||||
{
|
||||
(void)s;
|
||||
pa_t *pa = data;
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
static void* __pulse_init(const char* device, int rate, int latency)
|
||||
{
|
||||
pa_t *pa = calloc(1, sizeof(*pa));
|
||||
if (!pa)
|
||||
goto error;
|
||||
|
||||
pa->mainloop = pa_threaded_mainloop_new();
|
||||
if (!pa->mainloop)
|
||||
goto error;
|
||||
|
||||
pa->context = pa_context_new(pa_threaded_mainloop_get_api(pa->mainloop), "SSNES");
|
||||
if (!pa->context)
|
||||
goto error;
|
||||
|
||||
pa_context_set_state_callback(pa->context, context_state_cb, pa);
|
||||
|
||||
if (pa_context_connect(pa->context, device, PA_CONTEXT_NOFLAGS, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
if (pa_threaded_mainloop_start(pa->mainloop) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
|
||||
if (pa_context_get_state(pa->context) != PA_CONTEXT_READY)
|
||||
goto unlock_error;
|
||||
|
||||
pa_sample_spec spec = {
|
||||
.format = is_little_endian() ? PA_SAMPLE_FLOAT32LE : PA_SAMPLE_FLOAT32BE,
|
||||
.channels = 2,
|
||||
.rate = rate
|
||||
};
|
||||
|
||||
pa->stream = pa_stream_new(pa->context, "audio", &spec, NULL);
|
||||
if (!pa->stream)
|
||||
goto unlock_error;
|
||||
|
||||
pa_stream_set_state_callback(pa->stream, stream_state_cb, pa);
|
||||
pa_stream_set_write_callback(pa->stream, stream_request_cb, pa);
|
||||
pa_stream_set_latency_update_callback(pa->stream, stream_latency_update_cb, pa);
|
||||
|
||||
pa_buffer_attr buffer_attr = {
|
||||
.maxlength = -1,
|
||||
.tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &spec),
|
||||
.prebuf = -1,
|
||||
.minreq = -1,
|
||||
.fragsize = -1
|
||||
};
|
||||
|
||||
if (pa_stream_connect_playback(pa->stream, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
|
||||
if (pa_stream_get_state(pa->stream) != PA_STREAM_READY)
|
||||
goto unlock_error;
|
||||
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
|
||||
return pa;
|
||||
|
||||
unlock_error:
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
error:
|
||||
__pulse_free(pa);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t __pulse_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
unsigned length = pa_stream_writable_size(pa->stream);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
while (length < size)
|
||||
{
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
length = pa_stream_writable_size(pa->stream);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
|
||||
if (pa->nonblock)
|
||||
break;
|
||||
}
|
||||
|
||||
size_t write_size = length < size ? length : size;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
pa_stream_write(pa->stream, buf, write_size, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
return write_size;
|
||||
}
|
||||
|
||||
static bool __pulse_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __pulse_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __pulse_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
pa->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __pulse_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_pulse = {
|
||||
.init = __pulse_init,
|
||||
.write = __pulse_write,
|
||||
.stop = __pulse_stop,
|
||||
.start = __pulse_start,
|
||||
.set_nonblock_state = __pulse_set_nonblock_state,
|
||||
.use_float = __pulse_use_float,
|
||||
.free = __pulse_free,
|
||||
.ident = "pulse"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+77
-30
@@ -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"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
// This will currently only compile with MSVC2k10.
|
||||
// Should be compilable with some MinGW trickery as well.
|
||||
|
||||
#include "xaudio-c.h"
|
||||
|
||||
struct xaudio2 : public IXAudio2VoiceCallback
|
||||
{
|
||||
xaudio2(unsigned samplerate, unsigned channels, unsigned bits, unsigned size) :
|
||||
buf(nullptr), pXAudio2(nullptr), pMasterVoice(nullptr), pSourceVoice(nullptr),
|
||||
buffers(0), bufptr(0)
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = XAudio2Create(&pXAudio2, 0)))
|
||||
throw -1;
|
||||
if (FAILED(hr = pXAudio2->CreateMasteringVoice(
|
||||
&pMasterVoice, channels, samplerate, 0, 0, nullptr)))
|
||||
throw -1;
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = samplerate;
|
||||
wfx.nBlockAlign = channels * bits / 8;
|
||||
wfx.wBitsPerSample = bits;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(
|
||||
&pSourceVoice, &wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, nullptr, nullptr)))
|
||||
throw -1;
|
||||
|
||||
pSourceVoice->Start();
|
||||
|
||||
hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!hEvent)
|
||||
throw -1;
|
||||
|
||||
max_buffers = 8;
|
||||
bufsize = size / max_buffers;
|
||||
buf = new uint8_t[bufsize * max_buffers];
|
||||
write_buffer = 0;
|
||||
}
|
||||
|
||||
~xaudio2()
|
||||
{
|
||||
if (pSourceVoice)
|
||||
{
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (pMasterVoice)
|
||||
pMasterVoice->DestroyVoice();
|
||||
|
||||
if (pXAudio2)
|
||||
pXAudio2->Release();
|
||||
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent);
|
||||
|
||||
if (buf)
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
size_t write_avail() const
|
||||
{
|
||||
return bufsize * (max_buffers - buffers - 1);
|
||||
}
|
||||
|
||||
size_t write(const void *buffer, size_t bytes)
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < bytes)
|
||||
{
|
||||
size_t need = min(bytes - written, bufsize - bufptr);
|
||||
memcpy((uint8_t*)buf + write_buffer * bufsize + bufptr, (const uint8_t*)buffer + written, need);
|
||||
written += need;
|
||||
bufptr += need;
|
||||
|
||||
if (bufptr == bufsize)
|
||||
{
|
||||
while (buffers == max_buffers - 1)
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
|
||||
XAUDIO2_BUFFER xa2buffer = {0};
|
||||
xa2buffer.AudioBytes = bufsize;
|
||||
xa2buffer.pAudioData = buf + write_buffer * bufsize;
|
||||
xa2buffer.pContext = nullptr;
|
||||
InterlockedIncrement(&buffers);
|
||||
pSourceVoice->SubmitSourceBuffer(&xa2buffer);
|
||||
bufptr = 0;
|
||||
write_buffer = (write_buffer + 1) % max_buffers;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
STDMETHOD_(void, OnBufferStart) (void *) {}
|
||||
STDMETHOD_(void, OnBufferEnd) (void *)
|
||||
{
|
||||
InterlockedDecrement(&buffers);
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnLoopEnd) (void *) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceError) (void *, HRESULT) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) ()
|
||||
{
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
|
||||
uint8_t *buf;
|
||||
IXAudio2 *pXAudio2;
|
||||
IXAudio2MasteringVoice *pMasterVoice;
|
||||
IXAudio2SourceVoice *pSourceVoice;
|
||||
HANDLE hEvent;
|
||||
|
||||
volatile long buffers;
|
||||
unsigned max_buffers;
|
||||
unsigned bufsize;
|
||||
unsigned bufptr;
|
||||
unsigned write_buffer;
|
||||
};
|
||||
|
||||
XAUDIOC_API xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels, unsigned bits, size_t size)
|
||||
{
|
||||
xaudio2 *handle = nullptr;
|
||||
try {
|
||||
handle = new xaudio2(samplerate, channels, bits, size);
|
||||
} catch(...) { delete handle; return nullptr; }
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
XAUDIOC_API size_t xaudio2_write_avail(xaudio2_t *handle)
|
||||
{
|
||||
try {
|
||||
return handle->write_avail();
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
XAUDIOC_API size_t xaudio2_write(xaudio2_t *handle, const void *buf, size_t bytes)
|
||||
{
|
||||
try {
|
||||
return handle->write(buf, bytes);
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
XAUDIOC_API void xaudio2_free(xaudio2_t *handle)
|
||||
{
|
||||
delete handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO_C_H
|
||||
#define XAUDIO_C_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef XAUDIOC_EXPORTS
|
||||
#define XAUDIOC_API __declspec(dllexport)
|
||||
#else
|
||||
#define XAUDIOC_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
typedef struct xaudio2 xaudio2_t;
|
||||
|
||||
XAUDIOC_API xaudio2_t* xaudio2_new(unsigned samplerate, unsigned channels, unsigned bits, size_t bufsize);
|
||||
XAUDIOC_API size_t xaudio2_write_avail(xaudio2_t *handle);
|
||||
XAUDIOC_API size_t xaudio2_write(xaudio2_t *handle, const void *data, size_t bytes);
|
||||
XAUDIOC_API void xaudio2_free(xaudio2_t *handle);
|
||||
|
||||
typedef xaudio2_t* (*xaudio2_new_t)(unsigned, unsigned, unsigned, size_t);
|
||||
typedef size_t (*xaudio2_write_avail_t)(xaudio2_t*);
|
||||
typedef size_t (*xaudio2_write_t)(xaudio2_t*, const void*, size_t);
|
||||
typedef void (*xaudio2_free_t)(xaudio2_t*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/* 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 "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include "xaudio-c.h"
|
||||
#include "general.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
xaudio2_t *xa;
|
||||
bool nonblock;
|
||||
} xa_t;
|
||||
|
||||
static xaudio2_new_t pxanew = NULL;
|
||||
static xaudio2_write_t pxawrite = NULL;
|
||||
static xaudio2_write_avail_t pxawrite_avail = NULL;
|
||||
static xaudio2_free_t pxafree = NULL;
|
||||
static HMODULE lib = NULL;
|
||||
|
||||
#define LIB_NAME "xaudio-c.dll"
|
||||
#define SYM(X) ((void*)GetProcAddress(lib, "xaudio2_" #X))
|
||||
|
||||
static void deinit_lib(void)
|
||||
{
|
||||
FreeModule(lib);
|
||||
lib = NULL;
|
||||
}
|
||||
|
||||
static bool init_lib(void)
|
||||
{
|
||||
if (lib)
|
||||
return true;
|
||||
|
||||
lib = LoadLibrary(LIB_NAME);
|
||||
if (!lib)
|
||||
return false;
|
||||
|
||||
pxanew = SYM(new);
|
||||
pxawrite = SYM(write);
|
||||
pxawrite_avail = SYM(write_avail);
|
||||
pxafree = SYM(free);
|
||||
|
||||
if (!pxanew || !pxawrite || !pxawrite_avail || !pxafree)
|
||||
{
|
||||
deinit_lib();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void* __xa_init(const char* device, int rate, int latency)
|
||||
{
|
||||
if (!init_lib())
|
||||
return NULL;
|
||||
|
||||
xa_t *xa = calloc(1, sizeof(xa_t));
|
||||
if (xa == NULL)
|
||||
return NULL;
|
||||
|
||||
size_t bufsize = latency * rate / 1000;
|
||||
bufsize = next_pow2(bufsize);
|
||||
|
||||
SSNES_LOG("XAudio2: Requesting %d ms latency, using %d ms latency.\n", latency, (int)bufsize * 1000 / rate);
|
||||
|
||||
xa->xa = pxanew(rate, 2, 16, bufsize << 2);
|
||||
if (!xa->xa)
|
||||
{
|
||||
SSNES_ERR("Failed to init XAudio2.\n");
|
||||
free(xa);
|
||||
return NULL;
|
||||
}
|
||||
return xa;
|
||||
}
|
||||
|
||||
static ssize_t __xa_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
if (xa->nonblock)
|
||||
{
|
||||
size_t avail = pxawrite_avail(xa->xa);
|
||||
if (avail < size)
|
||||
size = avail;
|
||||
}
|
||||
return pxawrite(xa->xa, buf, size);
|
||||
}
|
||||
|
||||
static bool __xa_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __xa_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
xa->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __xa_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __xa_free(void *data)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
if (xa && xa->xa)
|
||||
{
|
||||
if (pxafree)
|
||||
pxafree(xa->xa);
|
||||
free(xa);
|
||||
}
|
||||
deinit_lib();
|
||||
}
|
||||
|
||||
const audio_driver_t audio_xa = {
|
||||
.init = __xa_init,
|
||||
.write = __xa_write,
|
||||
.stop = __xa_stop,
|
||||
.start = __xa_start,
|
||||
.set_nonblock_state = __xa_set_nonblock_state,
|
||||
.free = __xa_free,
|
||||
.ident = "xaudio"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+144
@@ -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
@@ -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
|
||||
+48
-15
@@ -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
|
||||
|
||||
|
||||
@@ -53,26 +51,34 @@
|
||||
#define AUDIO_AL 5
|
||||
#define AUDIO_JACK 6
|
||||
#define AUDIO_SDL 8
|
||||
#define AUDIO_XAUDIO 9
|
||||
#define AUDIO_PULSE 10
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
////////////////////////
|
||||
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
|
||||
|
||||
#if HAVE_ALSA
|
||||
#if defined(HAVE_ALSA)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
|
||||
#elif HAVE_OSS
|
||||
#elif defined(HAVE_PULSE)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_PULSE
|
||||
#elif defined(HAVE_OSS)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif HAVE_JACK
|
||||
#elif defined(HAVE_JACK)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif HAVE_RSOUND
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND
|
||||
#elif HAVE_ROAR
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR
|
||||
#elif HAVE_AL
|
||||
#elif defined(HAVE_AL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif HAVE_SDL
|
||||
#elif defined(HAVE_SDL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#elif defined(HAVE_XAUDIO)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_XAUDIO
|
||||
#elif defined(HAVE_RSOUND)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND
|
||||
#elif defined(HAVE_ROAR)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR
|
||||
#else
|
||||
#error Need at least one audio driver!
|
||||
#endif
|
||||
|
||||
#define INPUT_DEFAULT_DRIVER INPUT_SDL
|
||||
@@ -88,8 +94,8 @@ static const float yscale = 3.0; // Real y res = 224 * yscale
|
||||
|
||||
// Fullscreen
|
||||
static const bool fullscreen = false; // To start in Fullscreen or not
|
||||
static const unsigned fullscreen_x = 1280;
|
||||
static const unsigned fullscreen_y = 720;
|
||||
static const unsigned fullscreen_x = 0; // Fullscreen resolution. A value of 0 uses the desktop resolution.
|
||||
static const unsigned fullscreen_y = 0;
|
||||
|
||||
// Video VSYNC (recommended)
|
||||
static const bool vsync = true;
|
||||
@@ -121,7 +127,10 @@ static const unsigned out_rate = 48000;
|
||||
|
||||
// Input samplerate from libSNES.
|
||||
// Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
|
||||
static const unsigned in_rate = 31980;
|
||||
static const float in_rate = 31980.0;
|
||||
|
||||
// When changing input rate on-the-fly
|
||||
static const float audio_rate_step = 0.25;
|
||||
|
||||
// Audio device (e.g. hw:0,0 or /dev/audio). If NULL, will use defaults.
|
||||
static const char* audio_device = NULL;
|
||||
@@ -133,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;
|
||||
|
||||
|
||||
////////////////////
|
||||
@@ -171,6 +199,11 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
{ SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_MINUS, SDLK_F6, NO_BTN, AXIS_NONE },
|
||||
{ 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 }
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,18 @@ static const bool _jack_supp = true;
|
||||
static const bool _jack_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PULSE
|
||||
static const bool _pulse_supp = true;
|
||||
#else
|
||||
static const bool _pulse_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XAUDIO
|
||||
static const bool _xaudio_supp = true;
|
||||
#else
|
||||
static const bool _xaudio_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
static const bool _filter_supp = true;
|
||||
#else
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -49,6 +49,12 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&audio_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_XAUDIO
|
||||
&audio_xa,
|
||||
#endif
|
||||
#ifdef HAVE_PULSE
|
||||
&audio_pulse,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const video_driver_t *video_drivers[] = {
|
||||
@@ -160,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;
|
||||
@@ -186,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;
|
||||
@@ -268,27 +286,3 @@ void uninit_video_input(void)
|
||||
|
||||
driver_t driver;
|
||||
|
||||
#if 0
|
||||
driver_t driver = {
|
||||
#if VIDEO_DRIVER == VIDEO_GL
|
||||
.video = &video_gl,
|
||||
#else
|
||||
#error "Define a valid video driver in config.h"
|
||||
#endif
|
||||
|
||||
#if AUDIO_DRIVER == AUDIO_ALSA
|
||||
.audio = &audio_alsa,
|
||||
#elif AUDIO_DRIVER == AUDIO_RSOUND
|
||||
.audio = &audio_rsound,
|
||||
#elif AUDIO_DRIVER == AUDIO_OSS
|
||||
.audio = &audio_oss,
|
||||
#elif AUDIO_DRIVER == AUDIO_ROAR
|
||||
.audio = &audio_roar,
|
||||
#elif AUDIO_DRIVER == AUDIO_AL
|
||||
.audio = &audio_openal,
|
||||
#else
|
||||
#error "Define a valid audio driver in config.h"
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -34,8 +34,14 @@ enum
|
||||
SSNES_QUIT_KEY,
|
||||
SSNES_STATE_SLOT_PLUS,
|
||||
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
|
||||
{
|
||||
int id;
|
||||
@@ -67,15 +73,14 @@ 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 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)
|
||||
@@ -105,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;
|
||||
@@ -138,6 +144,8 @@ extern const audio_driver_t audio_roar;
|
||||
extern const audio_driver_t audio_openal;
|
||||
extern const audio_driver_t audio_jack;
|
||||
extern const audio_driver_t audio_sdl;
|
||||
extern const audio_driver_t audio_xa;
|
||||
extern const audio_driver_t audio_pulse;
|
||||
extern const video_driver_t video_gl;
|
||||
extern const input_driver_t input_sdl;
|
||||
////////////////////////////////////////////////
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
#include <libsnes.hpp>
|
||||
#include <string.h>
|
||||
#include "dynamic.h"
|
||||
#include "movie.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
// Load SNES rom only. Applies a hack for headered ROMs.
|
||||
static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
@@ -29,6 +35,10 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
ssize_t ret;
|
||||
if (file == NULL) // stdin
|
||||
{
|
||||
#ifdef _WIN32
|
||||
setmode(0, O_BINARY);
|
||||
#endif
|
||||
|
||||
SSNES_LOG("Reading ROM from stdin ...\n");
|
||||
size_t buf_size = 0xFFFFF; // Some initial guesstimate.
|
||||
size_t buf_ptr = 0;
|
||||
@@ -94,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;
|
||||
}
|
||||
|
||||
@@ -103,10 +115,7 @@ static ssize_t read_file(const char *path, void **buf)
|
||||
void *rom_buf = NULL;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file)
|
||||
{
|
||||
SSNES_ERR("Couldn't open file: \"%s\"\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file);
|
||||
@@ -139,10 +148,7 @@ static bool dump_to_file(const char *path, const void *data, size_t size)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (!file)
|
||||
{
|
||||
SSNES_ERR("Couldn't dump to file %s\n", path);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
fwrite(data, 1, size, file);
|
||||
@@ -169,6 +175,8 @@ bool save_state(const char* path)
|
||||
psnes_serialize(data, size);
|
||||
bool ret = dump_to_file(path, data, size);
|
||||
free(data);
|
||||
if (!ret)
|
||||
SSNES_ERR("Failed to save state to \"%s\".\n", path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -179,7 +187,7 @@ bool load_state(const char* path)
|
||||
ssize_t size = read_file(path, &buf);
|
||||
if (size < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to load state.\n");
|
||||
SSNES_ERR("Failed to load state from \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -302,12 +310,12 @@ static bool load_bsx_rom(bool slotted)
|
||||
if (slotted)
|
||||
{
|
||||
if (!psnes_load_cartridge_bsx_slotted(
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
goto error;
|
||||
}
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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,24 @@
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#include "audio/hermite.h"
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 20 // 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
|
||||
|
||||
enum ssnes_shader_type
|
||||
{
|
||||
SSNES_SHADER_CG,
|
||||
SSNES_SHADER_BSNES,
|
||||
SSNES_SHADER_AUTO,
|
||||
SSNES_SHADER_NONE
|
||||
};
|
||||
|
||||
// All config related settings go here.
|
||||
struct settings
|
||||
{
|
||||
struct
|
||||
@@ -54,6 +70,7 @@ struct settings
|
||||
char cg_shader_path[256];
|
||||
char bsnes_shader_path[256];
|
||||
unsigned filter;
|
||||
enum ssnes_shader_type shader_type;
|
||||
|
||||
char font_path[256];
|
||||
unsigned font_size;
|
||||
@@ -67,7 +84,8 @@ struct settings
|
||||
char driver[32];
|
||||
bool enable;
|
||||
unsigned out_rate;
|
||||
unsigned in_rate;
|
||||
float in_rate;
|
||||
float rate_step;
|
||||
char device[256];
|
||||
unsigned latency;
|
||||
bool sync;
|
||||
@@ -80,9 +98,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
|
||||
@@ -94,10 +120,11 @@ 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;
|
||||
|
||||
@@ -109,10 +136,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];
|
||||
@@ -130,6 +160,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;
|
||||
@@ -144,6 +180,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];
|
||||
|
||||
@@ -24,23 +24,27 @@
|
||||
#include <sys/time.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#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
|
||||
@@ -79,6 +83,8 @@ typedef struct gl
|
||||
bool quitting;
|
||||
bool keep_aspect;
|
||||
|
||||
unsigned full_x, full_y;
|
||||
|
||||
unsigned win_width;
|
||||
unsigned win_height;
|
||||
unsigned vp_width;
|
||||
@@ -96,20 +102,46 @@ typedef struct gl
|
||||
} gl_t;
|
||||
|
||||
////////////////// Shaders
|
||||
static inline bool gl_shader_init(void)
|
||||
static bool gl_shader_init(void)
|
||||
{
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0 && strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
SSNES_WARN("Both Cg and bSNES XML shader are defined in config file. Cg shader will be selected by default.\n");
|
||||
switch (g_settings.video.shader_type)
|
||||
{
|
||||
case SSNES_SHADER_AUTO:
|
||||
{
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0 && strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
SSNES_WARN("Both Cg and bSNES XML shader are defined in config file. Cg shader will be selected by default.\n");
|
||||
|
||||
#ifdef HAVE_CG
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0)
|
||||
return gl_cg_init(g_settings.video.cg_shader_path);
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0)
|
||||
return gl_cg_init(g_settings.video.cg_shader_path);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
if (strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
return gl_glsl_init(g_settings.video.bsnes_shader_path);
|
||||
if (strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
return gl_glsl_init(g_settings.video.bsnes_shader_path);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CG
|
||||
case SSNES_SHADER_CG:
|
||||
{
|
||||
return gl_cg_init(g_settings.video.cg_shader_path);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
case SSNES_SHADER_BSNES:
|
||||
{
|
||||
return gl_glsl_init(g_settings.video.bsnes_shader_path);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -234,7 +266,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,10 +291,11 @@ 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;
|
||||
glFlush();
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
}
|
||||
font_renderer_free_output(&out);
|
||||
@@ -392,13 +425,14 @@ static bool gl_frame(void *data, const uint16_t* frame, unsigned width, unsigned
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, width, height, GL_BGRA,
|
||||
GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
|
||||
glFlush();
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
if (msg)
|
||||
gl_render_msg(gl, msg);
|
||||
|
||||
show_fps();
|
||||
glFlush();
|
||||
SDL_GL_SwapBuffers();
|
||||
|
||||
return true;
|
||||
@@ -424,16 +458,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)
|
||||
SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use WGL VSync call directly instead.\n");
|
||||
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)
|
||||
SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use GLX VSync call directly instead.\n");
|
||||
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
|
||||
}
|
||||
@@ -444,9 +474,14 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return NULL;
|
||||
|
||||
const SDL_VideoInfo *video_info = SDL_GetVideoInfo();
|
||||
assert(video_info);
|
||||
unsigned full_x = video_info->current_w;
|
||||
unsigned full_y = video_info->current_h;
|
||||
SSNES_LOG("Detecting desktop resolution %ux%u.\n", full_x, full_y);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
|
||||
|
||||
if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
@@ -465,8 +500,21 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
if (!gl)
|
||||
return NULL;
|
||||
|
||||
gl->win_width = video->width;
|
||||
gl->win_height = video->height;
|
||||
gl->full_x = full_x;
|
||||
gl->full_y = full_y;
|
||||
|
||||
if (video->fullscreen)
|
||||
{
|
||||
gl->win_width = video->width ? video->width : gl->full_x;
|
||||
gl->win_height = video->height ? video->height : gl->full_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
gl->win_width = video->width;
|
||||
gl->win_height = video->height;
|
||||
}
|
||||
SSNES_LOG("GL: Using resolution %ux%u\n", gl->win_width, gl->win_height);
|
||||
|
||||
gl->vsync = video->vsync;
|
||||
gl->keep_aspect = video->force_aspect;
|
||||
set_viewport(gl);
|
||||
@@ -490,7 +538,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);
|
||||
@@ -556,11 +604,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"
|
||||
};
|
||||
|
||||
+36
-4
@@ -23,19 +23,43 @@
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
|
||||
#define NO_SDL_GLEXT
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.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"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define pglCreateProgram glCreateProgram
|
||||
#define pglUseProgram glUseProgram
|
||||
#define pglCreateShader glCreateShader
|
||||
#define pglDeleteShader glDeleteShader
|
||||
#define pglShaderSource glShaderSource
|
||||
#define pglCompileShader glCompileShader
|
||||
#define pglAttachShader glAttachShader
|
||||
#define pglDetachShader glDetachShader
|
||||
#define pglLinkProgram glLinkProgram
|
||||
#define pglGetUniformLocation glGetUniformLocation
|
||||
#define pglUniform1i glUniform1i
|
||||
#define pglUniform2fv glUniform2fv
|
||||
#define pglUniform4fv glUniform4fv
|
||||
#define pglGetShaderiv glGetShaderiv
|
||||
#define pglGetShaderInfoLog glGetShaderInfoLog
|
||||
#define pglGetProgramiv glGetProgramiv
|
||||
#define pglGetProgramInfoLog glGetProgramInfoLog
|
||||
#else
|
||||
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
|
||||
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
|
||||
static PFNGLCREATESHADERPROC pglCreateShader = NULL;
|
||||
@@ -53,6 +77,7 @@ static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL;
|
||||
static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL;
|
||||
static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL;
|
||||
static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL;
|
||||
#endif
|
||||
|
||||
static bool glsl_enable = false;
|
||||
static GLuint gl_program;
|
||||
@@ -174,6 +199,8 @@ static void print_linker_log(GLuint obj)
|
||||
|
||||
bool gl_glsl_init(const char *path)
|
||||
{
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Load shader functions.
|
||||
pglCreateProgram = SDL_GL_GetProcAddress("glCreateProgram");
|
||||
pglUseProgram = SDL_GL_GetProcAddress("glUseProgram");
|
||||
@@ -192,13 +219,18 @@ bool gl_glsl_init(const char *path)
|
||||
pglGetShaderInfoLog = SDL_GL_GetProcAddress("glGetShaderInfoLog");
|
||||
pglGetProgramiv = SDL_GL_GetProcAddress("glGetProgramiv");
|
||||
pglGetProgramInfoLog = SDL_GL_GetProcAddress("glGetProgramInfoLog");
|
||||
#endif
|
||||
|
||||
SSNES_LOG("Checking GLSL shader support ...\n");
|
||||
#ifdef __APPLE__
|
||||
const bool shader_support = true;
|
||||
#else
|
||||
bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader
|
||||
&& pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader
|
||||
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
|
||||
&& pglUniform1i && pglUniform2fv && pglUniform4fv
|
||||
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog;
|
||||
#endif
|
||||
|
||||
if (!shader_support)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Author: Themaister
|
||||
License: Public domain
|
||||
*/
|
||||
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
|
||||
output main_fragment (float2 tex : TEXCOORD0, uniform input IN, uniform sampler2D s0 : TEXUNIT0)
|
||||
{
|
||||
float2 texsize = IN.texture_size;
|
||||
const float scale_factor = 1.0;
|
||||
float2 delta = 0.5 / (texsize * scale_factor);
|
||||
float dx = delta.x;
|
||||
float dy = delta.y;
|
||||
|
||||
float3 c00 = tex2D(s0, tex + float2(-dx, -dy)).xyz;
|
||||
float3 c01 = tex2D(s0, tex + float2(-dx, 0)).xyz;
|
||||
float3 c02 = tex2D(s0, tex + float2(-dx, dy)).xyz;
|
||||
float3 c10 = tex2D(s0, tex + float2(0, -dy)).xyz;
|
||||
float3 c11 = tex2D(s0, tex + float2(0, 0)).xyz;
|
||||
float3 c12 = tex2D(s0, tex + float2(0, dy)).xyz;
|
||||
float3 c20 = tex2D(s0, tex + float2(dx, -dy)).xyz;
|
||||
float3 c21 = tex2D(s0, tex + float2(dx, 0)).xyz;
|
||||
float3 c22 = tex2D(s0, tex + float2(dx, dy)).xyz;
|
||||
|
||||
output OUT;
|
||||
|
||||
float3 first = lerp(c00, c20, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
float3 second = lerp(c02, c22, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
|
||||
float3 mid_horiz = lerp(c01, c21, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
float3 mid_vert = lerp(c10, c12, frac(scale_factor * tex.y * texsize.y + 0.5));
|
||||
|
||||
float3 res = lerp(first, second, frac(scale_factor * tex.y * texsize.y + 0.5));
|
||||
OUT.color = float4(0.28 * (res + mid_horiz + mid_vert) + 4.7 * abs(res - lerp(mid_horiz, mid_vert, 0.5)), 1.0);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
Author: Themaister
|
||||
License: Public domain
|
||||
*/
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
float4 compress(float4 in_color, float threshold, float ratio)
|
||||
{
|
||||
float4 diff = in_color - float4(threshold);
|
||||
diff = clamp(diff, 0.0, 100.0);
|
||||
return in_color - (diff * (1.0 - 1.0/ratio));
|
||||
}
|
||||
|
||||
output main_fragment (float2 tex : TEXCOORD0, uniform input IN, uniform sampler2D s0 : TEXUNIT0)
|
||||
{
|
||||
float2 texsize = IN.texture_size;
|
||||
const float scale_factor = 1.0;
|
||||
float2 delta = 0.5 / (texsize * scale_factor);
|
||||
float dx = delta.x;
|
||||
float dy = delta.y;
|
||||
|
||||
float3 c00 = tex2D(s0, tex + float2(-dx, -dy)).xyz;
|
||||
float3 c01 = tex2D(s0, tex + float2(-dx, 0)).xyz;
|
||||
float3 c02 = tex2D(s0, tex + float2(-dx, dy)).xyz;
|
||||
float3 c10 = tex2D(s0, tex + float2(0, -dy)).xyz;
|
||||
float3 c11 = tex2D(s0, tex + float2(0, 0)).xyz;
|
||||
float3 c12 = tex2D(s0, tex + float2(0, dy)).xyz;
|
||||
float3 c20 = tex2D(s0, tex + float2(dx, -dy)).xyz;
|
||||
float3 c21 = tex2D(s0, tex + float2(dx, 0)).xyz;
|
||||
float3 c22 = tex2D(s0, tex + float2(dx, dy)).xyz;
|
||||
|
||||
output OUT;
|
||||
|
||||
float3 first = lerp(c00, c20, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
float3 second = lerp(c02, c22, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
|
||||
float3 mid_horiz = lerp(c01, c21, frac(scale_factor * tex.x * texsize.x + 0.5));
|
||||
float3 mid_vert = lerp(c10, c12, frac(scale_factor * tex.y * texsize.y + 0.5));
|
||||
|
||||
float3 res = lerp(first, second, frac(scale_factor * tex.y * texsize.y + 0.5));
|
||||
float4 final = float4(0.26 * (res + mid_horiz + mid_vert) + 3.5 * abs(res - lerp(mid_horiz, mid_vert, 0.5)), 1.0);
|
||||
OUT.color = compress(final, 0.8, 5.0);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
+1
-6
@@ -59,11 +59,6 @@ static void* sdl_input_init(void)
|
||||
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_WARN("Desired SDL joystick #%u on port %u, but SDL can only detect %u joysticks ...\n",
|
||||
g_settings.input.joypad_map[i], i + 1, sdl->num_joysticks);
|
||||
}
|
||||
}
|
||||
|
||||
return sdl;
|
||||
@@ -327,7 +322,7 @@ static void sdl_input_poll(void *data)
|
||||
*sdl->should_resize = true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Executable
+133
@@ -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
|
||||
@@ -0,0 +1,315 @@
|
||||
/* 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"
|
||||
#include <assert.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;
|
||||
|
||||
long *frame_pos; // A ring buffer keeping track of many key presses were requested per frame.
|
||||
size_t frame_mask;
|
||||
uint16_t current_frame_count;
|
||||
size_t frame_ptr;
|
||||
|
||||
bool playback;
|
||||
unsigned min_file_pos;
|
||||
|
||||
bool first_rewind;
|
||||
bool did_rewind;
|
||||
};
|
||||
|
||||
#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->playback = true;
|
||||
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;
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
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;
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
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->frame_pos);
|
||||
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;
|
||||
|
||||
if (!(handle->frame_pos = calloc((1 << 20), sizeof(long)))) // Just pick something really large :D
|
||||
goto error;
|
||||
handle->frame_mask = (1 << 20) - 1;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
bsv_movie_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void bsv_movie_set_frame_start(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Starting frame: %u, Pos: %ld\n", (unsigned)handle->frame_ptr, ftell(handle->file));
|
||||
handle->frame_pos[handle->frame_ptr] = ftell(handle->file);
|
||||
|
||||
}
|
||||
|
||||
void bsv_movie_set_frame_end(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Frame++\n");
|
||||
handle->frame_ptr = (handle->frame_ptr + 1) & handle->frame_mask;
|
||||
|
||||
handle->first_rewind = !handle->did_rewind;
|
||||
handle->did_rewind = false;
|
||||
}
|
||||
|
||||
void bsv_movie_frame_rewind(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Frame--\n");
|
||||
|
||||
handle->did_rewind = true;
|
||||
if (handle->frame_ptr <= 1)
|
||||
{
|
||||
handle->frame_ptr = 0;
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->frame_ptr = (handle->frame_ptr - (handle->first_rewind ? 1 : 2)) & handle->frame_mask;
|
||||
fseek(handle->file, handle->frame_pos[handle->frame_ptr], SEEK_SET);
|
||||
}
|
||||
|
||||
if (ftell(handle->file) <= (long)handle->min_file_pos)
|
||||
{
|
||||
if (!handle->playback)
|
||||
{
|
||||
fseek(handle->file, 4 * sizeof(uint32_t), SEEK_SET);
|
||||
psnes_serialize(handle->state, psnes_serialize_size());
|
||||
fwrite(handle->state, 1, psnes_serialize_size(), handle->file);
|
||||
}
|
||||
else
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
*/
|
||||
|
||||
#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);
|
||||
|
||||
// Used for rewinding while playback/record.
|
||||
void bsv_movie_set_frame_start(bsv_movie_t *handle); // Debugging purposes.
|
||||
void bsv_movie_set_frame_end(bsv_movie_t *handle);
|
||||
void bsv_movie_frame_rewind(bsv_movie_t *handle);
|
||||
|
||||
void bsv_movie_free(bsv_movie_t *handle);
|
||||
|
||||
uint32_t crc32_calculate(const uint8_t *data, unsigned length);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,701 @@
|
||||
/* 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 64
|
||||
#define RETRY_MS 500
|
||||
|
||||
static int poll_input(netplay_t *handle, bool block)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->udp_fd, &fds);
|
||||
FD_SET(handle->fd, &fds);
|
||||
int max_fd = (handle->fd > handle->udp_fd ? handle->fd : handle->udp_fd) + 1;
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = block ? (RETRY_MS * 1000) : 0
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
if (select(max_fd, &fds, NULL, NULL, &tv) < 0)
|
||||
return -1;
|
||||
|
||||
// Somewhat hacky, but we aren't using the TCP connection for anything useful atm.
|
||||
// Will probably add some proper messaging system here later.
|
||||
if (FD_ISSET(handle->fd, &fds))
|
||||
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 -1;
|
||||
}
|
||||
|
||||
if (block)
|
||||
SSNES_LOG("Network lag of %d ms, resending packet... Attempt %d of %d ...\n", RETRY_MS, i, MAX_RETRIES);
|
||||
|
||||
// Seems to be necessary on Win32.
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->udp_fd, &fds);
|
||||
FD_SET(handle->fd, &fds);
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
+10
-4
@@ -17,10 +17,17 @@ 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
|
||||
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
@@ -39,14 +46,13 @@ if [ $HAVE_FFMPEG != no ]; then
|
||||
fi
|
||||
|
||||
check_pkgconf SRC samplerate
|
||||
check_critical SRC "Cannot find libsamplerate."
|
||||
|
||||
check_lib DYNAMIC -ldl dlopen
|
||||
|
||||
check_pkgconf FREETYPE freetype2
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE"
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.2
|
||||
PACKAGE_VERSION=0.3.1
|
||||
|
||||
# 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
|
||||
@@ -20,4 +21,5 @@ add_command_line_enable RSOUND "Enable RSound support" auto
|
||||
add_command_line_enable ROAR "Enable RoarAudio support" auto
|
||||
add_command_line_enable AL "Enable OpenAL support" auto
|
||||
add_command_line_enable JACK "Enable JACK support" auto
|
||||
add_command_line_enable PULSE "Enable PulseAudio support" auto
|
||||
add_command_line_enable FREETYPE "Enable FreeType support" auto
|
||||
+2
-2
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@ check_lib()
|
||||
|
||||
extralibs="$4"
|
||||
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $CFLAGS $LDFLAGS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
|
||||
@@ -75,7 +75,7 @@ check_lib_cxx()
|
||||
|
||||
extralibs="$4"
|
||||
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $CFLAGS $LDFLAGS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/* 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;
|
||||
bool first_pop;
|
||||
};
|
||||
|
||||
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->first_pop)
|
||||
{
|
||||
state->first_pop = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
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 each other, 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));
|
||||
state->first_pop = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+220
-160
@@ -69,6 +69,12 @@ static void set_defaults(void)
|
||||
case AUDIO_SDL:
|
||||
def_audio = "sdl";
|
||||
break;
|
||||
case AUDIO_XAUDIO:
|
||||
def_audio = "xaudio";
|
||||
break;
|
||||
case AUDIO_PULSE:
|
||||
def_audio = "pulse";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -101,6 +107,7 @@ static void set_defaults(void)
|
||||
#ifdef HAVE_FILTER
|
||||
g_settings.video.filter = FILTER_NONE;
|
||||
#endif
|
||||
g_settings.video.shader_type = SSNES_SHADER_AUTO;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
g_settings.video.font_size = font_size;
|
||||
@@ -111,11 +118,21 @@ static void set_defaults(void)
|
||||
g_settings.audio.enable = audio_enable;
|
||||
g_settings.audio.out_rate = out_rate;
|
||||
g_settings.audio.in_rate = in_rate;
|
||||
g_settings.audio.rate_step = audio_rate_step;
|
||||
if (audio_device)
|
||||
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));
|
||||
@@ -129,10 +146,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;
|
||||
for (int i = 0; i < 2; i++)
|
||||
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;
|
||||
for (int i = 2; i < MAX_PLAYERS; i++)
|
||||
g_settings.input.joypad_map[i] = SSNES_NO_JOYPAD;
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -149,6 +166,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)
|
||||
{
|
||||
@@ -164,32 +255,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;
|
||||
@@ -202,63 +268,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))
|
||||
{
|
||||
@@ -292,48 +321,41 @@ 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;
|
||||
|
||||
// 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_int(conf, "audio_in_rate", &tmp_int))
|
||||
g_settings.audio.in_rate = tmp_int;
|
||||
|
||||
if (config_get_string(conf, "audio_device", &tmp_str))
|
||||
#if defined(HAVE_CG) || defined(HAVE_XML)
|
||||
if (config_get_string(conf, "video_shader_type", &tmp_str))
|
||||
{
|
||||
strncpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device) - 1);
|
||||
if (strcmp("cg", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_CG;
|
||||
else if (strcmp("bsnes", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_BSNES;
|
||||
else if (strcmp("auto", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_AUTO;
|
||||
else if (strcmp("none", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_NONE;
|
||||
|
||||
free(tmp_str);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (config_get_int(conf, "audio_latency", &tmp_int))
|
||||
g_settings.audio.latency = 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");
|
||||
|
||||
if (config_get_bool(conf, "audio_sync", &tmp_bool))
|
||||
g_settings.audio.sync = tmp_bool;
|
||||
// Audio settings.
|
||||
|
||||
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,
|
||||
@@ -342,27 +364,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);
|
||||
|
||||
@@ -401,6 +418,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(state_slot_decrease, SSNES_STATE_SLOT_MINUS)
|
||||
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
|
||||
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(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -422,6 +444,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(state_slot_decrease, SSNES_STATE_SLOT_MINUS)
|
||||
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
|
||||
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(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -443,6 +470,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(state_slot_decrease, SSNES_STATE_SLOT_MINUS)
|
||||
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
|
||||
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(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -464,6 +496,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(state_slot_decrease, SSNES_STATE_SLOT_MINUS)
|
||||
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
|
||||
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(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -485,6 +522,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(state_slot_decrease, SSNES_STATE_SLOT_MINUS)
|
||||
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
|
||||
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(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
},
|
||||
};
|
||||
|
||||
@@ -511,6 +553,8 @@ static const struct key_map sdlk_map[] = {
|
||||
{ "alt", SDLK_LALT },
|
||||
{ "space", SDLK_SPACE },
|
||||
{ "escape", SDLK_ESCAPE },
|
||||
{ "kp_plus", SDLK_KP_PLUS },
|
||||
{ "kp_minus", SDLK_KP_MINUS },
|
||||
{ "f1", SDLK_F1 },
|
||||
{ "f2", SDLK_F2 },
|
||||
{ "f3", SDLK_F3 },
|
||||
@@ -523,6 +567,7 @@ static const struct key_map sdlk_map[] = {
|
||||
{ "f10", SDLK_F10 },
|
||||
{ "f11", SDLK_F11 },
|
||||
{ "f12", SDLK_F12 },
|
||||
{ "nul", SDLK_UNKNOWN },
|
||||
};
|
||||
|
||||
static struct snes_keybind *find_snes_bind(unsigned port, int id)
|
||||
@@ -556,6 +601,7 @@ static int find_sdlk_key(const char *str)
|
||||
return find_sdlk_bind(str);
|
||||
}
|
||||
|
||||
// Yes, this function needs a good refactor :)
|
||||
static void read_keybinds(config_file_t *conf)
|
||||
{
|
||||
char *tmp_key = NULL;
|
||||
@@ -570,6 +616,7 @@ static void read_keybinds(config_file_t *conf)
|
||||
if (!bind)
|
||||
continue;
|
||||
|
||||
// Check keybind
|
||||
if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key))
|
||||
{
|
||||
int key = find_sdlk_key(tmp_key);
|
||||
@@ -581,40 +628,53 @@ static void read_keybinds(config_file_t *conf)
|
||||
tmp_key = NULL;
|
||||
}
|
||||
|
||||
// Check joybutton bind (hats too)
|
||||
if (bind_maps[j][i].btn && config_get_string(conf, bind_maps[j][i].btn, &tmp_btn))
|
||||
{
|
||||
const char *btn = tmp_btn;
|
||||
if (*btn++ == 'h')
|
||||
if (strcmp(tmp_btn, "nul") == 0)
|
||||
{
|
||||
if (isdigit(*btn))
|
||||
{
|
||||
char *dir = NULL;
|
||||
int hat = strtol(btn, &dir, 0);
|
||||
int hat_dir = 0;
|
||||
if (dir)
|
||||
{
|
||||
if (strcasecmp(dir, "up") == 0)
|
||||
hat_dir = HAT_UP_MASK;
|
||||
else if (strcasecmp(dir, "down") == 0)
|
||||
hat_dir = HAT_DOWN_MASK;
|
||||
else if (strcasecmp(dir, "left") == 0)
|
||||
hat_dir = HAT_LEFT_MASK;
|
||||
else if (strcasecmp(dir, "right") == 0)
|
||||
hat_dir = HAT_RIGHT_MASK;
|
||||
|
||||
if (hat_dir)
|
||||
bind->joykey = HAT_MAP(hat, hat_dir);
|
||||
}
|
||||
}
|
||||
bind->joykey = NO_BTN;
|
||||
}
|
||||
else
|
||||
bind->joykey = strtol(tmp_btn, NULL, 0);
|
||||
{
|
||||
if (*btn++ == 'h')
|
||||
{
|
||||
if (isdigit(*btn))
|
||||
{
|
||||
char *dir = NULL;
|
||||
int hat = strtol(btn, &dir, 0);
|
||||
int hat_dir = 0;
|
||||
if (dir)
|
||||
{
|
||||
if (strcasecmp(dir, "up") == 0)
|
||||
hat_dir = HAT_UP_MASK;
|
||||
else if (strcasecmp(dir, "down") == 0)
|
||||
hat_dir = HAT_DOWN_MASK;
|
||||
else if (strcasecmp(dir, "left") == 0)
|
||||
hat_dir = HAT_LEFT_MASK;
|
||||
else if (strcasecmp(dir, "right") == 0)
|
||||
hat_dir = HAT_RIGHT_MASK;
|
||||
|
||||
if (hat_dir)
|
||||
bind->joykey = HAT_MAP(hat, hat_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
bind->joykey = strtol(tmp_btn, NULL, 0);
|
||||
}
|
||||
free(tmp_btn);
|
||||
}
|
||||
|
||||
// Check joyaxis binds.
|
||||
if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis))
|
||||
{
|
||||
if (strlen(tmp_axis) >= 2 && (*tmp_axis == '+' || *tmp_axis == '-'))
|
||||
if (strcmp(tmp_axis, "nul") == 0)
|
||||
{
|
||||
bind->joyaxis = AXIS_NONE;
|
||||
}
|
||||
else if (strlen(tmp_axis) >= 2 && (*tmp_axis == '+' || *tmp_axis == '-'))
|
||||
{
|
||||
int axis = strtol(tmp_axis + 1, NULL, 0);
|
||||
if (*tmp_axis == '+')
|
||||
|
||||
@@ -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"
|
||||
#define PACKAGE_VERSION "0.3.1"
|
||||
#endif
|
||||
|
||||
#include "config.features.h"
|
||||
@@ -257,6 +301,8 @@ static void print_features(void)
|
||||
_PSUPP(jack, "Jack", "audio driver");
|
||||
_PSUPP(rsound, "RSound", "audio driver");
|
||||
_PSUPP(roar, "RoarAudio", "audio driver");
|
||||
_PSUPP(pulse, "PulseAudio", "audio driver");
|
||||
_PSUPP(xaudio, "XAudio2", "audio driver");
|
||||
_PSUPP(al, "OpenAL", "audio driver");
|
||||
_PSUPP(filter, "Filter", "CPU based video filters");
|
||||
_PSUPP(cg, "Cg", "Cg pixel shaders");
|
||||
@@ -271,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.");
|
||||
@@ -292,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.");
|
||||
@@ -301,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)
|
||||
@@ -309,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' },
|
||||
@@ -330,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 }
|
||||
};
|
||||
|
||||
@@ -347,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;
|
||||
|
||||
@@ -376,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':
|
||||
@@ -405,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':
|
||||
@@ -439,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);
|
||||
@@ -451,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)
|
||||
@@ -466,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)
|
||||
{
|
||||
@@ -534,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);
|
||||
@@ -560,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);
|
||||
@@ -588,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,
|
||||
@@ -597,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
|
||||
@@ -634,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)
|
||||
@@ -641,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;
|
||||
|
||||
@@ -651,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.
|
||||
@@ -665,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);
|
||||
@@ -686,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))
|
||||
{
|
||||
@@ -725,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);
|
||||
}
|
||||
@@ -739,20 +1002,200 @@ 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);
|
||||
}
|
||||
old_should_slot_decrease = should_slot_decrease;
|
||||
}
|
||||
|
||||
static void check_input_rate(void)
|
||||
{
|
||||
bool display = false;
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_AUDIO_INPUT_RATE_PLUS))
|
||||
{
|
||||
g_settings.audio.in_rate += g_settings.audio.rate_step;
|
||||
display = true;
|
||||
}
|
||||
else if (driver.input->key_pressed(driver.input_data, SSNES_AUDIO_INPUT_RATE_MINUS))
|
||||
{
|
||||
g_settings.audio.in_rate -= g_settings.audio.rate_step;
|
||||
display = true;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "Audio input rate: %.2f Hz", g_settings.audio.in_rate);
|
||||
|
||||
msg_queue_clear(g_extern.msg_queue);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 0, 180);
|
||||
SSNES_LOG("%s\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void check_rewind(void)
|
||||
{
|
||||
g_extern.frame_is_reverse = false;
|
||||
static bool first = true;
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
g_extern.frame_is_reverse = true;
|
||||
msg_queue_push(g_extern.msg_queue, "Rewinding!", 0, 30);
|
||||
psnes_unserialize(buf, psnes_serialize_size());
|
||||
|
||||
if (g_extern.bsv_movie)
|
||||
{
|
||||
for (unsigned i = 0; i < (g_settings.rewind_granularity ? g_settings.rewind_granularity : 1); i++)
|
||||
bsv_movie_frame_rewind(g_extern.bsv_movie);
|
||||
}
|
||||
}
|
||||
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 || g_extern.bsv_movie)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -773,44 +1216,95 @@ 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_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);
|
||||
if (!g_extern.netplay)
|
||||
init_rewind();
|
||||
|
||||
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);
|
||||
if (g_extern.bsv_movie)
|
||||
bsv_movie_set_frame_start(g_extern.bsv_movie);
|
||||
|
||||
psnes_run();
|
||||
|
||||
if (g_extern.bsv_movie)
|
||||
bsv_movie_set_frame_end(g_extern.bsv_movie);
|
||||
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)
|
||||
save_files();
|
||||
|
||||
if (!g_extern.netplay)
|
||||
deinit_rewind();
|
||||
|
||||
deinit_movie();
|
||||
deinit_msg_queue();
|
||||
|
||||
psnes_unload_cartridge();
|
||||
psnes_term();
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
# video_xscale = 3.0
|
||||
# video_yscale = 3.0
|
||||
|
||||
# Fullscreen resolution
|
||||
# video_fullscreen_x = 1920
|
||||
# video_fullscreen_y = 1200
|
||||
# Fullscreen resolution. Resolution of 0 uses the resolution of the desktop.
|
||||
# video_fullscreen_x = 0
|
||||
# video_fullscreen_y = 0
|
||||
|
||||
# Start in fullscreen. Can be changed at runtime.
|
||||
# video_fullscreen = false
|
||||
@@ -19,7 +19,7 @@
|
||||
# Video vsync.
|
||||
# video_vsync = true
|
||||
|
||||
# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders.
|
||||
# Smoothens picture with bilinear filtering. Should be disabled if using pixel shaders.
|
||||
# video_smooth = true
|
||||
|
||||
# Forces rendering area to stay equal to SNES aspect ratio 4:3 or as defined in video_aspect_ratio.
|
||||
@@ -28,22 +28,28 @@
|
||||
# A floating point value for video aspect ratio (width / height)
|
||||
# video_aspect_ratio = 1.333
|
||||
|
||||
# Path to Cg shader. If enabled
|
||||
# Path to Cg shader.
|
||||
# video_cg_shader = "/path/to/cg/shader.cg"
|
||||
|
||||
# Path to bSNES-style XML shader. If both Cg shader path and XML shader path are defined, Cg shader will take priority.
|
||||
# Path to bSNES-style XML shader (GLSL only). If both Cg shader path and XML shader path are defined,
|
||||
# Cg shader will take priority unless overridden in video_shader_type.
|
||||
# video_bsnes_shader = "/path/to/bsnes/xml/shader.shader"
|
||||
|
||||
# Which shader type to use. Valid values are "cg", "bsnes", "none" and "auto"
|
||||
# video_shader_type = auto
|
||||
|
||||
# CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc.
|
||||
# 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.
|
||||
# video_font_size = 48
|
||||
|
||||
# Offset for where messages will be placed on screen. Values are in range 0.0 to 1.0 for both x and y values.
|
||||
# Offset for where messages will be placed on screen. Values are in range 0.0 to 1.0 for both x and y values.
|
||||
# [0.0, 0.0] maps to the lower left corner of the screen.
|
||||
# video_message_pos_x = 0.05
|
||||
# video_message_pox_y = 0.05
|
||||
|
||||
@@ -61,10 +67,13 @@
|
||||
# but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz.
|
||||
# audio_in_rate = 31980
|
||||
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, jack, rsound, roar, openal and sdl
|
||||
# When altering audio_in_rate on-the-fly, define by how much each time.
|
||||
# audio_rate_step = 0.25
|
||||
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl and xaudio
|
||||
# audio_driver =
|
||||
|
||||
# Override the default audio device the audio_driver uses.
|
||||
# Override the default audio device the audio_driver uses. This is driver dependant. E.g. ALSA wants a PCM device, OSS wants a path (e.g. /dev/dsp), Jack wants portnames (e.g. system:playback1,system:playback_2), and so on ...
|
||||
# audio_device =
|
||||
|
||||
# Will sync (block) on audio. Recommended.
|
||||
@@ -73,7 +82,7 @@
|
||||
# Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency.
|
||||
# audio_latency = 64
|
||||
|
||||
# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best.
|
||||
# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best respectively.
|
||||
# audio_src_quality =
|
||||
|
||||
### Input
|
||||
@@ -85,6 +94,8 @@
|
||||
# input_axis_threshold = 0.5
|
||||
|
||||
# Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on.
|
||||
# Keyboard input, Joypad and Joyaxis will all obey the "nul" bind, which disables the bind completely,
|
||||
# rather than relying on a default.
|
||||
# input_player1_a = x
|
||||
# input_player1_b = z
|
||||
# input_player1_y = a
|
||||
@@ -98,11 +109,14 @@
|
||||
# input_player1_up = up
|
||||
# input_player1_down = down
|
||||
|
||||
# If desired, it is possible to override which joypads are being used for player 1 and 2. First joypad available is 0.
|
||||
# If desired, it is possible to override which joypads are being used for player 1 through 5. First joypad available is 0.
|
||||
# input_player1_joypad_index = 0
|
||||
# input_player2_joypad_index = 1
|
||||
# input_player3_joypad_index = 2
|
||||
# input_player4_joypad_index = 3
|
||||
# input_player5_joypad_index = 4
|
||||
|
||||
# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output.
|
||||
# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output, or use ssnes-joyconfig.
|
||||
# You can use joypad hats with hnxx, where n is the hat, and xx is a string representing direction.
|
||||
# E.g. "h0up"
|
||||
# input_player1_a_btn = 1
|
||||
@@ -118,7 +132,7 @@
|
||||
# input_player1_up_btn = 13
|
||||
# input_player1_down_btn = 14
|
||||
|
||||
# Axis for DPAD.
|
||||
# Axis for SNES DPAD.
|
||||
# Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
|
||||
# Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity.
|
||||
# input_player1_left_axis = -0
|
||||
@@ -159,6 +173,8 @@
|
||||
# input_player2_up_axis = -1
|
||||
# input_player2_down_axis = +1
|
||||
|
||||
# This goes all the way to player 5, but again omitted for clarity.
|
||||
|
||||
# Toggles fullscreen.
|
||||
# input_toggle_fullscreen = f
|
||||
# Saves state.
|
||||
@@ -177,3 +193,37 @@
|
||||
# Key to exit emulator cleanly.
|
||||
# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.)
|
||||
# input_exit_emulator = escape
|
||||
|
||||
# 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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user