Compare commits

...
22 Commits
Author SHA1 Message Date
Themaister d304960c21 0.4.1-beta I suppose. 2011-04-22 03:17:31 +02:00
Themaister 8da13b716a Simple font rendering with SDL. 2011-04-22 03:13:09 +02:00
Themaister a6c08a24d7 Fix 32-bit 2011-04-21 14:30:54 +02:00
Themaister 6713272809 Hrm... Seems to work, except for 32-bit -> 32-bit <_< 2011-04-21 13:12:45 +02:00
Themaister 8d7cf23b40 Meh, SDL performs like shit :p 2011-04-21 03:39:03 +02:00
Themaister d317a9773f Start on SDL video driver. 2011-04-21 03:23:44 +02:00
Themaister e8806c48c9 Default keybinds and stuff. 2011-04-17 16:55:31 +02:00
Themaister abd7bab43a Cheat support... Somewhat. 2011-04-17 16:53:19 +02:00
Themaister 66464c2cce Parsing seems to work at least. 2011-04-17 15:58:18 +02:00
Themaister 092fa9d531 Get initial cheat support down. 2011-04-17 13:30:59 +02:00
Themaister 1005d7fa9b SHA256 support. Now onto cheats ... 2011-04-17 12:29:58 +02:00
Themaister 079549cbd2 Fix an edge case with strlcat(). 2011-04-11 21:28:03 +02:00
Themaister 0f0a4d0a5e Fix fullscreen state flickering with XVideo. 2011-04-09 13:15:14 +02:00
Themaister f0bbeb901d Fix some error handling for XVideo. 2011-04-08 20:53:11 +02:00
Themaister 8027a25016 Fix include issue in windows. 2011-04-07 21:31:58 +02:00
Themaister a1426c96f8 Update for Win32. 2011-04-07 21:26:02 +02:00
Themaister a4c901df57 Bit more logging when saving SRAM to be sure. 0.4! :D 2011-04-07 21:20:11 +02:00
Themaister f2cc19d1a7 Some fixups for SGB etc. 2011-04-07 21:06:31 +02:00
Themaister ad55829890 Convert to strlcpy/strlcat. More sane. 2011-04-03 22:29:36 +02:00
Themaister 6b8eea6363 Start with strlcpy/strlcat ... 2011-04-03 22:16:59 +02:00
Themaister b2d214ee79 Should fix path issues with UPS patching.
When filename had two dots ('.') in it,
using fill_pathname on g_extern.basename was wrong.
Added fill_pathname_noext to countermeasure this.
2011-04-03 21:43:25 +02:00
Themaister f5fb113563 Should fix case where we have 1 pass and scale attributes. 2011-03-30 23:09:29 +02:00
24 changed files with 1180 additions and 98 deletions

No files matched your search

