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-28 02:45:18 +02:00
# include "driver.h"
2010-12-24 01:26:36 +01:00
# include "file.h"
2010-12-30 01:33:40 +01:00
# include "hqflt/filters.h"
2010-12-24 01:26:36 +01:00
# include "general.h"
2010-12-30 13:54:49 +01:00
# include "dynamic.h"
2010-12-24 01:07:27 +01:00
2010-12-29 19:18:37 +01:00
struct global g_extern = {
. video_active = true ,
2010-12-29 20:05:57 +01:00
. audio_active = true ,
2010-12-29 19:18:37 +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-12-29 19:18:37 +01:00
if ( g_extern . video_active )
2010-08-19 15:56:00 +02:00
driver . video - > set_nonblock_state ( driver . video_data , syncing_state ) ;
2010-12-29 19:18:37 +01:00
if ( g_extern . audio_active )
2010-12-29 19:43:17 +01:00
driver . audio - > set_nonblock_state ( driver . audio_data , ( g_settings . 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-12-30 01:33:40 +01:00
# ifdef HAVE_FILTER
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 )
{
2010-12-29 19:18:37 +01:00
if ( ! g_extern . video_active )
2010-05-28 02:45:18 +02:00
return ;
2010-05-26 21:27:37 +02:00
2010-12-30 01:33:40 +01:00
# ifdef HAVE_FILTER
uint16_t output_filter [ width * height * 4 * 4 ] ;
2010-11-08 23:38:32 +01:00
uint16_t output [ width * height ] ;
2010-06-27 15:46:23 +02:00
process_frame ( output , data , width , height ) ;
2010-05-26 21:27:37 +02:00
2010-12-30 01:33:40 +01:00
switch ( g_settings . video . filter )
{
case FILTER_HQ2X :
ProcessHQ2x ( output , output_filter ) ;
if ( ! driver . video - > frame ( driver . video_data , output_filter , width < < 1 , height < < 1 , width < < 2 ) )
2010-12-30 01:38:20 +01:00
g_extern . video_active = false ;
2010-12-30 01:33:40 +01:00
break ;
case FILTER_HQ4X :
ProcessHQ4x ( output , output_filter ) ;
if ( ! driver . video - > frame ( driver . video_data , output_filter , width < < 2 , height < < 2 , width < < 3 ) )
2010-12-30 01:38:20 +01:00
g_extern . video_active = false ;
2010-12-30 01:33:40 +01:00
break ;
case FILTER_GRAYSCALE :
grayscale_filter ( output , width , height ) ;
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 ) )
2010-12-30 01:38:20 +01:00
g_extern . video_active = false ;
2010-12-30 01:33:40 +01:00
break ;
case FILTER_BLEED :
bleed_filter ( output , width , height ) ;
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 ) )
2010-12-30 01:38:20 +01:00
g_extern . video_active = false ;
2010-12-30 01:33:40 +01:00
break ;
case FILTER_NTSC :
ntsc_filter ( output_filter , output , width , height ) ;
if ( ! driver . video - > frame ( driver . video_data , output_filter , SNES_NTSC_OUT_WIDTH ( width ) , height , SNES_NTSC_OUT_WIDTH ( width ) < < 1 ) )
2010-12-30 01:38:20 +01:00
g_extern . video_active = false ;
2010-12-30 01:33:40 +01:00
break ;
default :
if ( ! driver . video - > frame ( driver . video_data , data , width , height , ( height = = 448 | | height = = 478 ) ? 1024 : 2048 ) )
g_extern . 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-12-29 19:18:37 +01:00
g_extern . video_active = false ;
2010-12-29 19:43:17 +01:00
# endif
2010-05-26 21:27:37 +02:00
}
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-12-29 19:18:37 +01:00
if ( ! g_extern . audio_active )
2010-05-28 02:45:18 +02:00
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 ;
2010-12-29 19:43:17 +01:00
src_data . src_ratio = ( double ) g_settings . audio . out_rate / ( double ) g_settings . audio . in_rate ;
2010-05-26 22:42:58 +02:00
2010-12-29 19:18:37 +01:00
src_process ( g_extern . 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-12-29 19:18:37 +01:00
g_extern . 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-12-29 19:43:17 +01:00
const struct snes_keybind * binds [ ] = { g_settings . input . binds [ 0 ] , g_settings . input . binds [ 1 ] } ;
2010-10-01 20:15:45 +02:00
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-12-29 21:12:56 +01:00
puts ( " \t -c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes " ) ;
2010-10-01 22:10:28 +02:00
puts ( " \t -v/--verbose: Verbose logging " ) ;
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-12-29 21:12:56 +01:00
{ " config " , 0 , NULL , ' c ' } ,
2010-10-01 21:39:15 +02:00
{ NULL , 0 , NULL , 0 }
} ;
int option_index = 0 ;
2010-12-29 21:12:56 +01:00
char optstring [ ] = " hs:vc: " ;
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 ' :
2010-12-29 19:43:17 +01:00
strncpy ( g_extern . savefile_name_srm , optarg , sizeof ( g_extern . savefile_name_srm ) ) ;
g_extern . savefile_name_srm [ sizeof ( g_extern . savefile_name_srm ) - 1 ] = ' \0 ' ;
2010-10-01 21:39:15 +02:00
break ;
2010-10-01 22:10:28 +02:00
case ' v ' :
2010-12-29 19:43:17 +01:00
g_extern . verbose = true ;
2010-10-01 22:10:28 +02:00
break ;
2010-12-29 21:12:56 +01:00
case ' c ' :
strncpy ( g_extern . config_path , optarg , sizeof ( g_extern . config_path ) - 1 ) ;
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 ' ;
2010-12-30 13:54:49 +01:00
psnes_set_cartridge_basename ( tmp ) ;
2010-12-22 15:55:38 +01:00
}
else
2010-12-30 13:54:49 +01:00
psnes_set_cartridge_basename ( tmp ) ;
2010-12-22 15:55:38 +01:00
2010-10-01 22:10:28 +02:00
SSNES_LOG ( " Opening file: \" %s \" \n " , argv [ optind ] ) ;
2010-12-29 19:43:17 +01:00
g_extern . rom_file = fopen ( argv [ optind ] , " rb " ) ;
if ( g_extern . rom_file = = NULL )
2010-10-01 21:39:15 +02:00
{
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 ) ;
}
2010-12-29 19:43:17 +01:00
if ( strlen ( g_extern . savefile_name_srm ) = = 0 )
fill_pathname ( g_extern . savefile_name_srm , argv [ optind ] , " .srm " ) ;
2010-10-01 21:39:15 +02:00
}
2010-12-29 19:43:17 +01:00
else if ( strlen ( g_extern . savefile_name_srm ) = = 0 )
2010-10-01 22:10:28 +02:00
{
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-29 21:12:56 +01:00
parse_input ( argc , argv ) ;
2010-12-30 01:33:40 +01:00
parse_config ( ) ;
2010-05-26 21:27:37 +02:00
2010-12-30 13:54:49 +01:00
init_dlsym ( ) ;
psnes_init ( ) ;
SSNES_LOG ( " Version of libsnes API: %u.%u \n " , psnes_library_revision_major ( ) , psnes_library_revision_minor ( ) ) ;
2010-10-01 21:39:15 +02:00
void * rom_buf ;
ssize_t rom_len = 0 ;
2010-12-29 19:43:17 +01:00
if ( ( rom_len = read_file ( g_extern . 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-12-29 19:43:17 +01:00
if ( g_extern . rom_file ! = NULL )
fclose ( g_extern . rom_file ) ;
2010-10-01 21:39:15 +02:00
2010-12-29 19:43:17 +01:00
char statefile_name [ strlen ( g_extern . savefile_name_srm ) + strlen ( " .state " ) + 1 ] ;
char savefile_name_rtc [ strlen ( g_extern . 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 ( ) ;
2010-12-30 13:54:49 +01:00
psnes_set_video_refresh ( video_frame ) ;
psnes_set_audio_sample ( audio_sample ) ;
psnes_set_input_poll ( input_poll ) ;
psnes_set_input_state ( input_state ) ;
2010-12-22 15:55:38 +01:00
2010-12-30 13:54:49 +01:00
if ( ! psnes_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 ) ;
2010-12-30 13:54:49 +01:00
unsigned serial_size = psnes_serialize_size ( ) ;
2010-05-26 21:27:37 +02:00
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-12-29 19:43:17 +01:00
load_save_file ( g_extern . savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
2010-08-15 11:08:40 +02:00
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-12-30 12:35:13 +01:00
bool quitting = glfwGetKey ( g_settings . input . exit_emulator_key ) | | ! glfwGetWindowParam ( GLFW_OPENED ) ;
2010-05-26 21:27:37 +02:00
if ( quitting )
break ;
2010-12-29 19:43:17 +01:00
if ( glfwGetKey ( g_settings . input . 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-12-29 19:43:17 +01:00
else if ( glfwGetKey ( g_settings . input . 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-12-29 19:43:17 +01:00
else if ( glfwGetKey ( g_settings . input . toggle_fullscreen_key ) )
2010-05-26 21:41:57 +02:00
{
2010-12-29 19:18:37 +01:00
g_settings . video . fullscreen = ! g_settings . video . 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
2010-12-30 13:54:49 +01:00
psnes_run ( ) ;
2010-05-26 21:27:37 +02:00
}
2010-12-29 19:43:17 +01:00
save_file ( g_extern . savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
2010-08-15 11:08:40 +02:00
save_file ( savefile_name_rtc , SNES_MEMORY_CARTRIDGE_RTC ) ;
2010-05-26 21:27:37 +02:00
2010-12-30 13:54:49 +01:00
psnes_unload_cartridge ( ) ;
psnes_term ( ) ;
2010-05-28 02:45:18 +02:00
uninit_drivers ( ) ;
2010-10-01 21:39:15 +02:00
free ( serial_data ) ;
2010-12-30 13:54:49 +01:00
uninit_dlsym ( ) ;
2010-05-26 21:27:37 +02:00
return 0 ;
2010-06-27 15:46:23 +02:00
error :
2010-12-30 13:54:49 +01:00
psnes_unload_cartridge ( ) ;
psnes_term ( ) ;
2010-06-27 15:46:23 +02:00
uninit_drivers ( ) ;
2010-12-30 13:54:49 +01:00
uninit_dlsym ( ) ;
2010-06-27 15:46:23 +02:00
return 1 ;
2010-05-26 21:27:37 +02:00
}
2010-05-27 00:39:56 +02:00