mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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)
|
||||
|
||||
|
||||
+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.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
|
||||
@@ -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)
|
||||
@@ -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,8 @@
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 25 // Needs to be increased every time there are new binds added.
|
||||
#define SSNES_NO_JOYPAD 0xFFFF
|
||||
|
||||
// All config related settings go here.
|
||||
struct settings
|
||||
{
|
||||
struct
|
||||
@@ -85,6 +89,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 +99,7 @@ struct settings
|
||||
unsigned rewind_granularity;
|
||||
|
||||
bool pause_nonactive;
|
||||
unsigned autosave_interval;
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
@@ -105,6 +111,7 @@ enum ssnes_game_type
|
||||
SSNES_CART_SUFAMI,
|
||||
};
|
||||
|
||||
// All run-time- / command line flag-related globals go here.
|
||||
struct global
|
||||
{
|
||||
bool verbose;
|
||||
@@ -124,6 +131,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 +170,32 @@ struct global
|
||||
|
||||
msg_queue_t *msg_queue;
|
||||
|
||||
// Rewind support.
|
||||
state_manager_t *state_manager;
|
||||
void *state_buf;
|
||||
bool frame_is_reverse;
|
||||
|
||||
// Movie record support
|
||||
bsv_movie_t *bsv_movie;
|
||||
char bsv_movie_path[256];
|
||||
bool bsv_movie_end;
|
||||
bool bsv_movie_playback;
|
||||
|
||||
// Pausing support
|
||||
bool is_paused;
|
||||
|
||||
// Autosave support.
|
||||
autosave_t *autosave[2];
|
||||
|
||||
// Netplay.
|
||||
netplay_t *netplay;
|
||||
char netplay_server[256];
|
||||
bool netplay_enable;
|
||||
bool netplay_is_client;
|
||||
unsigned netplay_sync_frames;
|
||||
uint16_t netplay_port;
|
||||
|
||||
// FFmpeg record.
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
char record_path[256];
|
||||
|
||||
+10
-1099
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,685 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "netplay.h"
|
||||
#include "general.h"
|
||||
#include "dynamic.h"
|
||||
#include "autosave.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
#define CONST_CAST (const char*)
|
||||
#define NONCONST_CAST (char*)
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
#define CONST_CAST
|
||||
#define NONCONST_CAST
|
||||
#endif
|
||||
|
||||
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
|
||||
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
|
||||
|
||||
struct delta_frame
|
||||
{
|
||||
uint8_t *state;
|
||||
|
||||
uint16_t real_input_state;
|
||||
uint16_t simulated_input_state;
|
||||
bool is_simulated;
|
||||
uint16_t self_state;
|
||||
bool used_real;
|
||||
};
|
||||
|
||||
#define UDP_FRAME_PACKETS 8
|
||||
|
||||
struct netplay
|
||||
{
|
||||
struct snes_callbacks cbs;
|
||||
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
|
||||
int udp_fd; // UDP connection for game state updates.
|
||||
unsigned port; // Which port is governed by netplay?
|
||||
bool has_connection;
|
||||
|
||||
struct delta_frame *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
size_t self_ptr; // Ptr where we are now.
|
||||
size_t other_ptr; // Points to the last reliable state that self ever had.
|
||||
size_t read_ptr; // Ptr to where we are reading. Generally, other_ptr <= read_ptr <= self_ptr.
|
||||
size_t tmp_ptr; // A temporary pointer used on replay.
|
||||
|
||||
size_t state_size;
|
||||
|
||||
size_t is_replay; // Are we replaying old frames?
|
||||
bool can_poll; // We don't want to poll several times on a frame.
|
||||
|
||||
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||
uint32_t frame_count;
|
||||
uint32_t read_frame_count;
|
||||
struct addrinfo *addr;
|
||||
struct sockaddr_storage their_addr;
|
||||
bool has_client_addr;
|
||||
};
|
||||
|
||||
void input_poll_net(void)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
|
||||
{
|
||||
netplay_callbacks(g_extern.netplay)->poll_cb();
|
||||
netplay_poll(g_extern.netplay);
|
||||
}
|
||||
}
|
||||
|
||||
void video_frame_net(const uint16_t *data, unsigned width, unsigned height)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay))
|
||||
netplay_callbacks(g_extern.netplay)->frame_cb(data, width, height);
|
||||
}
|
||||
|
||||
void audio_sample_net(uint16_t left, uint16_t right)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay))
|
||||
netplay_callbacks(g_extern.netplay)->sample_cb(left, right);
|
||||
}
|
||||
|
||||
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
if (netplay_is_alive(g_extern.netplay))
|
||||
return netplay_input_state(g_extern.netplay, port, device, index, id);
|
||||
else
|
||||
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||
}
|
||||
|
||||
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||
hints.ai_family = AF_INET;
|
||||
#else
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
#endif
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (!server)
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_buf[16];
|
||||
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
|
||||
if (getaddrinfo(server, port_buf, &hints, &res) < 0)
|
||||
return false;
|
||||
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (handle->fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to init socket...\n");
|
||||
|
||||
if (res)
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (connect(handle->fd, res->ai_addr, res->ai_addrlen) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to connect to server.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int yes = 1;
|
||||
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
int new_fd = accept(handle->fd, NULL, NULL);
|
||||
if (new_fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to accept socket.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
close(handle->fd);
|
||||
handle->fd = new_fd;
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_udp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||
hints.ai_family = AF_INET;
|
||||
#else
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
#endif
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
if (!server)
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_buf[16];
|
||||
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
|
||||
if (getaddrinfo(server, port_buf, &hints, &handle->addr) < 0)
|
||||
return false;
|
||||
|
||||
if (!handle->addr)
|
||||
return false;
|
||||
|
||||
handle->udp_fd = socket(handle->addr->ai_family, handle->addr->ai_socktype, handle->addr->ai_protocol);
|
||||
if (handle->udp_fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to init socket...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
// Note sure if we have to do this for UDP, but hey :)
|
||||
int yes = 1;
|
||||
setsockopt(handle->udp_fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->udp_fd, handle->addr->ai_addr, handle->addr->ai_addrlen) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->udp_fd);
|
||||
}
|
||||
|
||||
freeaddrinfo(handle->addr);
|
||||
handle->addr = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
|
||||
{
|
||||
WSACleanup();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
|
||||
#endif
|
||||
|
||||
if (!init_tcp_socket(handle, server, port))
|
||||
return false;
|
||||
if (!init_udp_socket(handle, server, port))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool netplay_can_poll(netplay_t *handle)
|
||||
{
|
||||
return handle->can_poll;
|
||||
}
|
||||
|
||||
static bool send_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
return false;
|
||||
|
||||
// Get SRAM data from Player 1 :)
|
||||
uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
{
|
||||
SSNES_ERR("Failed to receive header from client.\n");
|
||||
return false;
|
||||
}
|
||||
if (g_extern.cart_crc != ntohl(header[0]))
|
||||
{
|
||||
SSNES_ERR("Cart CRC32s differ! Cannot use different games!\n");
|
||||
return false;
|
||||
}
|
||||
if (psnes_serialize_size() != ntohl(header[1]))
|
||||
{
|
||||
SSNES_ERR("Serialization sizes differ, make sure you're using exact same libsnes implementations!\n");
|
||||
return false;
|
||||
}
|
||||
if (psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM) != ntohl(header[2]))
|
||||
{
|
||||
SSNES_ERR("Cartridge SRAM sizes do not correspond!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send SRAM data to our Player 2 :)
|
||||
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
{
|
||||
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void init_buffers(netplay_t *handle)
|
||||
{
|
||||
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
|
||||
handle->state_size = psnes_serialize_size();
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
{
|
||||
handle->buffer[i].state = malloc(handle->state_size);
|
||||
handle->buffer[i].is_simulated = true;
|
||||
}
|
||||
}
|
||||
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
|
||||
{
|
||||
netplay_t *handle = calloc(1, sizeof(*handle));
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->cbs = *cb;
|
||||
handle->port = server ? 0 : 1;
|
||||
|
||||
if (!init_socket(handle, server, port))
|
||||
{
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (!send_info(handle))
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get_info(handle))
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
handle->buffer_size = frames + 1;
|
||||
|
||||
init_buffers(handle);
|
||||
handle->has_connection = true;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
bool netplay_is_alive(netplay_t *handle)
|
||||
{
|
||||
return handle->has_connection;
|
||||
}
|
||||
|
||||
static bool send_chunk(netplay_t *handle)
|
||||
{
|
||||
const struct sockaddr *addr = NULL;
|
||||
if (handle->addr)
|
||||
addr = handle->addr->ai_addr;
|
||||
else if (handle->has_client_addr)
|
||||
addr = (const struct sockaddr*)&handle->their_addr;
|
||||
|
||||
if (addr)
|
||||
{
|
||||
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_RETRIES 16
|
||||
|
||||
static int poll_input(netplay_t *handle, bool block)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->udp_fd, &fds);
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = block ? 500000 : 0
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
if (select(handle->udp_fd + 1, &fds, NULL, NULL, &tv) < 0)
|
||||
return -1;
|
||||
|
||||
if (FD_ISSET(handle->udp_fd, &fds))
|
||||
return 1;
|
||||
|
||||
if (block && !send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
} while (i++ < MAX_RETRIES && block);
|
||||
|
||||
if (block)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Grab our own input state and send this over the network.
|
||||
static bool get_self_input_state(netplay_t *handle)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
|
||||
|
||||
uint32_t state = 0;
|
||||
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
|
||||
{
|
||||
snes_input_state_t cb = handle->cbs.state_cb;
|
||||
for (int i = 0; i <= 11; i++)
|
||||
{
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
|
||||
state |= tmp ? 1 << i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
|
||||
|
||||
if (!send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr->self_state = state;
|
||||
handle->self_ptr = NEXT_PTR(handle->self_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Somewhat better prediction. :P
|
||||
static void simulate_input(netplay_t *handle)
|
||||
{
|
||||
size_t ptr = PREV_PTR(handle->self_ptr);
|
||||
size_t prev = PREV_PTR(handle->read_ptr);
|
||||
|
||||
handle->buffer[ptr].simulated_input_state = handle->buffer[prev].real_input_state;
|
||||
handle->buffer[ptr].is_simulated = true;
|
||||
handle->buffer[ptr].used_real = false;
|
||||
}
|
||||
|
||||
static void parse_packet(netplay_t *handle, uint32_t *buffer, unsigned size)
|
||||
{
|
||||
for (unsigned i = 0; i < size * 2; i++)
|
||||
buffer[i] = ntohl(buffer[i]);
|
||||
|
||||
for (unsigned i = 0; i < size && handle->read_frame_count <= handle->frame_count; i++)
|
||||
{
|
||||
uint32_t frame = buffer[2 * i];
|
||||
uint32_t state = buffer[2 * i + 1];
|
||||
|
||||
if (frame == handle->read_frame_count)
|
||||
{
|
||||
handle->buffer[handle->read_ptr].is_simulated = false;
|
||||
handle->buffer[handle->read_ptr].real_input_state = state;
|
||||
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
||||
handle->read_frame_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
|
||||
{
|
||||
socklen_t addrlen = sizeof(handle->their_addr);
|
||||
if (recvfrom(handle->udp_fd, NONCONST_CAST buffer, size, 0, (struct sockaddr*)&handle->their_addr, &addrlen) != size)
|
||||
return false;
|
||||
handle->has_client_addr = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
|
||||
bool netplay_poll(netplay_t *handle)
|
||||
{
|
||||
if (!handle->has_connection)
|
||||
return false;
|
||||
|
||||
handle->can_poll = false;
|
||||
|
||||
if (!get_self_input_state(handle))
|
||||
return false;
|
||||
|
||||
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
|
||||
if (handle->frame_count == 0)
|
||||
{
|
||||
handle->buffer[0].used_real = true;
|
||||
handle->buffer[0].is_simulated = false;
|
||||
handle->buffer[0].real_input_state = 0;
|
||||
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
||||
handle->read_frame_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
|
||||
if (handle->buffer_size > 1)
|
||||
{
|
||||
assert(handle->other_ptr != handle->self_ptr);
|
||||
assert(handle->read_ptr != handle->self_ptr);
|
||||
}
|
||||
assert(handle->other_ptr == handle->read_ptr);
|
||||
|
||||
// We might have reached the end of the buffer, where we simply have to block.
|
||||
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
|
||||
if (res == -1)
|
||||
{
|
||||
handle->has_connection = false;
|
||||
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res == 1)
|
||||
{
|
||||
uint32_t first_read = handle->read_frame_count;
|
||||
do
|
||||
{
|
||||
uint32_t buffer[UDP_FRAME_PACKETS * 2];
|
||||
if (!receive_data(handle, buffer, sizeof(buffer)))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
|
||||
|
||||
} while ((handle->read_frame_count <= handle->frame_count) &&
|
||||
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
|
||||
(first_read == handle->read_frame_count)) == 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot allow this. Should not happen though.
|
||||
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (handle->read_ptr != handle->self_ptr)
|
||||
simulate_input(handle);
|
||||
else
|
||||
handle->buffer[PREV_PTR(handle->self_ptr)].used_real = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
uint16_t input_state = 0;
|
||||
size_t ptr = 0;
|
||||
|
||||
if (handle->is_replay)
|
||||
ptr = handle->tmp_ptr;
|
||||
else
|
||||
ptr = PREV_PTR(handle->self_ptr);
|
||||
|
||||
if ((port ? 1 : 0) == handle->port)
|
||||
{
|
||||
if (handle->buffer[ptr].is_simulated)
|
||||
input_state = handle->buffer[ptr].simulated_input_state;
|
||||
else
|
||||
input_state = handle->buffer[ptr].real_input_state;
|
||||
}
|
||||
else
|
||||
input_state = handle->buffer[ptr].self_state;
|
||||
|
||||
return ((1 << id) & input_state) ? 1 : 0;
|
||||
}
|
||||
|
||||
void netplay_free(netplay_t *handle)
|
||||
{
|
||||
close(handle->fd);
|
||||
close(handle->udp_fd);
|
||||
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
free(handle->buffer[i].state);
|
||||
|
||||
free(handle->buffer);
|
||||
if (handle->addr)
|
||||
freeaddrinfo(handle->addr);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
|
||||
{
|
||||
return &handle->cbs;
|
||||
}
|
||||
|
||||
bool netplay_should_skip(netplay_t *handle)
|
||||
{
|
||||
return handle->is_replay && handle->has_connection;
|
||||
}
|
||||
|
||||
void netplay_pre_frame(netplay_t *handle)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
|
||||
handle->can_poll = true;
|
||||
}
|
||||
|
||||
// Here we check if we have new input and replay from recorded input.
|
||||
void netplay_post_frame(netplay_t *handle)
|
||||
{
|
||||
handle->frame_count++;
|
||||
|
||||
// Nothing to do...
|
||||
if (handle->other_ptr == handle->read_ptr)
|
||||
return;
|
||||
|
||||
// Skip ahead if we predicted correctly. Skip until our simulation failed.
|
||||
while (handle->other_ptr != handle->read_ptr)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
|
||||
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
|
||||
break;
|
||||
handle->other_ptr = NEXT_PTR(handle->other_ptr);
|
||||
}
|
||||
|
||||
if (handle->other_ptr != handle->read_ptr)
|
||||
{
|
||||
// Replay frames
|
||||
handle->is_replay = true;
|
||||
handle->tmp_ptr = handle->other_ptr;
|
||||
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
|
||||
while (handle->tmp_ptr != handle->self_ptr)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
|
||||
lock_autosave();
|
||||
psnes_run();
|
||||
unlock_autosave();
|
||||
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
|
||||
}
|
||||
handle->other_ptr = handle->read_ptr;
|
||||
handle->is_replay = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
|
||||
+62
-29
@@ -131,6 +131,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 +145,10 @@ static void set_defaults(void)
|
||||
memcpy(g_settings.input.binds[4], snes_keybinds_5, sizeof(snes_keybinds_5));
|
||||
|
||||
g_settings.input.axis_threshold = AXIS_THRESHOLD;
|
||||
g_settings.input.netplay_client_swap_input = netplay_client_swap_input;
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
g_settings.input.joypad_map[i] = i;
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -162,6 +165,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 +254,7 @@ static void parse_config_file(void)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Just do something for now.
|
||||
conf = config_file_new("ssnes.cfg");
|
||||
#else
|
||||
const char *xdg = getenv("XDG_CONFIG_HOME");
|
||||
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;
|
||||
@@ -290,6 +321,7 @@ static void parse_config_file(void)
|
||||
#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 +362,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);
|
||||
|
||||
|
||||
@@ -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"
|
||||
#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.
|
||||
@@ -938,7 +1089,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 +1162,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_rewind();
|
||||
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,15 +1203,7 @@ 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)
|
||||
@@ -1065,10 +1212,22 @@ int main(int argc, char *argv[])
|
||||
init_rewind();
|
||||
}
|
||||
|
||||
init_netplay();
|
||||
init_drivers();
|
||||
|
||||
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 +1242,19 @@ 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);
|
||||
|
||||
psnes_run();
|
||||
|
||||
if (g_extern.netplay)
|
||||
netplay_post_frame(g_extern.netplay);
|
||||
|
||||
unlock_autosave();
|
||||
}
|
||||
else
|
||||
{
|
||||
input_poll();
|
||||
@@ -1099,11 +1270,16 @@ 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)
|
||||
if (!g_extern.bsv_movie_playback && !g_extern.netplay_is_client)
|
||||
{
|
||||
deinit_rewind();
|
||||
save_files();
|
||||
|
||||
@@ -214,3 +214,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