Compare commits

...
34 Commits
Author SHA1 Message Date
Themaister 0735398e64 Updates for 0.3. 2011-02-22 22:24:58 +01:00
Themaister ec2d10e069 Add default path to OSX and check in APPDATA for Win32. 2011-02-22 11:28:28 +01:00
Themaister 4cdb6151e5 Add netplay_client_swap_input. 2011-02-20 12:12:53 +01:00
Themaister e482b1bd18 Fix potential problem. Some cleanup. 2011-02-18 23:51:51 +01:00
Themaister 9be3c13a07 Hrm. This stuff is very error prone. :V 2011-02-18 15:50:57 +01:00
Themaister 8a4bcacf88 hrm... 2011-02-18 14:49:15 +01:00
Themaister 6b9b37b528 Should fix an extreme edge case. 2011-02-18 13:39:31 +01:00
Themaister 8e87c1ff19 Could this be it? :v 2011-02-18 03:42:43 +01:00
Themaister aa648c1a49 Hm... Still borked. Seems hard to fix. 2011-02-18 03:14:32 +01:00
Themaister 056a590671 Hm. More progress, but still not quite right. 2011-02-18 02:16:32 +01:00
Themaister 97f2503794 Some initial work on UDP networking. Still borked. 2011-02-18 02:01:47 +01:00
Themaister 143079efbe Should fix abrupt application termination on Unix when netplay ends. 2011-02-16 18:33:45 +01:00
Themaister 7bcc173c5f Should fix some issues with Win32 netplay. 2011-02-15 17:29:03 +01:00
Themaister 0485f783b3 Updates for Win32. 2011-02-15 16:42:55 +01:00
Themaister f3c07325bd Fix Windows build. 2011-02-15 15:57:59 +01:00
Themaister e8eabb0102 Update docs. 2011-02-15 15:42:29 +01:00
Themaister b73f5e77ff Cleanups, add --frames. 2011-02-15 15:32:26 +01:00
Themaister d07712b308 Woot. looks like it works o.O 2011-02-15 15:10:03 +01:00
Themaister 89ba64d27f Borked, borked... 2011-02-14 22:37:57 +01:00
Themaister 1a6f112d8a More changes to netplay. Still quite broken, but it's not far away! 2011-02-14 20:11:46 +01:00
Themaister 70e8681487 Started to work on a look-ahead algorithm.
Still very broken, but frame-by-frame sync works at least :)
2011-02-14 16:10:53 +01:00
Themaister 17ae0f72df Do not try to poll when we've disconnected. 2011-02-13 21:19:20 +01:00
Themaister 466485e62e Lots more error messages. 2011-02-13 20:24:54 +01:00
Themaister f6ee0845f2 Add some message. 2011-02-13 19:50:45 +01:00
Themaister df1eee1a74 Redundant... 2011-02-13 19:00:27 +01:00
Themaister 46b46ba0b1 Update docs a bit. 2011-02-13 18:47:48 +01:00
Themaister a763848644 Fix some issues with message queues. 2011-02-13 18:19:37 +01:00
Themaister 9ba2d6c9fa Send SRAM to client. 2011-02-13 17:45:14 +01:00
Themaister fd1367e257 Update build for Win32 2011-02-13 16:49:18 +01:00
Themaister 85dbc05ded Some start on netplay. :) 2011-02-13 16:40:24 +01:00
Themaister 1eea7786bc Should fix possible error where game can hang for prolonged time on
exit.
2011-02-11 19:54:42 +01:00
Themaister d039bedad4 Fix up some path handling with srm/states. 2011-02-11 14:27:19 +01:00
Themaister ad6719426c Fix up autosaving a bit. 2011-02-11 13:44:31 +01:00
Themaister 719e4942ce Add autosave possibility. 2011-02-10 21:16:59 +01:00
14 changed files with 1289 additions and 1192 deletions

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,144 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "autosave.h"
#include "SDL.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "general.h"
struct autosave
{
volatile bool quit;
SDL_mutex *lock;
SDL_mutex *cond_lock;
SDL_cond *cond;
SDL_Thread *thread;
void *buffer;
const void *snes_buffer;
const char *path;
size_t bufsize;
unsigned interval;
};
static int autosave_thread(void *data)
{
autosave_t *save = data;
while (!save->quit)
{
autosave_lock(save);
memcpy(save->buffer, save->snes_buffer, save->bufsize);
autosave_unlock(save);
// Should probably deal with this more elegantly.
FILE *file = fopen(save->path, "wb");
if (file)
{
SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path);
bool failed = false;
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
failed |= fflush(file) != 0;
failed |= fclose(file) != 0;
if (failed)
SSNES_WARN("Failed to autosave SRAM! Disk might be full.\n");
}
SDL_mutexP(save->cond_lock);
if (!save->quit)
SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000);
SDL_mutexV(save->cond_lock);
}
return 0;
}
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval)
{
autosave_t *handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
handle->bufsize = size;
handle->interval = interval;
handle->path = path;
handle->buffer = malloc(size);
handle->snes_buffer = data;
if (!handle->buffer)
{
free(handle);
return NULL;
}
handle->lock = SDL_CreateMutex();
handle->cond_lock = SDL_CreateMutex();
handle->cond = SDL_CreateCond();
handle->thread = SDL_CreateThread(autosave_thread, handle);
return handle;
}
void autosave_lock(autosave_t *handle)
{
SDL_mutexP(handle->lock);
}
void autosave_unlock(autosave_t *handle)
{
SDL_mutexV(handle->lock);
}
void autosave_free(autosave_t *handle)
{
SDL_mutexP(handle->cond_lock);
handle->quit = true;
SDL_mutexV(handle->cond_lock);
SDL_CondSignal(handle->cond);
SDL_WaitThread(handle->thread, NULL);
SDL_DestroyMutex(handle->lock);
SDL_DestroyMutex(handle->cond_lock);
SDL_DestroyCond(handle->cond);
free(handle->buffer);
free(handle);
}
void lock_autosave(void)
{
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (g_extern.autosave[i])
autosave_lock(g_extern.autosave[i]);
}
}
void unlock_autosave(void)
{
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
{
if (g_extern.autosave[i])
autosave_unlock(g_extern.autosave[i]);
}
}
+33
View File
@@ -0,0 +1,33 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_AUTOSAVE_H
#define __SSNES_AUTOSAVE_H
#include <stddef.h>
typedef struct autosave autosave_t;
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval);
void autosave_lock(autosave_t *handle);
void autosave_unlock(autosave_t *handle);
void autosave_free(autosave_t *handle);
void lock_autosave(void);
void unlock_autosave(void);
#endif
+6
View File
@@ -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
View File
@@ -47,7 +47,7 @@ Should this not be defined, \fBssnes\fR will look in platform specific paths to
/etc/ssnes.cfg (when installed), or ssnes.cfg in the source tarball serves as a skeleton configuration file.
.IP
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnesrc. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnes.cfg. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
.IP
Windows will look in ssnes.cfg in same folder where ssnes.exe resides.
@@ -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)
+24
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+685
View File
@@ -0,0 +1,685 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "netplay.h"
#include "general.h"
#include "dynamic.h"
#include "autosave.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <assert.h>
#ifdef _WIN32
#define _WIN32_WINNT 0x0501
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
// Woohoo, Winsock has headers from the STONE AGE! :D
#define close(x) closesocket(x)
#define CONST_CAST (const char*)
#define NONCONST_CAST (char*)
#else
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#define CONST_CAST
#define NONCONST_CAST
#endif
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
struct delta_frame
{
uint8_t *state;
uint16_t real_input_state;
uint16_t simulated_input_state;
bool is_simulated;
uint16_t self_state;
bool used_real;
};
#define UDP_FRAME_PACKETS 8
struct netplay
{
struct snes_callbacks cbs;
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
int udp_fd; // UDP connection for game state updates.
unsigned port; // Which port is governed by netplay?
bool has_connection;
struct delta_frame *buffer;
size_t buffer_size;
size_t self_ptr; // Ptr where we are now.
size_t other_ptr; // Points to the last reliable state that self ever had.
size_t read_ptr; // Ptr to where we are reading. Generally, other_ptr <= read_ptr <= self_ptr.
size_t tmp_ptr; // A temporary pointer used on replay.
size_t state_size;
size_t is_replay; // Are we replaying old frames?
bool can_poll; // We don't want to poll several times on a frame.
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
uint32_t frame_count;
uint32_t read_frame_count;
struct addrinfo *addr;
struct sockaddr_storage their_addr;
bool has_client_addr;
};
void input_poll_net(void)
{
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
{
netplay_callbacks(g_extern.netplay)->poll_cb();
netplay_poll(g_extern.netplay);
}
}
void video_frame_net(const uint16_t *data, unsigned width, unsigned height)
{
if (!netplay_should_skip(g_extern.netplay))
netplay_callbacks(g_extern.netplay)->frame_cb(data, width, height);
}
void audio_sample_net(uint16_t left, uint16_t right)
{
if (!netplay_should_skip(g_extern.netplay))
netplay_callbacks(g_extern.netplay)->sample_cb(left, right);
}
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
{
if (netplay_is_alive(g_extern.netplay))
return netplay_input_state(g_extern.netplay, port, device, index, id);
else
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
}
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
{
struct addrinfo hints, *res = NULL;
memset(&hints, 0, sizeof(hints));
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
hints.ai_family = AF_INET;
#else
hints.ai_family = AF_UNSPEC;
#endif
hints.ai_socktype = SOCK_STREAM;
if (!server)
hints.ai_flags = AI_PASSIVE;
char port_buf[16];
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
if (getaddrinfo(server, port_buf, &hints, &res) < 0)
return false;
if (!res)
return false;
handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (handle->fd < 0)
{
SSNES_ERR("Failed to init socket...\n");
if (res)
freeaddrinfo(res);
return false;
}
if (server)
{
if (connect(handle->fd, res->ai_addr, res->ai_addrlen) < 0)
{
SSNES_ERR("Failed to connect to server.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
}
else
{
int yes = 1;
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0)
{
SSNES_ERR("Failed to bind socket.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
int new_fd = accept(handle->fd, NULL, NULL);
if (new_fd < 0)
{
SSNES_ERR("Failed to accept socket.\n");
close(handle->fd);
freeaddrinfo(res);
return false;
}
close(handle->fd);
handle->fd = new_fd;
}
freeaddrinfo(res);
return true;
}
static bool init_udp_socket(netplay_t *handle, const char *server, uint16_t port)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
hints.ai_family = AF_INET;
#else
hints.ai_family = AF_UNSPEC;
#endif
hints.ai_socktype = SOCK_DGRAM;
if (!server)
hints.ai_flags = AI_PASSIVE;
char port_buf[16];
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
if (getaddrinfo(server, port_buf, &hints, &handle->addr) < 0)
return false;
if (!handle->addr)
return false;
handle->udp_fd = socket(handle->addr->ai_family, handle->addr->ai_socktype, handle->addr->ai_protocol);
if (handle->udp_fd < 0)
{
SSNES_ERR("Failed to init socket...\n");
return false;
}
if (!server)
{
// Note sure if we have to do this for UDP, but hey :)
int yes = 1;
setsockopt(handle->udp_fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
if (bind(handle->udp_fd, handle->addr->ai_addr, handle->addr->ai_addrlen) < 0)
{
SSNES_ERR("Failed to bind socket.\n");
close(handle->udp_fd);
}
freeaddrinfo(handle->addr);
handle->addr = NULL;
}
return true;
}
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
WSACleanup();
return false;
}
#else
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
#endif
if (!init_tcp_socket(handle, server, port))
return false;
if (!init_udp_socket(handle, server, port))
return false;
return true;
}
bool netplay_can_poll(netplay_t *handle)
{
return handle->can_poll;
}
static bool send_info(netplay_t *handle)
{
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
return false;
// Get SRAM data from Player 1 :)
uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
while (sram_size > 0)
{
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
if (ret <= 0)
{
SSNES_ERR("Failed to receive SRAM data from host.\n");
return false;
}
sram += ret;
sram_size -= ret;
}
return true;
}
static bool get_info(netplay_t *handle)
{
uint32_t header[3];
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
{
SSNES_ERR("Failed to receive header from client.\n");
return false;
}
if (g_extern.cart_crc != ntohl(header[0]))
{
SSNES_ERR("Cart CRC32s differ! Cannot use different games!\n");
return false;
}
if (psnes_serialize_size() != ntohl(header[1]))
{
SSNES_ERR("Serialization sizes differ, make sure you're using exact same libsnes implementations!\n");
return false;
}
if (psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM) != ntohl(header[2]))
{
SSNES_ERR("Cartridge SRAM sizes do not correspond!\n");
return false;
}
// Send SRAM data to our Player 2 :)
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
while (sram_size > 0)
{
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
if (ret <= 0)
{
SSNES_ERR("Failed to send SRAM data to client.\n");
return false;
}
sram += ret;
sram_size -= ret;
}
return true;
}
static void init_buffers(netplay_t *handle)
{
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
handle->state_size = psnes_serialize_size();
for (unsigned i = 0; i < handle->buffer_size; i++)
{
handle->buffer[i].state = malloc(handle->state_size);
handle->buffer[i].is_simulated = true;
}
}
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
{
netplay_t *handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
handle->cbs = *cb;
handle->port = server ? 0 : 1;
if (!init_socket(handle, server, port))
{
free(handle);
return NULL;
}
if (server)
{
if (!send_info(handle))
{
close(handle->fd);
free(handle);
return NULL;
}
}
else
{
if (!get_info(handle))
{
close(handle->fd);
free(handle);
return NULL;
}
}
handle->buffer_size = frames + 1;
init_buffers(handle);
handle->has_connection = true;
return handle;
}
bool netplay_is_alive(netplay_t *handle)
{
return handle->has_connection;
}
static bool send_chunk(netplay_t *handle)
{
const struct sockaddr *addr = NULL;
if (handle->addr)
addr = handle->addr->ai_addr;
else if (handle->has_client_addr)
addr = (const struct sockaddr*)&handle->their_addr;
if (addr)
{
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
}
return true;
}
#define MAX_RETRIES 16
static int poll_input(netplay_t *handle, bool block)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(handle->udp_fd, &fds);
struct timeval tv = {
.tv_sec = 0,
.tv_usec = block ? 500000 : 0
};
int i = 0;
do
{
if (select(handle->udp_fd + 1, &fds, NULL, NULL, &tv) < 0)
return -1;
if (FD_ISSET(handle->udp_fd, &fds))
return 1;
if (block && !send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
} while (i++ < MAX_RETRIES && block);
if (block)
return -1;
return 0;
}
// Grab our own input state and send this over the network.
static bool get_self_input_state(netplay_t *handle)
{
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
uint32_t state = 0;
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
{
snes_input_state_t cb = handle->cbs.state_cb;
for (int i = 0; i <= 11; i++)
{
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
state |= tmp ? 1 << i : 0;
}
}
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
if (!send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
ptr->self_state = state;
handle->self_ptr = NEXT_PTR(handle->self_ptr);
return true;
}
// TODO: Somewhat better prediction. :P
static void simulate_input(netplay_t *handle)
{
size_t ptr = PREV_PTR(handle->self_ptr);
size_t prev = PREV_PTR(handle->read_ptr);
handle->buffer[ptr].simulated_input_state = handle->buffer[prev].real_input_state;
handle->buffer[ptr].is_simulated = true;
handle->buffer[ptr].used_real = false;
}
static void parse_packet(netplay_t *handle, uint32_t *buffer, unsigned size)
{
for (unsigned i = 0; i < size * 2; i++)
buffer[i] = ntohl(buffer[i]);
for (unsigned i = 0; i < size && handle->read_frame_count <= handle->frame_count; i++)
{
uint32_t frame = buffer[2 * i];
uint32_t state = buffer[2 * i + 1];
if (frame == handle->read_frame_count)
{
handle->buffer[handle->read_ptr].is_simulated = false;
handle->buffer[handle->read_ptr].real_input_state = state;
handle->read_ptr = NEXT_PTR(handle->read_ptr);
handle->read_frame_count++;
}
}
}
static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
{
socklen_t addrlen = sizeof(handle->their_addr);
if (recvfrom(handle->udp_fd, NONCONST_CAST buffer, size, 0, (struct sockaddr*)&handle->their_addr, &addrlen) != size)
return false;
handle->has_client_addr = true;
return true;
}
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
bool netplay_poll(netplay_t *handle)
{
if (!handle->has_connection)
return false;
handle->can_poll = false;
if (!get_self_input_state(handle))
return false;
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
if (handle->frame_count == 0)
{
handle->buffer[0].used_real = true;
handle->buffer[0].is_simulated = false;
handle->buffer[0].real_input_state = 0;
handle->read_ptr = NEXT_PTR(handle->read_ptr);
handle->read_frame_count++;
return true;
}
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
if (handle->buffer_size > 1)
{
assert(handle->other_ptr != handle->self_ptr);
assert(handle->read_ptr != handle->self_ptr);
}
assert(handle->other_ptr == handle->read_ptr);
// We might have reached the end of the buffer, where we simply have to block.
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
if (res == -1)
{
handle->has_connection = false;
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
return false;
}
if (res == 1)
{
uint32_t first_read = handle->read_frame_count;
do
{
uint32_t buffer[UDP_FRAME_PACKETS * 2];
if (!receive_data(handle, buffer, sizeof(buffer)))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
handle->has_connection = false;
return false;
}
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
} while ((handle->read_frame_count <= handle->frame_count) &&
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
(first_read == handle->read_frame_count)) == 1);
}
else
{
// Cannot allow this. Should not happen though.
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
return false;
}
}
if (handle->read_ptr != handle->self_ptr)
simulate_input(handle);
else
handle->buffer[PREV_PTR(handle->self_ptr)].used_real = true;
return true;
}
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
{
uint16_t input_state = 0;
size_t ptr = 0;
if (handle->is_replay)
ptr = handle->tmp_ptr;
else
ptr = PREV_PTR(handle->self_ptr);
if ((port ? 1 : 0) == handle->port)
{
if (handle->buffer[ptr].is_simulated)
input_state = handle->buffer[ptr].simulated_input_state;
else
input_state = handle->buffer[ptr].real_input_state;
}
else
input_state = handle->buffer[ptr].self_state;
return ((1 << id) & input_state) ? 1 : 0;
}
void netplay_free(netplay_t *handle)
{
close(handle->fd);
close(handle->udp_fd);
for (unsigned i = 0; i < handle->buffer_size; i++)
free(handle->buffer[i].state);
free(handle->buffer);
if (handle->addr)
freeaddrinfo(handle->addr);
free(handle);
}
const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
{
return &handle->cbs;
}
bool netplay_should_skip(netplay_t *handle)
{
return handle->is_replay && handle->has_connection;
}
void netplay_pre_frame(netplay_t *handle)
{
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
handle->can_poll = true;
}
// Here we check if we have new input and replay from recorded input.
void netplay_post_frame(netplay_t *handle)
{
handle->frame_count++;
// Nothing to do...
if (handle->other_ptr == handle->read_ptr)
return;
// Skip ahead if we predicted correctly. Skip until our simulation failed.
while (handle->other_ptr != handle->read_ptr)
{
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
break;
handle->other_ptr = NEXT_PTR(handle->other_ptr);
}
if (handle->other_ptr != handle->read_ptr)
{
// Replay frames
handle->is_replay = true;
handle->tmp_ptr = handle->other_ptr;
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
while (handle->tmp_ptr != handle->self_ptr)
{
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
lock_autosave();
psnes_run();
unlock_autosave();
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
}
handle->other_ptr = handle->read_ptr;
handle->is_replay = false;
}
}
+61
View File
@@ -0,0 +1,61 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_NETPLAY_H
#define __SSNES_NETPLAY_H
#include <stdint.h>
#include <stdbool.h>
#include <libsnes.hpp>
void input_poll_net(void);
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id);
void video_frame_net(const uint16_t *data, unsigned width, unsigned height);
void audio_sample_net(uint16_t left, uint16_t right);
typedef struct netplay netplay_t;
struct snes_callbacks
{
snes_video_refresh_t frame_cb;
snes_audio_sample_t sample_cb;
snes_input_poll_t poll_cb;
snes_input_state_t state_cb;
};
// Creates a new netplay handle. A NULL host means we're hosting (player 1). :)
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb);
void netplay_free(netplay_t *handle);
// Call this before running snes_run()
void netplay_pre_frame(netplay_t *handle);
// Call this after running snes_run()
void netplay_post_frame(netplay_t *handle);
// Checks if input port/index is controlled by netplay or not.
bool netplay_is_alive(netplay_t *handle);
bool netplay_poll(netplay_t *handle);
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id);
// If we're fast-forward replaying to resync, check if we should actually show frame.
bool netplay_should_skip(netplay_t *handle);
bool netplay_can_poll(netplay_t *handle);
const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
#endif
+1 -1
View File
@@ -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
View File
@@ -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);
+234 -58
View File
@@ -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();
+7
View File
@@ -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