mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -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 |
No files matched your search
@@ -2,7 +2,7 @@ include config.mk
|
||||
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o netplay.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
@@ -113,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
|
||||
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.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
|
||||
@@ -14,7 +14,7 @@ HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 1
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS =
|
||||
LIBS = -lws2_32
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
@@ -99,7 +99,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.2.2.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
|
||||
zip -r ssnes-win32-0.3.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
|
||||
|
||||
+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
|
||||
+8
-2
@@ -94,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;
|
||||
@@ -159,6 +159,12 @@ 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;
|
||||
|
||||
|
||||
////////////////////
|
||||
// Keybinds, Joypad
|
||||
|
||||
+18
-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.
|
||||
@@ -109,5 +109,22 @@ It also requires to play back with the same libsnes backend that was used for re
|
||||
\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)
|
||||
@@ -41,6 +41,7 @@ enum
|
||||
SSNES_PAUSE_TOGGLE,
|
||||
};
|
||||
|
||||
|
||||
struct snes_keybind
|
||||
{
|
||||
int id;
|
||||
@@ -78,7 +79,6 @@ typedef struct audio_driver
|
||||
|
||||
#define AXIS_NEG_GET(x) (((uint32_t)(x) >> 16) & 0xFFFFU)
|
||||
#define AXIS_POS_GET(x) ((uint32_t)(x) & 0xFFFFU)
|
||||
#define AXIS_NONE (0xFFFFFFFFU)
|
||||
|
||||
#define NO_BTN ((uint16_t)0xFFFFU) // I hope no joypad will ever have this many buttons ... ;)
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "message.h"
|
||||
#include "rewind.h"
|
||||
#include "movie.h"
|
||||
#include "autosave.h"
|
||||
#include "netplay.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -41,6 +43,16 @@
|
||||
#define MAX_PLAYERS 5
|
||||
#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
|
||||
@@ -58,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;
|
||||
@@ -85,6 +98,7 @@ 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];
|
||||
@@ -94,6 +108,7 @@ struct settings
|
||||
unsigned rewind_granularity;
|
||||
|
||||
bool pause_nonactive;
|
||||
unsigned autosave_interval;
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
@@ -105,6 +120,8 @@ enum ssnes_game_type
|
||||
SSNES_CART_SUFAMI,
|
||||
};
|
||||
|
||||
|
||||
// All run-time- / command line flag-related globals go here.
|
||||
struct global
|
||||
{
|
||||
bool verbose;
|
||||
@@ -124,6 +141,8 @@ struct global
|
||||
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];
|
||||
@@ -161,17 +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,6 +24,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -82,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;
|
||||
@@ -99,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;
|
||||
}
|
||||
@@ -266,6 +295,7 @@ static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
GL_UNSIGNED_BYTE, head->output);
|
||||
|
||||
head = head->next;
|
||||
glFlush();
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
}
|
||||
font_renderer_free_output(&out);
|
||||
@@ -395,6 +425,8 @@ 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)
|
||||
@@ -442,6 +474,12 @@ 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);
|
||||
|
||||
@@ -462,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);
|
||||
|
||||
+27
-1
@@ -25,7 +25,6 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
#include <OpenGL/glext.h>
|
||||
#else
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
@@ -42,6 +41,25 @@
|
||||
|
||||
#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;
|
||||
@@ -59,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;
|
||||
@@ -180,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");
|
||||
@@ -198,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;
|
||||
}
|
||||
|
||||
+10
-1099
File diff suppressed because it is too large.
Load diff
@@ -20,6 +20,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "general.h"
|
||||
#include "dynamic.h"
|
||||
#include <assert.h>
|
||||
|
||||
// CRC32 implementation taken from BSNES source :)
|
||||
|
||||
@@ -87,6 +88,17 @@ 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
|
||||
@@ -127,6 +139,7 @@ static inline uint16_t swap_if_big16(uint16_t val)
|
||||
|
||||
static bool init_playback(bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
handle->playback = true;
|
||||
handle->file = fopen(path, "rb");
|
||||
if (!handle->file)
|
||||
{
|
||||
@@ -158,6 +171,8 @@ static bool init_playback(bsv_movie_t *handle, const char *path)
|
||||
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");
|
||||
@@ -196,6 +211,8 @@ static bool init_record(bsv_movie_t *handle, const char *path)
|
||||
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;
|
||||
@@ -208,6 +225,7 @@ void bsv_movie_free(bsv_movie_t *handle)
|
||||
if (handle->file)
|
||||
fclose(handle->file);
|
||||
free(handle->state);
|
||||
free(handle->frame_pos);
|
||||
free(handle);
|
||||
}
|
||||
}
|
||||
@@ -240,9 +258,58 @@ bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type)
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,14 @@ 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
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.2.2
|
||||
PACKAGE_VERSION=0.3.1
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
|
||||
+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
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ struct state_manager
|
||||
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)
|
||||
@@ -77,6 +78,11 @@ void state_manager_free(state_manager_t *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;
|
||||
@@ -126,7 +132,7 @@ static void generate_delta(state_manager_t *state, const void *data)
|
||||
const uint32_t *new_state = data;
|
||||
|
||||
state->buffer[state->top_ptr++] = 0; // For each separate delta, we have a 0 value sentinel in between.
|
||||
if (state->top_ptr == state->bottom_ptr) // Check if top_ptr and bottom_ptr crossed eachother, which means we need to delete old cruft.
|
||||
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++)
|
||||
@@ -156,6 +162,7 @@ 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;
|
||||
}
|
||||
|
||||
+123
-52
@@ -107,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;
|
||||
@@ -131,6 +132,7 @@ static void set_defaults(void)
|
||||
g_settings.rewind_buffer_size = rewind_buffer_size;
|
||||
g_settings.rewind_granularity = rewind_granularity;
|
||||
g_settings.pause_nonactive = pause_nonactive;
|
||||
g_settings.autosave_interval = autosave_interval;
|
||||
|
||||
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
|
||||
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
|
||||
@@ -144,8 +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;
|
||||
g_settings.input.netplay_client_swap_input = netplay_client_swap_input;
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
g_settings.input.joypad_map[i] = i;
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -162,6 +166,62 @@ 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
|
||||
@@ -195,35 +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");
|
||||
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("/.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;
|
||||
@@ -289,7 +321,24 @@ static void parse_config_file(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CG) || defined(HAVE_XML)
|
||||
if (config_get_string(conf, "video_shader_type", &tmp_str))
|
||||
{
|
||||
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
|
||||
|
||||
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");
|
||||
@@ -330,6 +379,7 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_INT(rewind_granularity, "rewind_granularity");
|
||||
|
||||
CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive");
|
||||
CONFIG_GET_INT(autosave_interval, "autosave_interval");
|
||||
|
||||
read_keybinds(conf);
|
||||
|
||||
@@ -372,6 +422,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
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)
|
||||
@@ -397,6 +448,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
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)
|
||||
@@ -422,6 +474,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
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)
|
||||
@@ -447,6 +500,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
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)
|
||||
@@ -472,6 +526,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
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)
|
||||
},
|
||||
};
|
||||
|
||||
@@ -512,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)
|
||||
@@ -545,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;
|
||||
@@ -559,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);
|
||||
@@ -570,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 == '+')
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "record/ffemu.h"
|
||||
#include "rewind.h"
|
||||
#include "movie.h"
|
||||
#include "netplay.h"
|
||||
#include <assert.h>
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
@@ -275,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.2"
|
||||
#define PACKAGE_VERSION "0.3.1"
|
||||
#endif
|
||||
|
||||
#include "config.features.h"
|
||||
@@ -320,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.");
|
||||
@@ -342,6 +339,10 @@ static void print_help(void)
|
||||
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.");
|
||||
@@ -351,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)
|
||||
@@ -359,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' },
|
||||
@@ -381,6 +394,10 @@ static void parse_input(int argc, char *argv[])
|
||||
{ "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 }
|
||||
};
|
||||
|
||||
@@ -398,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:P:" 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;
|
||||
|
||||
@@ -427,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':
|
||||
@@ -456,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':
|
||||
@@ -495,6 +515,32 @@ static void parse_input(int argc, char *argv[])
|
||||
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);
|
||||
@@ -507,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)
|
||||
@@ -522,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)
|
||||
{
|
||||
@@ -590,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);
|
||||
@@ -616,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);
|
||||
@@ -734,16 +775,115 @@ static void deinit_movie(void)
|
||||
bsv_movie_free(g_extern.bsv_movie);
|
||||
}
|
||||
|
||||
static void fill_pathnames(void)
|
||||
#define SSNES_DEFAULT_PORT 55435
|
||||
|
||||
static void init_netplay(void)
|
||||
{
|
||||
if (!g_extern.bsv_movie_playback)
|
||||
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "");
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -754,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.
|
||||
@@ -886,6 +1037,13 @@ static void check_input_rate(void)
|
||||
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;
|
||||
|
||||
@@ -895,9 +1053,15 @@ static void check_rewind(void)
|
||||
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());
|
||||
g_extern.frame_is_reverse = true;
|
||||
|
||||
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);
|
||||
@@ -906,7 +1070,7 @@ static void check_rewind(void)
|
||||
{
|
||||
static unsigned cnt = 0;
|
||||
cnt = (cnt + 1) % (g_settings.rewind_granularity ? g_settings.rewind_granularity : 1); // Avoid possible SIGFPE.
|
||||
if (cnt == 0)
|
||||
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);
|
||||
@@ -938,7 +1102,7 @@ static void check_movie_record(void)
|
||||
|
||||
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!", 2, 180);
|
||||
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");
|
||||
@@ -1011,23 +1175,27 @@ static void check_pause(void)
|
||||
|
||||
static void do_state_checks(void)
|
||||
{
|
||||
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)
|
||||
if (!g_extern.netplay)
|
||||
{
|
||||
check_stateslots();
|
||||
check_savestates();
|
||||
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_fullscreen();
|
||||
check_input_rate();
|
||||
|
||||
if (!g_extern.bsv_movie_playback)
|
||||
check_movie_record();
|
||||
}
|
||||
|
||||
|
||||
@@ -1048,27 +1216,32 @@ int main(int argc, char *argv[])
|
||||
if (!init_rom_file(g_extern.game_type))
|
||||
goto error;
|
||||
|
||||
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);
|
||||
|
||||
init_msg_queue();
|
||||
init_controllers();
|
||||
init_movie();
|
||||
|
||||
if (!g_extern.bsv_movie)
|
||||
{
|
||||
load_save_files();
|
||||
init_rewind();
|
||||
}
|
||||
|
||||
|
||||
init_netplay();
|
||||
init_drivers();
|
||||
|
||||
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();
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
init_recording();
|
||||
#endif
|
||||
|
||||
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
|
||||
init_autosave();
|
||||
|
||||
// Main loop
|
||||
for(;;)
|
||||
@@ -1083,7 +1256,23 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Run libsnes for one frame.
|
||||
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();
|
||||
@@ -1099,15 +1288,20 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
deinit_netplay();
|
||||
|
||||
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
|
||||
deinit_autosave();
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
deinit_recording();
|
||||
#endif
|
||||
|
||||
if (!g_extern.bsv_movie)
|
||||
{
|
||||
deinit_rewind();
|
||||
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
|
||||
save_files();
|
||||
}
|
||||
|
||||
if (!g_extern.netplay)
|
||||
deinit_rewind();
|
||||
|
||||
deinit_movie();
|
||||
deinit_msg_queue();
|
||||
|
||||
@@ -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
|
||||
@@ -31,9 +31,13 @@
|
||||
# Path to Cg shader.
|
||||
# video_cg_shader = "/path/to/cg/shader.cg"
|
||||
|
||||
# Path to bSNES-style XML shader (GLSL only). 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
|
||||
|
||||
@@ -90,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
|
||||
@@ -214,3 +220,10 @@
|
||||
|
||||
# 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
|
||||
Reference in new issue
Block a user