2010-05-28 18:21:33 +02:00
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright ( C ) 2010 - 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/>.
*/
2010-05-27 16:46:22 +02:00
# include <stdbool.h>
2010-05-26 21:27:37 +02:00
# include <GL/glfw.h>
2010-05-26 22:42:58 +02:00
# include <samplerate.h>
2010-05-28 18:07:04 +02:00
# include <libsnes.hpp>
2010-05-26 21:27:37 +02:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
2010-10-01 21:39:15 +02:00
# include <getopt.h>
2010-05-26 21:27:37 +02:00
# include "config.h"
2010-05-28 02:45:18 +02:00
# include "driver.h"
2010-12-24 01:26:36 +01:00
# include "file.h"
2010-05-29 16:59:57 +02:00
# include "hqflt/pastlib.h"
2010-08-19 15:21:30 +02:00
# include "hqflt/grayscale.h"
2010-08-19 23:44:12 +02:00
# include "hqflt/bleed.h"
2010-08-28 16:36:15 +02:00
# include "hqflt/ntsc.h"
2010-12-24 01:26:36 +01:00
# include "general.h"
2010-12-24 01:07:27 +01:00
2010-08-16 18:40:17 +02:00
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
2010-08-16 20:17:01 +02:00
# define AUDIO_CHUNK_SIZE_BLOCKING 64
2010-08-17 23:06:44 +02:00
# define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio.
2010-08-16 20:17:01 +02:00
static size_t audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING ;
2010-08-16 18:40:17 +02:00
void set_fast_forward_button ( bool new_button_state )
{
static bool old_button_state = false ;
static bool syncing_state = false ;
if ( new_button_state & & ! old_button_state )
{
syncing_state = ! syncing_state ;
2010-08-19 15:26:58 +02:00
if ( video_active )
2010-08-19 15:56:00 +02:00
driver . video - > set_nonblock_state ( driver . video_data , syncing_state ) ;
2010-08-19 15:26:58 +02:00
if ( audio_active )
2010-08-19 15:52:43 +02:00
driver . audio - > set_nonblock_state ( driver . audio_data , ( audio_sync ) ? syncing_state : true ) ;
2010-08-16 20:17:01 +02:00
if ( syncing_state )
audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING ;
else
audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING ;
2010-08-16 18:40:17 +02:00
}
old_button_state = new_button_state ;
}
2010-11-08 23:38:32 +01:00
# if VIDEO_FILTER != FILTER_NONE
2010-06-27 15:46:23 +02:00
static inline void process_frame ( uint16_t * restrict out , const uint16_t * restrict in , unsigned width , unsigned height )
{
2010-08-28 23:55:09 +02:00
int pitch = 1024 ;
if ( height = = 448 | | height = = 478 )
pitch = 512 ;
2010-06-27 15:46:23 +02:00
for ( int y = 0 ; y < height ; y + + )
{
2010-08-28 23:55:09 +02:00
const uint16_t * src = in + y * pitch ;
2010-06-27 15:46:23 +02:00
uint16_t * dst = out + y * width ;
memcpy ( dst , src , width * sizeof ( uint16_t ) ) ;
}
}
2010-11-08 23:38:32 +01:00
# endif
2010-06-27 15:46:23 +02:00
2010-06-27 18:24:26 +02:00
// libsnes: 0.065
// Format received is 16-bit 0RRRRRGGGGGBBBBB
2010-05-28 02:45:18 +02:00
static void video_frame ( const uint16_t * data , unsigned width , unsigned height )
{
if ( ! video_active )
return ;
2010-05-26 21:27:37 +02:00
2010-05-29 16:59:57 +02:00
# if VIDEO_FILTER == FILTER_HQ2X
uint16_t outputHQ2x [ width * height * 2 * 2 ] ;
2010-06-27 14:57:37 +02:00
# elif VIDEO_FILTER == FILTER_HQ4X
2010-05-29 16:59:57 +02:00
uint16_t outputHQ4x [ width * height * 4 * 4 ] ;
2010-08-28 16:36:15 +02:00
# elif VIDEO_FILTER == FILTER_NTSC
uint16_t output_ntsc [ SNES_NTSC_OUT_WIDTH ( width ) * height ] ;
2010-05-29 16:59:57 +02:00
# endif
2010-05-26 21:27:37 +02:00
2010-11-08 23:38:32 +01:00
# if VIDEO_FILTER != FILTER_NONE
uint16_t output [ width * height ] ;
2010-06-27 15:46:23 +02:00
process_frame ( output , data , width , height ) ;
2010-11-08 23:38:32 +01:00
# endif
2010-05-26 21:27:37 +02:00
2010-11-08 23:38:32 +01:00
# if VIDEO_FILTER == FILTER_HQ2X
2010-05-29 16:59:57 +02:00
ProcessHQ2x ( output , outputHQ2x ) ;
2010-11-08 23:46:56 +01:00
if ( ! driver . video - > frame ( driver . video_data , outputHQ2x , width < < 1 , height < < 1 , width < < 2 ) )
2010-05-29 16:59:57 +02:00
video_active = false ;
# elif VIDEO_FILTER == FILTER_HQ4X
ProcessHQ4x ( output , outputHQ4x ) ;
2010-11-08 23:46:56 +01:00
if ( ! driver . video - > frame ( driver . video_data , outputHQ4x , width < < 2 , height < < 2 , width < < 3 ) )
2010-05-29 16:59:57 +02:00
video_active = false ;
2010-08-19 15:21:30 +02:00
# elif VIDEO_FILTER == FILTER_GRAYSCALE
grayscale_filter ( output , width , height ) ;
2010-11-08 23:46:56 +01:00
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 ) )
2010-08-19 23:44:12 +02:00
video_active = false ;
# elif VIDEO_FILTER == FILTER_BLEED
bleed_filter ( output , width , height ) ;
2010-11-08 23:46:56 +01:00
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 ) )
2010-08-19 15:21:30 +02:00
video_active = false ;
2010-08-28 16:36:15 +02:00
# elif VIDEO_FILTER == FILTER_NTSC
ntsc_filter ( output_ntsc , output , width , height ) ;
2010-11-08 23:46:56 +01:00
if ( ! driver . video - > frame ( driver . video_data , output_ntsc , SNES_NTSC_OUT_WIDTH ( width ) , height , SNES_NTSC_OUT_WIDTH ( width ) < < 1 ) )
2010-08-28 16:36:15 +02:00
video_active = false ;
2010-05-29 16:59:57 +02:00
# else
2010-11-08 23:38:32 +01:00
if ( ! driver . video - > frame ( driver . video_data , data , width , height , ( height = = 448 | | height = = 478 ) ? 1024 : 2048 ) )
2010-05-28 02:45:18 +02:00
video_active = false ;
2010-05-29 16:59:57 +02:00
# endif
2010-05-26 21:27:37 +02:00
}
2010-12-24 01:26:36 +01:00
SRC_STATE * source = NULL ;
2010-05-28 02:45:18 +02:00
static void audio_sample ( uint16_t left , uint16_t right )
2010-05-26 21:27:37 +02:00
{
2010-05-28 02:45:18 +02:00
if ( ! audio_active )
return ;
2010-08-16 20:17:01 +02:00
static float data [ AUDIO_CHUNK_SIZE_NONBLOCKING ] ;
2010-05-27 00:26:11 +02:00
static int data_ptr = 0 ;
2010-05-26 22:42:58 +02:00
2010-05-27 00:26:11 +02:00
data [ data_ptr + + ] = ( float ) ( * ( int16_t * ) & left ) / 0x7FFF ;
data [ data_ptr + + ] = ( float ) ( * ( int16_t * ) & right ) / 0x7FFF ;
2010-05-26 21:27:37 +02:00
2010-08-16 20:17:01 +02:00
if ( data_ptr > = audio_chunk_size )
2010-05-26 22:42:58 +02:00
{
2010-08-16 20:17:01 +02:00
float outsamples [ audio_chunk_size * 16 ] ;
int16_t temp_outsamples [ audio_chunk_size * 16 ] ;
2010-05-26 22:42:58 +02:00
2010-05-27 00:26:11 +02:00
SRC_DATA src_data ;
2010-05-26 22:42:58 +02:00
2010-05-27 00:26:11 +02:00
src_data . data_in = data ;
src_data . data_out = outsamples ;
2010-08-16 20:17:01 +02:00
src_data . input_frames = audio_chunk_size / 2 ;
src_data . output_frames = audio_chunk_size * 8 ;
2010-05-27 00:26:11 +02:00
src_data . end_of_input = 0 ;
src_data . src_ratio = ( double ) out_rate / ( double ) in_rate ;
2010-05-26 22:42:58 +02:00
2010-05-27 00:26:11 +02:00
src_process ( source , & src_data ) ;
2010-05-26 22:42:58 +02:00
2010-08-15 10:02:04 +02:00
src_float_to_short_array ( outsamples , temp_outsamples , src_data . output_frames_gen * 2 ) ;
2010-05-26 22:42:58 +02:00
2010-05-28 02:45:18 +02:00
if ( driver . audio - > write ( driver . audio_data , temp_outsamples , src_data . output_frames_gen * 4 ) < 0 )
2010-08-16 19:16:03 +02:00
{
fprintf ( stderr , " SSNES [ERROR]: Audio backend failed to write. Will continue without sound. \n " ) ;
2010-05-28 02:45:18 +02:00
audio_active = false ;
2010-08-16 19:16:03 +02:00
}
2010-05-26 22:42:58 +02:00
2010-05-28 02:45:18 +02:00
data_ptr = 0 ;
2010-05-26 21:27:37 +02:00
}
}
2010-05-28 02:45:18 +02:00
static void input_poll ( void )
2010-05-26 21:27:37 +02:00
{
2010-05-28 02:45:18 +02:00
driver . input - > poll ( driver . input_data ) ;
2010-05-26 21:27:37 +02:00
}
static int16_t input_state ( bool port , unsigned device , unsigned index , unsigned id )
{
2010-10-01 20:15:45 +02:00
const struct snes_keybind * binds [ ] = { snes_keybinds_1 , snes_keybinds_2 } ;
return driver . input - > input_state ( driver . input_data , binds , port , device , index , id ) ;
2010-05-26 21:27:37 +02:00
}
2010-08-28 17:20:29 +02:00
static void fill_pathname ( char * out_path , char * in_path , const char * replace )
{
char tmp_path [ strlen ( in_path ) + 1 ] ;
strcpy ( tmp_path , in_path ) ;
char * tok = NULL ;
tok = strrchr ( tmp_path , ' . ' ) ;
if ( tok ! = NULL )
* tok = ' \0 ' ;
strcpy ( out_path , tmp_path ) ;
strcat ( out_path , replace ) ;
}
2010-05-26 21:27:37 +02:00
2010-10-01 21:39:15 +02:00
static void print_help ( void )
{
2010-10-02 13:54:32 +02:00
puts ( " ================================================= " ) ;
2010-10-01 21:39:15 +02:00
puts ( " ssnes: Simple Super Nintendo Emulator (libsnes) " ) ;
2010-10-02 13:54:32 +02:00
puts ( " ================================================= " ) ;
2010-10-01 21:39:15 +02:00
puts ( " Usage: ssnes [rom file] [-h/--help | -s/--save] " ) ;
puts ( " \t -h/--help: Show this help message " ) ;
puts ( " \t -s/--save: Path for save file (*.srm). Required when rom is input from stdin " ) ;
2010-11-13 15:14:30 +01:00
# ifdef HAVE_CG
puts ( " \t -f/--shader: Path to Cg shader. Will be compiled at runtime. \n " ) ;
# endif
2010-10-01 22:10:28 +02:00
puts ( " \t -v/--verbose: Verbose logging " ) ;
2010-10-01 21:39:15 +02:00
}
2010-12-31 14:20:49 +01:00
bool fullscreen = START_FULLSCREEN ;
2010-10-01 21:39:15 +02:00
static FILE * rom_file = NULL ;
static char savefile_name_srm [ 256 ] = { 0 } ;
2010-12-24 01:26:36 +01:00
bool verbose = false ;
2010-11-13 15:14:30 +01:00
# ifdef HAVE_CG
char cg_shader_path [ 256 ] = DEFAULT_CG_SHADER ;
# endif
2010-10-01 22:10:28 +02:00
2010-10-01 21:39:15 +02:00
static void parse_input ( int argc , char * argv [ ] )
2010-05-26 21:27:37 +02:00
{
2010-10-01 21:39:15 +02:00
if ( argc < 2 )
2010-05-26 21:27:37 +02:00
{
2010-10-01 21:39:15 +02:00
print_help ( ) ;
2010-05-26 21:27:37 +02:00
exit ( 1 ) ;
}
2010-05-29 15:21:30 +02:00
2010-10-01 21:39:15 +02:00
struct option opts [ ] = {
{ " help " , 0 , NULL , ' h ' } ,
{ " save " , 1 , NULL , ' s ' } ,
2010-10-01 22:10:28 +02:00
{ " verbose " , 0 , NULL , ' v ' } ,
2010-11-13 15:14:30 +01:00
# ifdef HAVE_CG
{ " shader " , 1 , NULL , ' f ' } ,
# endif
2010-10-01 21:39:15 +02:00
{ NULL , 0 , NULL , 0 }
} ;
int option_index = 0 ;
2010-11-13 15:14:30 +01:00
# ifdef HAVE_CG
char optstring [ ] = " hs:vf: " ;
# else
2010-10-01 22:10:28 +02:00
char optstring [ ] = " hs:v " ;
2010-11-13 15:14:30 +01:00
# endif
2010-10-01 21:39:15 +02:00
for ( ; ; )
2010-05-29 15:21:30 +02:00
{
2010-10-01 21:39:15 +02:00
int c = getopt_long ( argc , argv , optstring , opts , & option_index ) ;
if ( c = = - 1 )
break ;
switch ( c )
{
case ' h ' :
print_help ( ) ;
exit ( 0 ) ;
case ' s ' :
strncpy ( savefile_name_srm , optarg , sizeof ( savefile_name_srm ) ) ;
savefile_name_srm [ sizeof ( savefile_name_srm ) - 1 ] = ' \0 ' ;
break ;
2010-10-01 22:10:28 +02:00
2010-11-13 15:14:30 +01:00
# ifdef HAVE_CG
case ' f ' :
strncpy ( cg_shader_path , optarg , sizeof ( cg_shader_path ) - 1 ) ;
break ;
# endif
2010-10-01 22:10:28 +02:00
case ' v ' :
verbose = true ;
break ;
2010-10-01 21:39:15 +02:00
case ' ? ' :
print_help ( ) ;
exit ( 1 ) ;
default :
2010-10-01 22:10:28 +02:00
SSNES_ERR ( " Error parsing arguments. \n " ) ;
2010-10-01 21:39:15 +02:00
exit ( 1 ) ;
}
2010-05-29 15:21:30 +02:00
}
2010-10-01 21:39:15 +02:00
if ( optind < argc )
{
2010-12-22 15:55:38 +01:00
char tmp [ strlen ( argv [ optind ] ) + 1 ] ;
strcpy ( tmp , argv [ optind ] ) ;
char * dst = strrchr ( tmp , ' . ' ) ;
if ( dst )
{
* dst = ' \0 ' ;
snes_set_cartridge_basename ( tmp ) ;
}
else
snes_set_cartridge_basename ( tmp ) ;
2010-10-01 22:10:28 +02:00
SSNES_LOG ( " Opening file: \" %s \" \n " , argv [ optind ] ) ;
2010-10-01 21:39:15 +02:00
rom_file = fopen ( argv [ optind ] , " rb " ) ;
if ( rom_file = = NULL )
{
2010-10-01 22:10:28 +02:00
SSNES_ERR ( " Could not open file: \" %s \" \n " , optarg ) ;
2010-10-01 21:39:15 +02:00
exit ( 1 ) ;
}
if ( strlen ( savefile_name_srm ) = = 0 )
fill_pathname ( savefile_name_srm , argv [ optind ] , " .srm " ) ;
}
2010-10-01 22:10:28 +02:00
else if ( strlen ( savefile_name_srm ) = = 0 )
{
SSNES_ERR ( " Need savefile argument when reading rom from stdin. \n " ) ;
print_help ( ) ;
exit ( 1 ) ;
}
2010-10-01 21:39:15 +02:00
}
2010-08-15 10:02:04 +02:00
2010-10-01 21:39:15 +02:00
int main ( int argc , char * argv [ ] )
{
2010-12-22 15:55:38 +01:00
snes_init ( ) ;
2010-10-01 21:39:15 +02:00
parse_input ( argc , argv ) ;
2010-05-26 21:27:37 +02:00
2010-10-01 21:39:15 +02:00
void * rom_buf ;
ssize_t rom_len = 0 ;
if ( ( rom_len = read_file ( rom_file , & rom_buf ) ) = = - 1 )
2010-05-26 21:27:37 +02:00
{
2010-10-01 22:10:28 +02:00
SSNES_ERR ( " Could not read ROM file. \n " ) ;
2010-10-01 21:39:15 +02:00
exit ( 1 ) ;
2010-05-26 21:27:37 +02:00
}
2010-10-01 22:10:28 +02:00
SSNES_LOG ( " ROM size: %zi bytes \n " , rom_len ) ;
2010-05-26 21:27:37 +02:00
2010-10-01 21:39:15 +02:00
if ( rom_file ! = NULL )
fclose ( rom_file ) ;
char statefile_name [ strlen ( savefile_name_srm ) + strlen ( " .state " ) + 1 ] ;
char savefile_name_rtc [ strlen ( savefile_name_srm ) + strlen ( " .rtc " ) + 1 ] ;
2010-05-26 21:27:37 +02:00
2010-10-01 21:39:15 +02:00
fill_pathname ( statefile_name , argv [ 1 ] , " .state " ) ;
fill_pathname ( savefile_name_rtc , argv [ 1 ] , " .rtc " ) ;
init_drivers ( ) ;
snes_set_video_refresh ( video_frame ) ;
snes_set_audio_sample ( audio_sample ) ;
snes_set_input_poll ( input_poll ) ;
snes_set_input_state ( input_state ) ;
2010-12-22 15:55:38 +01:00
2010-10-01 21:39:15 +02:00
if ( ! snes_load_cartridge_normal ( NULL , rom_buf , rom_len ) )
2010-08-15 11:24:27 +02:00
{
2010-10-01 22:10:28 +02:00
SSNES_ERR ( " ROM file is not valid! \n " ) ;
2010-08-15 11:24:27 +02:00
goto error ;
}
2010-05-26 21:27:37 +02:00
free ( rom_buf ) ;
unsigned serial_size = snes_serialize_size ( ) ;
uint8_t * serial_data = malloc ( serial_size ) ;
2010-10-01 21:39:15 +02:00
if ( serial_data = = NULL )
{
2010-10-01 22:10:28 +02:00
SSNES_ERR ( " Failed to allocate memory for states! \n " ) ;
2010-10-01 21:39:15 +02:00
goto error ;
}
2010-06-27 15:46:23 +02:00
2010-08-15 11:08:40 +02:00
load_save_file ( savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
load_save_file ( savefile_name_rtc , SNES_MEMORY_CARTRIDGE_RTC ) ;
2010-05-26 21:27:37 +02:00
2010-05-28 02:45:18 +02:00
///// TODO: Modular friendly!!!
2010-05-26 21:27:37 +02:00
for ( ; ; )
{
2010-10-01 21:39:15 +02:00
bool quitting = glfwGetKey ( GLFW_KEY_ESC ) | | ! glfwGetWindowParam ( GLFW_OPENED ) ;
2010-05-26 21:27:37 +02:00
if ( quitting )
break ;
2010-05-26 21:41:57 +02:00
if ( glfwGetKey ( SAVE_STATE_KEY ) )
2010-05-27 00:39:56 +02:00
{
2010-08-15 11:08:40 +02:00
write_file ( statefile_name , serial_data , serial_size ) ;
2010-05-27 00:39:56 +02:00
}
2010-05-26 21:41:57 +02:00
else if ( glfwGetKey ( LOAD_STATE_KEY ) )
2010-08-15 10:02:04 +02:00
load_state ( statefile_name , serial_data , serial_size ) ;
2010-05-27 00:39:56 +02:00
2010-05-26 21:41:57 +02:00
else if ( glfwGetKey ( TOGGLE_FULLSCREEN ) )
{
fullscreen = ! fullscreen ;
2010-05-28 02:45:18 +02:00
uninit_drivers ( ) ;
init_drivers ( ) ;
2010-05-26 21:41:57 +02:00
}
2010-05-26 21:27:37 +02:00
snes_run ( ) ;
}
2010-08-15 11:08:40 +02:00
save_file ( savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
save_file ( savefile_name_rtc , SNES_MEMORY_CARTRIDGE_RTC ) ;
2010-05-26 21:27:37 +02:00
2010-06-27 14:40:06 +02:00
snes_unload_cartridge ( ) ;
2010-05-26 21:27:37 +02:00
snes_term ( ) ;
2010-05-28 02:45:18 +02:00
uninit_drivers ( ) ;
2010-10-01 21:39:15 +02:00
free ( serial_data ) ;
2010-05-26 21:27:37 +02:00
return 0 ;
2010-06-27 15:46:23 +02:00
error :
snes_unload_cartridge ( ) ;
snes_term ( ) ;
uninit_drivers ( ) ;
return 1 ;
2010-05-26 21:27:37 +02:00
}
2010-05-27 00:39:56 +02:00