+7 -3
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 autosave.o gfx/gfx_common.o ups.o
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
HEADERS = $(wildcard */*.h) $(wildcard *.h)
@@ -67,7 +67,7 @@ ifeq ($(HAVE_PULSE), 1)
endif
ifeq ($(HAVE_SDL), 1)
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
DEFINES += $(SDL_CFLAGS)
LIBS += $(SDL_LIBS)
ifeq ($(OSX),1)
@@ -92,7 +92,7 @@ ifeq ($(HAVE_CG), 1)
endif
ifeq ($(HAVE_XML), 1)
OBJ += gfx/shader_glsl.o
OBJ += gfx/shader_glsl.o sha256.o cheats.o
LIBS += $(XML_LIBS)
DEFINES += $(XML_CFLAGS)
endif
@@ -119,6 +119,10 @@ else
LIBS += $(libsnes)
endif
ifeq ($(HAVE_STRL), 1)
DEFINES += -DHAVE_STRL
endif
ifneq ($(V),1)
Q := @
endif
+4 -4
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 autosave.o gfx/gfx_common.o ups.o
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
CC = gcc
@@ -33,7 +33,7 @@ else
endif
ifeq ($(HAVE_SDL), 1)
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
LIBS += $(SDL_LIBS) -lopengl32
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
endif
@@ -50,7 +50,7 @@ ifeq ($(HAVE_RSOUND), 1)
endif
ifeq ($(HAVE_XML), 1)
OBJ += gfx/shader_glsl.o
OBJ += gfx/shader_glsl.o sha256.o cheats.o
DEFINES += $(XML_CFLAGS) -DHAVE_XML
LIBS += -lxml2
endif
@@ -116,7 +116,7 @@ clean:
rm -f tools/*.o
dist: all
zip -r ssnes-win32-0.4-beta.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)
zip -r ssnes-win32-0.4.1-beta.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
+261
View File
@@ -0,0 +1,261 @@
/* 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 "cheats.h"
#include "sha256.h"
#include "dynamic.h"
#include "general.h"
#include "strl.h"
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
struct cheat
{
char *desc;
bool state;
char *code;
};
struct cheat_manager
{
struct cheat *cheats;
unsigned ptr;
unsigned size;
unsigned buf_size;
};
static char* strcat_alloc(char *dest, const char *input)
{
size_t dest_len = dest ? strlen(dest) : 0;
size_t input_len = strlen(input);
size_t required_len = dest_len + input_len + 1;
char *output = realloc(dest, required_len);
assert(output);
if (dest)
strlcat(output, input, required_len);
else
strlcpy(output, input, required_len);
return output;
}
static void xml_grab_cheat(struct cheat *cht, xmlNodePtr ptr)
{
memset(cht, 0, sizeof(struct cheat));
bool first = true;
for (xmlNodePtr node = ptr; node; node = node->next)
{
if (strcmp((const char*)node->name, "description") == 0)
{
cht->desc = (char*)xmlNodeGetContent(node);
}
else if (strcmp((const char*)node->name, "code") == 0)
{
if (!first)
cht->code = strcat_alloc(cht->code, "+");
xmlChar *code = xmlNodeGetContent(node);
assert(code);
cht->code = strcat_alloc(cht->code, (const char*)code);
xmlFree(code);
first = false;
}
}
}
static void xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr)
{
xmlNodePtr node = NULL;
for (node = ptr; node; node = node->next)
{
if (strcmp((const char*)node->name, "name") == 0)
{
xmlChar *name = xmlNodeGetContent(node);
if (name)
{
SSNES_LOG("Found cheat for game: \"%s\"\n", name);
xmlFree(name);
}
}
else if (strcmp((const char*)node->name, "cheat") == 0)
{
if (handle->size == handle->buf_size)
{
handle->buf_size *= 2;
handle->cheats = realloc(handle->cheats, handle->buf_size * sizeof(struct cheat));
assert(handle->cheats);
}
xml_grab_cheat(&handle->cheats[handle->size++], node->children);
}
}
}
cheat_manager_t* cheat_manager_new(const char *path)
{
psnes_cheat_reset();
xmlParserCtxtPtr ctx = NULL;
xmlDocPtr doc = NULL;
cheat_manager_t *handle = calloc(1, sizeof(handle));
if (!handle)
return NULL;
handle->buf_size = 32;
handle->cheats = malloc(handle->buf_size * sizeof(struct cheat));
if (!handle->cheats)
{
handle->buf_size = 0;
goto error;
}
ctx = xmlNewParserCtxt();
if (!ctx)
goto error;
doc = xmlCtxtReadFile(ctx, path, NULL, 0);
if (!doc)
{
SSNES_ERR("Failed to parse XML file: %s\n", path);
goto error;
}
if (ctx->valid == 0)
{
SSNES_ERR("Cannot validate XML file: %s\n", path);
goto error;
}
xmlNodePtr head = xmlDocGetRootElement(doc);
xmlNodePtr cur = NULL;
for (cur = head; cur; cur = cur->next)
{
if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "database") == 0)
break;
}
if (!cur)
goto error;
for (cur = cur->children; cur; cur = cur->next)
{
if (cur->type != XML_ELEMENT_NODE)
continue;
if (strcmp((const char*)cur->name, "cartridge") == 0)
{
xmlChar *sha256 = xmlGetProp(cur, (const xmlChar*)"sha256");
if (!sha256)
continue;
if (memcmp(sha256, g_extern.sha256, 64) == 0)
{
xmlFree(sha256);
break;
}
xmlFree(sha256);
}
}
if (!cur)
goto error;
xml_grab_cheats(handle, cur->children);
xmlFreeDoc(doc);
xmlFreeParserCtxt(ctx);
return handle;
error:
cheat_manager_free(handle);
if (doc)
xmlFreeDoc(doc);
if (ctx)
xmlFreeParserCtxt(ctx);
return NULL;
}
void cheat_manager_free(cheat_manager_t *handle)
{
if (!handle)
return;
if (handle->cheats)
{
for (unsigned i = 0; i < handle->size; i++)
{
xmlFree(handle->cheats[i].desc);
free(handle->cheats[i].code);
}
free(handle->cheats);
}
free(handle);
}
static void cheat_manager_update(cheat_manager_t *handle)
{
msg_queue_clear(g_extern.msg_queue);
char msg[256];
snprintf(msg, sizeof(msg), "Cheat: #%u [%s]: %s", handle->ptr, handle->cheats[handle->ptr].state ? "ON" : "OFF", handle->cheats[handle->ptr].desc);
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
SSNES_LOG("%s\n", msg);
}
void cheat_manager_toggle(cheat_manager_t *handle)
{
handle->cheats[handle->ptr].state ^= true;
unsigned index = 0;
psnes_cheat_reset();
for (unsigned i = 0; i < handle->size; i++)
{
if (handle->cheats[handle->ptr].state)
psnes_cheat_set(index++, true, handle->cheats[handle->ptr].code);
}
cheat_manager_update(handle);
}
void cheat_manager_index_next(cheat_manager_t *handle)
{
handle->ptr = (handle->ptr + 1) % handle->size;
cheat_manager_update(handle);
}
void cheat_manager_index_prev(cheat_manager_t *handle)
{
if (handle->ptr == 0)
handle->ptr = handle->size - 1;
else
handle->ptr--;
cheat_manager_update(handle);
}
+30
View File
@@ -0,0 +1,30 @@
/* 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_CHEATS_H
#define __SSNES_CHEATS_H
typedef struct cheat_manager cheat_manager_t;
cheat_manager_t* cheat_manager_new(const char *path);
void cheat_manager_free(cheat_manager_t *handle);
void cheat_manager_index_next(cheat_manager_t *handle);
void cheat_manager_index_prev(cheat_manager_t *handle);
void cheat_manager_toggle(cheat_manager_t *handle);
#endif
+7
View File
@@ -44,6 +44,7 @@
///////////////// Drivers
#define VIDEO_GL 0
#define VIDEO_XVIDEO 11
#define VIDEO_SDL 13
////////////////////////
#define AUDIO_RSOUND 1
#define AUDIO_OSS 2
@@ -107,6 +108,9 @@ static const bool fullscreen = false; // To start in Fullscreen or not
static const unsigned fullscreen_x = 0; // Fullscreen resolution. A value of 0 uses the desktop resolution.
static const unsigned fullscreen_y = 0;
// Force 16-bit colors.
static const bool force_16bit = false;
// Video VSYNC (recommended)
static const bool vsync = true;
@@ -223,6 +227,9 @@ static const struct snes_keybind snes_keybinds_1[] = {
{ SSNES_RESET, SDLK_h, NO_BTN, AXIS_NONE },
{ SSNES_SHADER_NEXT, SDLK_m, NO_BTN, AXIS_NONE },
{ SSNES_SHADER_PREV, SDLK_n, NO_BTN, AXIS_NONE },
{ SSNES_CHEAT_INDEX_PLUS, SDLK_y, NO_BTN, AXIS_NONE },
{ SSNES_CHEAT_INDEX_MINUS, SDLK_t, NO_BTN, AXIS_NONE },
{ SSNES_CHEAT_TOGGLE, SDLK_u, NO_BTN, AXIS_NONE },
{ -1 }
};
+1
View File
@@ -61,6 +61,7 @@ static const audio_driver_t *audio_drivers[] = {
static const video_driver_t *video_drivers[] = {
#ifdef HAVE_SDL
&video_gl,
&video_sdl,
#endif
#ifdef HAVE_XVIDEO
&video_xvideo,
+5 -1
View File
@@ -42,6 +42,9 @@ enum
SSNES_RESET,
SSNES_SHADER_NEXT,
SSNES_SHADER_PREV,
SSNES_CHEAT_INDEX_PLUS,
SSNES_CHEAT_INDEX_MINUS,
SSNES_CHEAT_TOGGLE
};
@@ -108,7 +111,7 @@ typedef struct input_driver
typedef struct video_driver
{
void* (*init)(video_info_t *video, const input_driver_t **input, void **input_data);
void* (*init)(const video_info_t *video, const input_driver_t **input, void **input_data);
// Should the video driver act as an input driver as well? :) The video init might preinitialize an input driver to override the settings in case the video driver relies on input driver for event handling, e.g.
bool (*frame)(void* data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg); // msg is for showing a message on the screen along with the video frame.
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
@@ -153,6 +156,7 @@ extern const audio_driver_t audio_xa;
extern const audio_driver_t audio_pulse;
extern const video_driver_t video_gl;
extern const video_driver_t video_xvideo;
extern const video_driver_t video_sdl;
extern const input_driver_t input_sdl;
extern const input_driver_t input_x;
////////////////////////////////////////////////
+7
View File
@@ -57,6 +57,9 @@ void (*psnes_set_input_state)(snes_input_state_t);
void (*psnes_reset)(void);
void (*psnes_run)(void);
void (*psnes_cheat_reset)(void);
void (*psnes_cheat_set)(unsigned, bool, const char*);
const char *(*psnes_library_id)(void) = NULL;
unsigned (*psnes_library_revision_minor)(void);
unsigned (*psnes_library_revision_major)(void);
@@ -111,6 +114,8 @@ static void load_dynamic(void)
OPT_SYM(snes_library_id);
SYM(snes_library_revision_minor);
SYM(snes_library_revision_major);
SYM(snes_cheat_reset);
SYM(snes_cheat_set);
SYM(snes_reset);
SYM(snes_run);
SYM(snes_get_region);
@@ -145,6 +150,8 @@ static void set_statics(void)
SSYM(snes_set_input_state);
SSYM(snes_library_revision_minor);
SSYM(snes_library_revision_major);
SSYM(snes_cheat_reset);
SSYM(snes_cheat_set);
SSYM(snes_reset);
SSYM(snes_run);
SSYM(snes_get_region);
+3
View File
@@ -37,6 +37,9 @@ extern void (*psnes_set_audio_sample)(snes_audio_sample_t);
extern void (*psnes_set_input_poll)(snes_input_poll_t);
extern void (*psnes_set_input_state)(snes_input_state_t);
extern void (*psnes_cheat_reset)(void);
extern void (*psnes_cheat_set)(unsigned, bool, const char*);
extern const char* (*psnes_library_id)(void);
extern unsigned (*psnes_library_revision_minor)(void);
extern unsigned (*psnes_library_revision_major)(void);
+18 -30
View File
@@ -24,6 +24,11 @@
#include "dynamic.h"
#include "movie.h"
#include "ups.h"
#include "strl.h"
#ifdef HAVE_XML
#include "sha256.h"
#endif
#ifdef _WIN32
#include <io.h>
@@ -164,9 +169,9 @@ static ssize_t read_rom_file(FILE* file, void** buf)
}
}
else if (*g_extern.ups_name)
SSNES_LOG("Could not find UPS patch.\n");
SSNES_LOG("Could not find UPS patch in: \"%s\".\n", g_extern.ups_name);
// Remove SMC header if present.
// Remove copier header if present (512 first bytes).
if ((ret & 0x7fff) == 512)
{
memmove(ret_buf, ret_buf + 512, ret - 512);
@@ -174,6 +179,10 @@ static ssize_t read_rom_file(FILE* file, void** buf)
}
g_extern.cart_crc = crc32_calculate(ret_buf, ret);
#ifdef HAVE_XML
sha256_hash(g_extern.sha256, ret_buf, ret);
SSNES_LOG("SHA256 sum: %s\n", g_extern.sha256);
#endif
*buf = ret_buf;
return ret;
}
@@ -257,7 +266,7 @@ void save_ram_file(const char* path, int type)
size_t size = psnes_get_memory_size(type);
uint8_t *data = psnes_get_memory_data(type);
if ( data && size > 0 )
if (data && size > 0)
dump_to_file(path, data, size);
}
@@ -276,14 +285,7 @@ static bool load_sgb_rom(void)
goto error;
}
extra_rom = fopen(g_extern.gb_rom_path, "rb");
if (!extra_rom)
{
SSNES_ERR("Couldn't open GameBoy ROM!\n");
goto error;
}
if ((extra_rom_len = read_rom_file(extra_rom, &extra_rom_buf)) == -1)
if ((extra_rom_len = read_file(g_extern.gb_rom_path, &extra_rom_buf)) == -1)
{
SSNES_ERR("Cannot read GameBoy rom.\n");
goto error;
@@ -330,14 +332,7 @@ static bool load_bsx_rom(bool slotted)
goto error;
}
extra_rom = fopen(g_extern.bsx_rom_path, "rb");
if (!extra_rom)
{
SSNES_ERR("Couldn't open BSX game rom!\n");
goto error;
}
if ((extra_rom_len = read_rom_file(extra_rom, &extra_rom_buf)) == -1)
if ((extra_rom_len = read_file(g_extern.bsx_rom_path, &extra_rom_buf)) == -1)
{
SSNES_ERR("Cannot read BSX game rom.\n");
goto error;
@@ -404,16 +399,9 @@ static bool load_sufami_rom(void)
{
if (strlen(roms[i]) > 0)
{
extra_rom[i] = fopen(roms[i], "rb");
if (!extra_rom[i])
if ((extra_rom_len[i] = read_file(roms[i], &extra_rom_buf[i])) == -1)
{
SSNES_ERR("Couldn't open BSX game rom!\n");
goto error;
}
if ((extra_rom_len[i] = read_rom_file(extra_rom[i], &extra_rom_buf[i])) == -1)
{
SSNES_ERR("Cannot read BSX game rom.\n");
SSNES_ERR("Cannot read Sufami game rom.\n");
goto error;
}
}
@@ -537,8 +525,8 @@ char** dir_list_new(const char *dir, const char *ext)
wchar_t wchar_buf[MAX_PATH + 1];
char utf8_buf[MAX_PATH + 3];
strncpy(utf8_buf, dir, MAX_PATH);
strcat(utf8_buf, "/*");
strlcpy(utf8_buf, dir, sizeof(utf8_buf));
strlcat(utf8_buf, "/*", sizeof(utf8_buf));
utf8_buf[MAX_PATH + 2] = '\0';
int ret = MultiByteToWideChar(CP_UTF8, 0, utf8_buf, strlen(utf8_buf), wchar_buf, MAX_PATH);
+10 -1
View File
@@ -29,6 +29,7 @@
#include "autosave.h"
#include "netplay.h"
#include "dynamic.h"
#include "cheats.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -42,7 +43,7 @@
#define MAX_PLAYERS 5
#define MAX_BINDS 28 // Needs to be increased every time there are new binds added.
#define MAX_BINDS 31 // Needs to be increased every time there are new binds added.
#define SSNES_NO_JOYPAD 0xFFFF
enum ssnes_shader_type
@@ -85,6 +86,7 @@ struct settings
float msg_pos_x;
float msg_pos_y;
bool force_16bit;
} video;
struct
@@ -110,6 +112,7 @@ struct settings
} input;
char libsnes[256];
char cheat_database[256];
bool rewind_enable;
unsigned rewind_buffer_size;
@@ -243,6 +246,12 @@ struct global
size_t size;
size_t ptr;
} shader_dir;
char sha256[65];
#ifdef HAVE_XML
cheat_manager_t *cheat;
#endif
};
void parse_config(void);
+1 -1
View File
@@ -35,7 +35,7 @@ bool gfx_window_title(char *buf, size_t size)
if (frames == 0)
{
gettimeofday(&tv, NULL);
snprintf(buf, size, g_extern.title_buf);
snprintf(buf, size, "%s", g_extern.title_buf);
ret = true;
}
+15 -11
View File
@@ -320,7 +320,15 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
{
#ifdef HAVE_FBO
if (!g_settings.video.render_to_texture && gl_shader_num() <= 1)
if (!g_settings.video.render_to_texture && gl_shader_num() == 0)
return;
struct gl_fbo_scale scale, scale_last;
gl_shader_scale(1, &scale);
gl_shader_scale(gl_shader_num(), &scale_last);
// No need to use FBOs.
if (gl_shader_num() == 1 && !scale.valid && !g_settings.video.render_to_texture)
return;
if (!load_fbo_proc())
@@ -330,13 +338,12 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
}
gl->fbo_pass = gl_shader_num() - 1;
struct gl_fbo_scale scale;
gl_shader_scale(gl_shader_num(), &scale);
if (scale.valid)
if (scale_last.valid)
gl->fbo_pass++;
gl_shader_scale(1, &scale);
if (gl->fbo_pass <= 0)
gl->fbo_pass = 1;
if (!scale.valid)
{
scale.scale_x = g_settings.video.fbo_scale_x;
@@ -385,9 +392,6 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
SSNES_LOG("Creating FBO 0 @ %ux%u\n", gl->fbo_rect[0].width, gl->fbo_rect[0].height);
if (gl->fbo_pass <= 0)
gl->fbo_pass = 1;
for (int i = 1; i < gl->fbo_pass; i++)
{
gl_shader_scale(i + 1, &gl->fbo_scale[i]);
@@ -889,7 +893,7 @@ static void gl_set_nonblock_state(void *data, bool state)
}
}
static void* gl_init(video_info_t *video, const input_driver_t **input, void **input_data)
static void* gl_init(const video_info_t *video, const input_driver_t **input, void **input_data)
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
return NULL;
@@ -903,7 +907,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
if (!SDL_SetVideoMode(video->width, video->height, g_settings.video.force_16bit ? 16 : 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
return NULL;
// Remove that ugly mouse :D
+365
View File
@@ -0,0 +1,365 @@
/* 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 "SDL.h"
#include "driver.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "general.h"
#include "input/ssnes_sdl_input.h"
#include "gfx_common.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_FREETYPE
#include "fonts.h"
#endif
typedef struct sdl_video sdl_video_t;
struct sdl_video
{
SDL_Surface *screen, *buffer;
bool quitting;
bool rgb32;
#ifdef HAVE_FREETYPE
font_renderer_t *font;
#endif
};
static void sdl_gfx_free(void *data)
{
sdl_video_t *vid = data;
if (!vid)
return;
if (vid->buffer)
SDL_FreeSurface(vid->screen);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
#ifdef HAVE_FREETYPE
if (vid->font)
font_renderer_free(vid->font);
#endif
free(vid);
}
static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font_size)
{
#ifdef HAVE_FREETYPE
if (*font_path)
{
vid->font = font_renderer_new(font_path, font_size);
if (!vid->font)
SSNES_WARN("Failed to init font.\n");
}
#else
(void)vid;
(void)font_path;
(void)font_size;
#endif
}
// Not very optimized, but hey :D
static void sdl_render_msg_15(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height)
{
#ifdef HAVE_FREETYPE
struct font_output_list out;
font_renderer_msg(vid->font, msg, &out);
struct font_output *head = out.head;
int base_x = g_settings.video.msg_pos_x * width;
int base_y = (1.0 - g_settings.video.msg_pos_y) * height;
while (head)
{
int rbase_x = base_x + head->off_x;
int rbase_y = base_y - head->off_y;
if (rbase_y >= 0)
{
for (int y = 0; y < head->height && (y + rbase_y) < height; y++)
{
if (rbase_x < 0)
continue;
const uint8_t *a = head->output + head->pitch * y;
uint16_t *out = (uint16_t*)buffer->pixels + (rbase_y - head->height + y) * (buffer->pitch >> 1) + rbase_x;
for (int x = 0; x < head->width && (x + rbase_x) < width; x++)
{
unsigned blend = a[x];
unsigned out_pix = out[x];
unsigned r = (out_pix >> 10) & 0x1f;
unsigned g = (out_pix >> 5) & 0x1f;
unsigned b = (out_pix >> 0) & 0x1f;
unsigned out_r = (r * (256 - blend) + 0x1f * blend) >> 8;
unsigned out_g = (g * (256 - blend) + 0x1f * blend) >> 8;
unsigned out_b = (b * (256 - blend) + 0x1f * blend) >> 8;
out[x] = (out_r << 10) | (out_g << 5) | out_b;
}
}
}
head = head->next;
}
font_renderer_free_output(&out);
#else
(void)vid;
(void)buffer;
(void)msg;
#endif
}
static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height)
{
#ifdef HAVE_FREETYPE
struct font_output_list out;
font_renderer_msg(vid->font, msg, &out);
struct font_output *head = out.head;
int base_x = g_settings.video.msg_pos_x * width;
int base_y = (1.0 - g_settings.video.msg_pos_y) * height;
while (head)
{
int rbase_x = base_x + head->off_x;
int rbase_y = base_y - head->off_y;
if (rbase_y >= 0)
{
for (int y = 0; y < head->height && (y + rbase_y) < height; y++)
{
if (rbase_x < 0)
continue;
const uint8_t *a = head->output + head->pitch * y;
uint32_t *out = (uint32_t*)buffer->pixels + (rbase_y - head->height + y) * (buffer->pitch >> 2) + rbase_x;
for (int x = 0; x < head->width && (x + rbase_x) < width; x++)
{
unsigned blend = a[x];
unsigned out_pix = out[x];
unsigned r = (out_pix >> 16) & 0xff;
unsigned g = (out_pix >> 8) & 0xff;
unsigned b = (out_pix >> 0) & 0xff;
unsigned out_r = (r * (256 - blend) + 0xff * blend) >> 8;
unsigned out_g = (g * (256 - blend) + 0xff * blend) >> 8;
unsigned out_b = (b * (256 - blend) + 0xff * blend) >> 8;
out[x] = (out_r << 16) | (out_g << 8) | out_b;
}
}
}
head = head->next;
}
font_renderer_free_output(&out);
#else
(void)vid;
(void)buffer;
(void)msg;
#endif
}
static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data)
{
SDL_InitSubSystem(SDL_INIT_VIDEO);
sdl_video_t *vid = calloc(1, sizeof(*vid));
if (!vid)
return NULL;
const SDL_VideoInfo *video_info = SDL_GetVideoInfo();
assert(video_info);
unsigned full_x = video_info->current_w;
unsigned full_y = video_info->current_h;
SSNES_LOG("Detecting desktop resolution %ux%u.\n", full_x, full_y);
vid->screen = SDL_SetVideoMode(video->width, video->height, (g_settings.video.force_16bit || !video->rgb32) ? 15 : 32, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
if (!vid->screen)
{
SSNES_ERR("Failed to init SDL surface.\n");
goto error;
}
SDL_ShowCursor(SDL_DISABLE);
if (g_settings.video.force_16bit || !video->rgb32)
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 15,
0x7c00, 0x03e0, 0x001f, 0);
else
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 32,
0, 0, 0, 0);
if (!vid->buffer)
{
SSNES_ERR("SDL_CreateRGBSurface failed: %s\n", SDL_GetError());
goto error;
}
sdl_input_t *sdl_input = input_sdl.init();
if (sdl_input)
{
sdl_input->quitting = &vid->quitting;
*input = &input_sdl;
*input_data = sdl_input;
}
else
*input = NULL;
vid->rgb32 = video->rgb32;
sdl_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);
return vid;
error:
sdl_gfx_free(vid);
return NULL;
}
static inline uint16_t conv_pixel(uint32_t pix)
{
uint16_t r = (pix & 0xf8000000) >> 17;
uint16_t g = (pix & 0x00f80000) >> 14;
uint16_t b = (pix & 0x0000f800) >> 11;
return r | g | b;
}
static void convert_32bit_15bit(uint16_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch)
{
for (unsigned y = 0; y < height; y++)
{
for (unsigned x = 0; x < width; x++)
{
out[x] = conv_pixel(input[x]);
}
out += outpitch >> 1;
input += pitch >> 2;
}
}
static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
{
sdl_video_t *vid = data;
if (SDL_MUSTLOCK(vid->buffer))
SDL_LockSurface(vid->buffer);
// :(
// 15-bit -> 15-bit
if (!vid->rgb32)
{
for (unsigned y = 0; y < height; y++)
{
uint16_t *dest = (uint16_t*)vid->buffer->pixels + ((y * vid->buffer->pitch) >> 1);
const uint16_t *src = (const uint16_t*)frame + ((y * pitch) >> 1);
memcpy(dest, src, width * sizeof(uint16_t));
}
}
// 32-bit -> 15-bit
else if (vid->rgb32 && g_settings.video.force_16bit)
{
convert_32bit_15bit(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch);
}
// 32-bit -> 32-bit
else
{
for (unsigned y = 0; y < height; y++)
{
uint32_t *dest = (uint32_t*)vid->buffer->pixels + ((y * vid->buffer->pitch) >> 2);
const uint32_t *src = (const uint32_t*)frame + ((y * pitch) >> 2);
for (unsigned x = 0; x < width; x++)
dest[x] = (src[x] >> 8) | 0xff000000;
}
}
if (msg)
{
if (!vid->rgb32 || g_settings.video.force_16bit)
sdl_render_msg_15(vid, vid->buffer, msg, width, height);
else
sdl_render_msg_32(vid, vid->buffer, msg, width, height);
}
if (SDL_MUSTLOCK(vid->buffer))
SDL_UnlockSurface(vid->buffer);
SDL_Rect src = {
.x = 0,
.y = 0,
.w = width,
.h = height
};
SDL_Rect dest = {
.x = 0,
.y = 0,
.w = vid->screen->w,
.h = vid->screen->h
};
SDL_SoftStretch(vid->buffer, &src, vid->screen, &dest);
char buf[128];
if (gfx_window_title(buf, sizeof(buf)))
SDL_WM_SetCaption(buf, NULL);
SDL_Flip(vid->screen);
return true;
}
static void sdl_gfx_set_nonblock_state(void *data, bool state)
{
(void)data; // Can SDL even do this?
(void)state;
}
static bool sdl_gfx_alive(void *data)
{
sdl_video_t *vid = data;
return !vid->quitting;
}
static bool sdl_gfx_focus(void *data)
{
(void)data;
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
}
const video_driver_t video_sdl = {
.init = sdl_gfx_init,
.frame = sdl_gfx_frame,
.alive = sdl_gfx_alive,
.set_nonblock_state = sdl_gfx_set_nonblock_state,
.focus = sdl_gfx_focus,
.free = sdl_gfx_free,
.ident = "sdl"
};
+5 -5
View File
@@ -77,7 +77,7 @@ static void xv_set_nonblock_state(void *data, bool state)
{
xv_t *xv = data;
Atom atom = XInternAtom(xv->display, "XV_SYNC_TO_VBLANK", true);
if (atom != None && xv->port >= 0)
if (atom != None && xv->port)
XvSetPortAttribute(xv->display, xv->port, atom, !state);
else
SSNES_WARN("Failed to set SYNC_TO_VBLANK attribute.\n");
@@ -177,7 +177,7 @@ static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
{
xv->font = font_renderer_new(font_path, font_size);
if (!xv->font)
SSNES_WARN("Failed to init font\n");
SSNES_WARN("Failed to init font.\n");
}
#endif
}
@@ -298,7 +298,7 @@ static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned
}
static void* xv_init(video_info_t *video, const input_driver_t **input, void **input_data)
static void* xv_init(const video_info_t *video, const input_driver_t **input, void **input_data)
{
xv_t *xv = calloc(1, sizeof(*xv));
if (!xv)
@@ -315,7 +315,7 @@ static void* xv_init(video_info_t *video, const input_driver_t **input, void **i
xv->keep_aspect = video->force_aspect;
// Find an appropriate Xv port.
xv->port = -1;
xv->port = 0;
XvAdaptorInfo *adaptor_info;
unsigned adaptor_count = 0;
XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &adaptor_count, &adaptor_info);
@@ -333,7 +333,7 @@ static void* xv_init(video_info_t *video, const input_driver_t **input, void **i
}
XvFreeAdaptorInfo(adaptor_info);
if (xv->port < 0)
if (xv->port == 0)
{
SSNES_ERR("XVideo: Failed to find valid XvPort.\n");
goto error;
+3 -1
View File
@@ -61,8 +61,10 @@ check_lib DYNAMIC -ldl dlopen
check_pkgconf FREETYPE freetype2
check_lib XVIDEO -lXv XvShmCreateImage
check_lib STRL -lc strlcpy
# Creates config.mk and config.h.
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO"
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL"
create_config_make config.mk $VARS
create_config_header config.h $VARS
+1 -1
View File
@@ -1,7 +1,7 @@
. qb/qb.params.sh
PACKAGE_NAME=ssnes
PACKAGE_VERSION=0.4-beta
PACKAGE_VERSION=0.4.1-beta
# Adds a command line opt to ./configure --help
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
+28 -6
View File
@@ -18,7 +18,7 @@
#include "general.h"
#include "conf/config_file.h"
#include <assert.h>
#include <string.h>
#include "strl.h"
#include "config.def.h"
#ifdef HAVE_CONFIG_H
@@ -47,6 +47,9 @@ static void set_defaults(void)
case VIDEO_XVIDEO:
def_video = "xvideo";
break;
case VIDEO_SDL:
def_video = "sdl";
break;
default:
break;
}
@@ -94,17 +97,18 @@ static void set_defaults(void)
}
if (def_video)
strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1);
strlcpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver));
if (def_audio)
strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1);
strlcpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver));
if (def_input)
strncpy(g_settings.input.driver, def_input, sizeof(g_settings.input.driver) - 1);
strlcpy(g_settings.input.driver, def_input, sizeof(g_settings.input.driver));
g_settings.video.xscale = xscale;
g_settings.video.yscale = yscale;
g_settings.video.fullscreen = fullscreen;
g_settings.video.fullscreen_x = fullscreen_x;
g_settings.video.fullscreen_y = fullscreen_y;
g_settings.video.force_16bit = force_16bit;
g_settings.video.vsync = vsync;
g_settings.video.smooth = video_smooth;
g_settings.video.force_aspect = force_aspect;
@@ -129,7 +133,7 @@ static void set_defaults(void)
g_settings.audio.in_rate = in_rate;
g_settings.audio.rate_step = audio_rate_step;
if (audio_device)
strncpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device));
strlcpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device));
g_settings.audio.latency = out_latency;
g_settings.audio.sync = audio_sync;
@@ -244,7 +248,7 @@ static config_file_t *open_default_config_file(void)
#define CONFIG_GET_STRING(var, key) do { \
if (config_get_string(conf, key, &tmp_str)) \
{ \
strncpy(g_settings.var, tmp_str, sizeof(g_settings.var) - 1); \
strlcpy(g_settings.var, tmp_str, sizeof(g_settings.var)); \
free(tmp_str); \
} \
} while(0)
@@ -282,6 +286,7 @@ static void parse_config_file(void)
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
CONFIG_GET_BOOL(video.force_16bit, "video_force_16bit");
CONFIG_GET_BOOL(video.vsync, "video_vsync");
CONFIG_GET_BOOL(video.smooth, "video_smooth");
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
@@ -369,6 +374,8 @@ static void parse_config_file(void)
CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive");
CONFIG_GET_INT(autosave_interval, "autosave_interval");
CONFIG_GET_STRING(cheat_database, "cheat_database_path");
read_keybinds(conf);
config_file_free(conf);
@@ -414,6 +421,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
},
{
DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -443,6 +453,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
},
{
DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -472,6 +485,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
},
{
DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -501,6 +517,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
},
{
DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A)
@@ -530,6 +549,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
},
};
+180
View File
@@ -0,0 +1,180 @@
/* 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/>.
*/
// SHA256 implementation from bSNES. Written by valditx.
//
#include "sha256.h"
#include <string.h>
#include <stdio.h>
#define PTR(t, a) ((t*)(a))
#define SWAP32(x) ((uint32_t)( \
(((uint32_t)(x) & 0x000000ff) << 24) | \
(((uint32_t)(x) & 0x0000ff00) << 8) | \
(((uint32_t)(x) & 0x00ff0000) >> 8) | \
(((uint32_t)(x) & 0xff000000) >> 24) \
))
#define ST32(a, d) *PTR(uint32_t, a) = (d)
#define ST32BE(a, d) ST32(a, SWAP32(d))
#define LD32(a) *PTR(uint32_t, a)
#define LD32BE(a) SWAP32(LD32(a))
#define LSL32(x, n) ((uint32_t)(x) << (n))
#define LSR32(x, n) ((uint32_t)(x) >> (n))
#define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n)))
// First 32 bits of the fractional parts of the square roots of the first 8 primes 2..19
static const uint32_t T_H[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
};
// First 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311
static const uint32_t T_K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};
struct sha256_ctx
{
uint8_t in[64];
unsigned inlen;
uint32_t w[64];
uint32_t h[8];
uint64_t len;
};
static void sha256_init(struct sha256_ctx *p)
{
memset(p, 0, sizeof(struct sha256_ctx));
memcpy(p->h, T_H, sizeof(T_H));
}
static void sha256_block(struct sha256_ctx *p)
{
unsigned i;
uint32_t s0, s1;
uint32_t a, b, c, d, e, f, g, h;
uint32_t t1, t2, maj, ch;
for (i = 0; i < 16; i++)
p->w[i] = LD32BE(p->in + i * 4);
for (i = 16; i < 64; i++)
{
s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3);
s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10);
p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1;
}
a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3];
e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7];
for (i = 0; i < 64; i++)
{
s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22);
maj = (a & b) ^ (a & c) ^ (b & c);
t2 = s0 + maj;
s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25);
ch = (e & f) ^ (~e & g);
t1 = h + s1 + ch + T_K[i] + p->w[i];
h = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d;
p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h;
// Next block
p->inlen = 0;
}
static void sha256_chunk(struct sha256_ctx *p, const uint8_t *s, unsigned len)
{
unsigned l;
p->len += len;
while (len)
{
l = 64 - p->inlen;
l = (len < l) ? len : l;
memcpy(p->in + p->inlen, s, l);
s += l;
p->inlen += l;
len -= l;
if (p->inlen == 64)
sha256_block(p);
}
}
static void sha256_final(struct sha256_ctx *p)
{
uint64_t len;
p->in[p->inlen++] = 0x80;
if (p->inlen > 56)
{
memset(p->in + p->inlen, 0, 64 - p->inlen);
sha256_block(p);
}
memset(p->in + p->inlen, 0, 56 - p->inlen);
len = p->len << 3;
ST32BE(p->in + 56, len >> 32);
ST32BE(p->in + 60, len);
sha256_block(p);
}
static void sha256_subhash(struct sha256_ctx *p, uint32_t *t)
{
for (unsigned i = 0; i < 8; i++)
ST32BE(t++, p->h[i]);
}
void sha256_hash(char *out, const uint8_t *in, size_t size)
{
struct sha256_ctx sha;
union
{
uint32_t u32[8];
uint8_t u8[32];
} shahash;
sha256_init(&sha);
sha256_chunk(&sha, in, size);
sha256_final(&sha);
sha256_subhash(&sha, shahash.u32);
for (unsigned i = 0; i < 32; i++)
snprintf(out + 2 * i, 3, "%02x", (unsigned)shahash.u8[i]);
}
+28
View File
@@ -0,0 +1,28 @@
/* 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_SHA256_H
#define __SSNES_SHA256_H
#include <stdint.h>
#include <stddef.h>
// Hashes sha256 and outputs a human readable string for comparing with the cheat XML values.
void sha256_hash(char *out, const uint8_t *in, size_t size);
#endif
+100 -34
View File
@@ -31,6 +31,8 @@
#include "rewind.h"
#include "movie.h"
#include "netplay.h"
#include "strl.h"
#include "cheats.h"
#include <assert.h>
#ifdef HAVE_SRC
#include <samplerate.h>
@@ -220,16 +222,22 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
return res;
}
static void fill_pathname(char *out_path, char *in_path, const char *replace)
static void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size)
{
char tmp_path[strlen(in_path) + 1];
strcpy(tmp_path, in_path);
strlcpy(tmp_path, in_path, sizeof(tmp_path));
char *tok = NULL;
tok = strrchr(tmp_path, '.');
if (tok != NULL)
*tok = '\0';
strcpy(out_path, tmp_path);
strcat(out_path, replace);
assert(strlcpy(out_path, tmp_path, size) < size);
assert(strlcat(out_path, replace, size) < size);
}
static void fill_pathname_noext(char *out_path, const char *in_path, const char *replace, size_t size)
{
assert(strlcpy(out_path, in_path, size) < size);
assert(strlcat(out_path, replace, size) < size);
}
#ifdef _WIN32
@@ -241,7 +249,7 @@ static void fill_pathname(char *out_path, char *in_path, const char *replace)
#endif
#ifdef _WIN32
#define PACKAGE_VERSION "0.4-beta"
#define PACKAGE_VERSION "0.4.1-beta"
#endif
#include "config.features.h"
@@ -320,7 +328,7 @@ static void set_basename(const char *path)
char *dst = strrchr(tmp, '.');
if (dst)
*dst = '\0';
strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1);
strlcpy(g_extern.basename, tmp, sizeof(g_extern.basename));
}
static void parse_input(int argc, char *argv[])
@@ -406,37 +414,37 @@ static void parse_input(int argc, char *argv[])
break;
case 's':
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1);
strlcpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm));
g_extern.has_set_save_path = true;
break;
case 'g':
strncpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path) - 1);
strlcpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path));
g_extern.game_type = SSNES_CART_SGB;
break;
case 'b':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path));
g_extern.game_type = SSNES_CART_BSX;
break;
case 'B':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
strlcpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path));
g_extern.game_type = SSNES_CART_BSX_SLOTTED;
break;
case 'Y':
strncpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]) - 1);
strlcpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]));
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'Z':
strncpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]) - 1);
strlcpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]));
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'S':
strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1);
strlcpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name));
g_extern.has_set_state_path = true;
break;
@@ -461,19 +469,19 @@ static void parse_input(int argc, char *argv[])
#ifdef HAVE_CONFIGFILE
case 'c':
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
strlcpy(g_extern.config_path, optarg, sizeof(g_extern.config_path));
break;
#endif
#ifdef HAVE_FFMPEG
case 'r':
strncpy(g_extern.record_path, optarg, sizeof(g_extern.record_path) - 1);
strlcpy(g_extern.record_path, optarg, sizeof(g_extern.record_path));
g_extern.recording = true;
break;
#endif
case 'P':
strncpy(g_extern.bsv_movie_path, optarg, sizeof(g_extern.bsv_movie_path) - 1);
strlcpy(g_extern.bsv_movie_path, optarg, sizeof(g_extern.bsv_movie_path));
g_extern.bsv_movie_playback = true;
break;
@@ -483,7 +491,7 @@ static void parse_input(int argc, char *argv[])
case 'C':
g_extern.netplay_enable = true;
strncpy(g_extern.netplay_server, optarg, sizeof(g_extern.netplay_server) - 1);
strlcpy(g_extern.netplay_server, optarg, sizeof(g_extern.netplay_server));
break;
case 'F':
@@ -493,7 +501,7 @@ static void parse_input(int argc, char *argv[])
break;
case 'U':
strncpy(g_extern.ups_name, optarg, sizeof(g_extern.ups_name) - 1);
strlcpy(g_extern.ups_name, optarg, sizeof(g_extern.ups_name));
break;
case 0:
@@ -530,9 +538,9 @@ static void parse_input(int argc, char *argv[])
}
// strl* would be nice :D
if (!g_extern.has_set_save_path)
fill_pathname(g_extern.savefile_name_srm, g_extern.basename, ".srm");
fill_pathname_noext(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
if (!g_extern.has_set_state_path)
fill_pathname(g_extern.savestate_name, g_extern.basename, ".state");
fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
}
else if (strlen(g_extern.savefile_name_srm) == 0)
{
@@ -619,22 +627,26 @@ static inline void save_files(void)
switch (g_extern.game_type)
{
case SSNES_CART_NORMAL:
SSNES_LOG("Saving regular SRAM.\n");
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:
SSNES_LOG("Saving Gameboy SRAM.\n");
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:
SSNES_LOG("Saving BSX (P)RAM.\n");
save_ram_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
save_ram_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
break;
case SSNES_CART_SUFAMI:
SSNES_LOG("Saving Sufami turbo A/B RAM.\n");
save_ram_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
save_ram_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
break;
@@ -697,6 +709,20 @@ static void deinit_msg_queue(void)
msg_queue_free(g_extern.msg_queue);
}
#ifdef HAVE_XML
static void init_cheats(void)
{
if (*g_settings.cheat_database)
g_extern.cheat = cheat_manager_new(g_settings.cheat_database);
}
static void deinit_cheats(void)
{
if (g_extern.cheat)
cheat_manager_free(g_extern.cheat);
}
#endif
static void init_rewind(void)
{
if (g_settings.rewind_enable)
@@ -851,37 +877,37 @@ static void fill_pathnames(void)
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");
fill_pathname(g_extern.savefile_name_srm, g_extern.bsx_rom_path, ".srm", sizeof(g_extern.savefile_name_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");
fill_pathname(g_extern.savestate_name, g_extern.bsx_rom_path, ".state", sizeof(g_extern.savestate_name));
fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm", sizeof(g_extern.savefile_name_psrm));
break;
case SSNES_CART_SUFAMI:
// SUFAMI ARAM
fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm");
fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm", sizeof(g_extern.savefile_name_asrm));
// SUFAMI BRAM
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm");
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm", sizeof(g_extern.savefile_name_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");
fill_pathname(g_extern.savefile_name_srm, g_extern.gb_rom_path, ".srm", sizeof(g_extern.savefile_name_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");
fill_pathname(g_extern.savestate_name, g_extern.gb_rom_path, ".state", sizeof(g_extern.savestate_name));
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc));
break;
default:
// Infer .rtc save path from save ram path.
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc", sizeof(g_extern.savefile_name_rtc));
}
if (!g_extern.bsv_movie_playback)
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "");
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "", sizeof(g_extern.bsv_movie_path));
if (!(*g_extern.ups_name) && *g_extern.basename)
fill_pathname(g_extern.ups_name, g_extern.basename, ".ups");
fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
}
// Save or load state here.
@@ -942,13 +968,17 @@ static void check_savestates(void)
static void check_fullscreen(void)
{
static bool was_pressed = false;
bool pressed;
// If we go fullscreen we drop all drivers and reinit to be safe.
if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY))
if ((pressed = driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY)) && !was_pressed)
{
g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
}
was_pressed = pressed;
}
static void check_stateslots(void)
@@ -1162,7 +1192,7 @@ static void check_reset(void)
static void check_shader_dir(void)
{
static bool old_pressed_next = false;
static bool old_pressed_prev = true;
static bool old_pressed_prev = false;
if (!g_extern.shader_dir.elems || !driver.video->xml_shader)
return;
@@ -1188,7 +1218,7 @@ static void check_shader_dir(void)
{
const char *shader = g_extern.shader_dir.elems[g_extern.shader_dir.ptr];
strncpy(g_settings.video.bsnes_shader_path, shader, sizeof(g_settings.video.bsnes_shader_path) - 1);
strlcpy(g_settings.video.bsnes_shader_path, shader, sizeof(g_settings.video.bsnes_shader_path));
g_settings.video.shader_type = SSNES_SHADER_BSNES;
msg_queue_clear(g_extern.msg_queue);
@@ -1205,6 +1235,31 @@ static void check_shader_dir(void)
old_pressed_prev = pressed_prev;
}
static void check_cheats(void)
{
if (!g_extern.cheat)
return;
static bool old_pressed_prev = false;
static bool old_pressed_next = false;
static bool old_pressed_toggle = false;
bool pressed_next = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_PLUS);
bool pressed_prev = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_MINUS);
bool pressed_toggle = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_TOGGLE);
if (pressed_next && !old_pressed_next)
cheat_manager_index_next(g_extern.cheat);
else if (pressed_prev && !old_pressed_prev)
cheat_manager_index_prev(g_extern.cheat);
else if (pressed_toggle && !old_pressed_toggle)
cheat_manager_toggle(g_extern.cheat);
old_pressed_prev = pressed_prev;
old_pressed_next = pressed_next;
old_pressed_toggle = pressed_toggle;
}
#endif
static void do_state_checks(void)
@@ -1212,6 +1267,9 @@ static void do_state_checks(void)
if (!g_extern.netplay)
{
check_reset();
#ifdef HAVE_XML
check_cheats();
#endif
check_pause();
if (g_extern.is_paused)
return;
@@ -1264,6 +1322,10 @@ int main(int argc, char *argv[])
if (!init_rom_file(g_extern.game_type))
goto error;
#ifdef HAVE_XML
init_cheats();
#endif
init_msg_queue();
init_movie();
@@ -1365,6 +1427,10 @@ int main(int argc, char *argv[])
if (!g_extern.netplay)
deinit_rewind();
#ifdef HAVE_XML
deinit_cheats();
#endif
deinit_movie();
deinit_msg_queue();
+11
View File
@@ -16,6 +16,9 @@
# Start in fullscreen. Can be changed at runtime.
# video_fullscreen = false
# Force 16-bit colors. Apparently some video cards in use today have troubles with 32-bit ...
# video_force_16bit = false
# Video vsync.
# video_vsync = true
@@ -252,3 +255,11 @@
# When being client over netplay, use keybinds for player 1.
# netplay_client_swap_input = false
# Path to XML cheat database (as used by bSNES).
# cheat_database_path =
# Cheats.
# input_cheat_index_plus = y
# input_cheat_index_minus = t
# input_cheat_toggle = u
+52
View File
@@ -0,0 +1,52 @@
/* 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 "strl.h"
#ifndef HAVE_STRL
// Implementation of strlcpy()/strlcat() based on OpenBSD.
size_t strlcpy(char *dest, const char *source, size_t size)
{
size_t src_size = 0;
size_t n = size;
if (n)
while (--n && (*dest++ = *source++)) src_size++;
if (!n)
{
if (size) *dest = '\0';
while (*source++) src_size++;
}
return src_size;
}
size_t strlcat(char *dest, const char *source, size_t size)
{
size_t len = strlen(dest);
dest += len;
if (len > size)
size = 0;
else
size -= len;
return len + strlcpy(dest, source, size);
}
#endif
+38
View File
@@ -0,0 +1,38 @@
/* 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_STRL_H
#define __SSNES_STRL_H
#include <string.h>
#include <stddef.h>
#ifdef HAVE_CONFIG
#include "config.h"
#endif
#ifndef HAVE_STRL
// Avoid possible naming collitions during link since we prefer to use the actual name.
#define strlcpy(dst, src, size) __strlcpy_ssnes(dst, src, size)
#define strlcat(dst, src, size) __strlcat_ssnes(dst, src, size)
size_t strlcpy(char *dest, const char *source, size_t size);
size_t strlcat(char *dest, const char *source, size_t size);
#endif
#endif