2011-01-17 19:54:58 +00:00
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
2010-05-28 16:21:33 +00:00
* 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 14:46:22 +00:00
# include <stdbool.h>
2010-05-28 16:07:04 +00:00
# include <libsnes.hpp>
2010-05-26 19:27:37 +00:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
2010-10-01 19:39:15 +00:00
# include <getopt.h>
2010-05-28 00:45:18 +00:00
# include "driver.h"
2010-12-24 00:26:36 +00:00
# include "file.h"
2010-12-30 00:33:40 +00:00
# include "hqflt/filters.h"
2010-12-24 00:26:36 +00:00
# include "general.h"
2010-12-30 12:54:49 +00:00
# include "dynamic.h"
2011-01-03 19:46:50 +00:00
# include "record/ffemu.h"
# include <assert.h>
2011-01-07 10:09:56 +00:00
# ifdef HAVE_SRC
# include <samplerate.h>
# endif
2010-12-24 00:07:27 +00:00
2010-12-29 18:18:37 +00:00
struct global g_extern = {
. video_active = true ,
2010-12-29 19:05:57 +00:00
. audio_active = true ,
2011-01-12 17:05:57 +00:00
. game_type = SSNES_CART_NORMAL ,
2010-12-29 18:18:37 +00:00
} ;
2010-08-16 16:40:17 +00: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.
2011-01-08 17:37:45 +00:00
static void set_fast_forward_button ( bool new_button_state )
2010-08-16 16:40:17 +00:00
{
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 18:18:37 +00:00
if ( g_extern . video_active )
2010-08-19 13:56:00 +00:00
driver . video - > set_nonblock_state ( driver . video_data , syncing_state ) ;
2010-12-29 18:18:37 +00:00
if ( g_extern . audio_active )
2010-12-29 18:43:17 +00:00
driver . audio - > set_nonblock_state ( driver . audio_data , ( g_settings . audio . sync ) ? syncing_state : true ) ;
2011-01-14 14:34:38 +00:00
2010-08-16 18:17:01 +00:00
if ( syncing_state )
2011-01-14 14:34:38 +00:00
g_extern . audio_data . chunk_size = g_extern . audio_data . nonblock_chunk_size ;
2010-08-16 18:17:01 +00:00
else
2011-01-14 14:34:38 +00:00
g_extern . audio_data . chunk_size = g_extern . audio_data . block_chunk_size ;
2010-08-16 16:40:17 +00:00
}
old_button_state = new_button_state ;
}
2010-12-30 00:33:40 +00:00
# ifdef HAVE_FILTER
2010-06-27 13:46:23 +00:00
static inline void process_frame ( uint16_t * restrict out , const uint16_t * restrict in , unsigned width , unsigned height )
{
2010-08-28 21:55:09 +00:00
int pitch = 1024 ;
if ( height = = 448 | | height = = 478 )
pitch = 512 ;
2010-06-27 13:46:23 +00:00
for ( int y = 0 ; y < height ; y + + )
{
2010-08-28 21:55:09 +00:00
const uint16_t * src = in + y * pitch ;
2010-06-27 13:46:23 +00:00
uint16_t * dst = out + y * width ;
memcpy ( dst , src , width * sizeof ( uint16_t ) ) ;
}
}
2010-11-08 22:38:32 +00:00
# endif
2010-06-27 13:46:23 +00:00
2010-06-27 16:24:26 +00:00
// libsnes: 0.065
// Format received is 16-bit 0RRRRRGGGGGBBBBB
2010-05-28 00:45:18 +00:00
static void video_frame ( const uint16_t * data , unsigned width , unsigned height )
{
2010-12-29 18:18:37 +00:00
if ( ! g_extern . video_active )
2010-05-28 00:45:18 +00:00
return ;
2010-05-26 19:27:37 +00:00
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
if ( g_extern . recording )
{
struct ffemu_video_data ffemu_data = {
. data = data ,
. pitch = height = = 448 | | height = = 478 ? 1024 : 2048 ,
. width = width ,
. height = height
} ;
ffemu_push_video ( g_extern . rec , & ffemu_data ) ;
}
# endif
2011-01-03 19:46:50 +00:00
2010-12-30 00:33:40 +00:00
# ifdef HAVE_FILTER
uint16_t output_filter [ width * height * 4 * 4 ] ;
2010-11-08 22:38:32 +00:00
uint16_t output [ width * height ] ;
2011-01-12 18:28:49 +00:00
if ( g_settings . video . filter ! = FILTER_NONE )
process_frame ( output , data , width , height ) ;
2010-05-26 19:27:37 +00:00
2010-12-30 00:33:40 +00:00
switch ( g_settings . video . filter )
{
case FILTER_HQ2X :
ProcessHQ2x ( output , output_filter ) ;
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , output_filter , width < < 1 , height < < 1 , width < < 2 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:38:20 +00:00
g_extern . video_active = false ;
2010-12-30 00:33:40 +00:00
break ;
case FILTER_HQ4X :
ProcessHQ4x ( output , output_filter ) ;
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , output_filter , width < < 2 , height < < 2 , width < < 3 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:38:20 +00:00
g_extern . video_active = false ;
2010-12-30 00:33:40 +00:00
break ;
case FILTER_GRAYSCALE :
grayscale_filter ( output , width , height ) ;
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:38:20 +00:00
g_extern . video_active = false ;
2010-12-30 00:33:40 +00:00
break ;
case FILTER_BLEED :
bleed_filter ( output , width , height ) ;
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , output , width , height , width < < 1 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:38:20 +00:00
g_extern . video_active = false ;
2010-12-30 00:33:40 +00:00
break ;
case FILTER_NTSC :
ntsc_filter ( output_filter , output , width , height ) ;
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , output_filter , SNES_NTSC_OUT_WIDTH ( width ) , height , SNES_NTSC_OUT_WIDTH ( width ) < < 1 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:38:20 +00:00
g_extern . video_active = false ;
2010-12-30 00:33:40 +00:00
break ;
default :
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , data , width , height , ( height = = 448 | | height = = 478 ) ? 1024 : 2048 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-30 00:33:40 +00:00
g_extern . video_active = false ;
}
2010-05-29 14:59:57 +00:00
# else
2011-01-23 01:23:20 +00:00
if ( ! driver . video - > frame ( driver . video_data , data , width , height , ( height = = 448 | | height = = 478 ) ? 1024 : 2048 , msg_queue_pull ( g_extern . msg_queue ) ) )
2010-12-29 18:18:37 +00:00
g_extern . video_active = false ;
2010-12-29 18:43:17 +00:00
# endif
2010-05-26 19:27:37 +00:00
}
2010-05-28 00:45:18 +00:00
static void audio_sample ( uint16_t left , uint16_t right )
2010-05-26 19:27:37 +00:00
{
2010-12-29 18:18:37 +00:00
if ( ! g_extern . audio_active )
2010-05-28 00:45:18 +00:00
return ;
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
if ( g_extern . recording )
{
static int16_t static_data [ 2 ] ;
static_data [ 0 ] = left ;
static_data [ 1 ] = right ;
struct ffemu_audio_data ffemu_data = {
. data = static_data ,
. frames = 1
} ;
ffemu_push_audio ( g_extern . rec , & ffemu_data ) ;
}
# endif
2011-01-03 19:46:50 +00:00
2011-01-14 14:34:38 +00:00
g_extern . audio_data . data [ g_extern . audio_data . data_ptr + + ] = ( float ) ( * ( int16_t * ) & left ) / 0x8000 ;
g_extern . audio_data . data [ g_extern . audio_data . data_ptr + + ] = ( float ) ( * ( int16_t * ) & right ) / 0x8000 ;
2010-05-26 19:27:37 +00:00
2011-01-14 14:34:38 +00:00
if ( g_extern . audio_data . data_ptr > = g_extern . audio_data . chunk_size )
2010-05-26 20:42:58 +00:00
{
2010-05-26 22:26:11 +00:00
SRC_DATA src_data ;
2010-05-26 20:42:58 +00:00
2011-01-14 14:34:38 +00:00
src_data . data_in = g_extern . audio_data . data ;
src_data . data_out = g_extern . audio_data . outsamples ;
src_data . input_frames = g_extern . audio_data . chunk_size / 2 ;
src_data . output_frames = g_extern . audio_data . chunk_size * 8 ;
2010-05-26 22:26:11 +00:00
src_data . end_of_input = 0 ;
2010-12-29 18:43:17 +00:00
src_data . src_ratio = ( double ) g_settings . audio . out_rate / ( double ) g_settings . audio . in_rate ;
2010-05-26 20:42:58 +00:00
2010-12-29 18:18:37 +00:00
src_process ( g_extern . source , & src_data ) ;
2010-05-26 20:42:58 +00:00
2011-01-15 19:37:42 +00:00
if ( g_extern . audio_data . use_float )
2010-08-16 17:16:03 +00:00
{
2011-01-14 14:34:38 +00:00
if ( driver . audio - > write ( driver . audio_data , g_extern . audio_data . outsamples , src_data . output_frames_gen * sizeof ( float ) * 2 ) < 0 )
{
fprintf ( stderr , " SSNES [ERROR]: Audio backend failed to write. Will continue without sound. \n " ) ;
g_extern . audio_active = false ;
}
}
else
{
src_float_to_short_array ( g_extern . audio_data . outsamples , g_extern . audio_data . conv_outsamples , src_data . output_frames_gen * 2 ) ;
if ( driver . audio - > write ( driver . audio_data , g_extern . audio_data . conv_outsamples , src_data . output_frames_gen * sizeof ( int16_t ) * 2 ) < 0 )
{
fprintf ( stderr , " SSNES [ERROR]: Audio backend failed to write. Will continue without sound. \n " ) ;
g_extern . audio_active = false ;
}
2010-08-16 17:16:03 +00:00
}
2010-05-26 20:42:58 +00:00
2011-01-14 14:34:38 +00:00
g_extern . audio_data . data_ptr = 0 ;
2010-05-26 19:27:37 +00:00
}
}
2010-05-28 00:45:18 +00:00
static void input_poll ( void )
2010-05-26 19:27:37 +00:00
{
2010-05-28 00:45:18 +00:00
driver . input - > poll ( driver . input_data ) ;
2010-05-26 19:27:37 +00:00
}
static int16_t input_state ( bool port , unsigned device , unsigned index , unsigned id )
{
2011-01-10 15:53:37 +00:00
const struct snes_keybind * binds [ MAX_PLAYERS ] ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
binds [ i ] = g_settings . input . binds [ i ] ;
2010-10-01 18:15:45 +00:00
return driver . input - > input_state ( driver . input_data , binds , port , device , index , id ) ;
2010-05-26 19:27:37 +00:00
}
2010-08-28 15:20:29 +00: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 19:27:37 +00:00
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
# define FFMPEG_HELP_QUARK " | -r / --record "
# else
# define FFMPEG_HELP_QUARK
# endif
2011-01-07 16:59:53 +00:00
# ifdef _WIN32
# define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe"
# else
# define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME / ssnes / ssnes.cfg"
# endif
2010-10-01 19:39:15 +00:00
static void print_help ( void )
{
2010-10-02 11:54:32 +00:00
puts ( " ================================================= " ) ;
2010-10-01 19:39:15 +00:00
puts ( " ssnes: Simple Super Nintendo Emulator (libsnes) " ) ;
2010-10-02 11:54:32 +00:00
puts ( " ================================================= " ) ;
2011-01-12 20:57:55 +00:00
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 " ] " ) ;
2010-10-01 19:39:15 +00:00
puts ( " \t -h/--help: Show this help message " ) ;
puts ( " \t -s/--save: Path for save file (*.srm). Required when rom is input from stdin " ) ;
2011-01-12 17:07:31 +00:00
puts ( " \t -S/--savestate: Path to use for save states. If not selected, *.state will be assumed. " ) ;
2011-01-19 11:54:19 +00:00
# ifdef HAVE_CONFIGFILE
2011-01-07 16:59:53 +00:00
puts ( " \t -c/--config: Path for config file. " SSNES_DEFAULT_CONF_PATH_STR ) ;
2011-01-19 11:54:19 +00:00
# endif
2011-01-12 17:05:57 +00:00
puts ( " \t -g/--gameboy: Path to Gameboy ROM. Load SuperGameBoy as the regular rom. " ) ;
2011-01-12 20:57:55 +00:00
puts ( " \t -b/--bsx: Path to BSX rom. Load BSX BIOS as the regular rom. " ) ;
puts ( " \t -B/--bsxslot: Path to BSX slotted rom. Load BSX BIOS as the regular rom. " ) ;
puts ( " \t --sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom. " ) ;
puts ( " \t --sufamiB: Path to B slot of Sufami Turbo. " ) ;
2011-01-10 13:29:00 +00:00
puts ( " \t -m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2). " ) ;
2011-01-10 16:34:26 +00:00
puts ( " \t \t This argument can be specified several times to connect more mice. " ) ;
2011-01-10 13:39:14 +00:00
puts ( " \t -p/--scope: Connect a virtual SuperScope into port 2 of the SNES. " ) ;
2011-01-10 15:53:37 +00:00
puts ( " \t -j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES. " ) ;
2011-01-12 17:07:31 +00:00
puts ( " \t -J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES. " ) ;
2011-01-10 15:53:37 +00:00
puts ( " \t -4/--multitap: Connect a multitap to port 2 of the SNES. " ) ;
2011-01-07 16:59:53 +00:00
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
puts ( " \t -r/--record: Path to record video file. Settings for video/audio codecs are found in config file. " ) ;
# endif
2010-10-01 20:10:28 +00:00
puts ( " \t -v/--verbose: Verbose logging " ) ;
2010-10-01 19:39:15 +00:00
}
static void parse_input ( int argc , char * argv [ ] )
2010-05-26 19:27:37 +00:00
{
2010-10-01 19:39:15 +00:00
if ( argc < 2 )
2010-05-26 19:27:37 +00:00
{
2010-10-01 19:39:15 +00:00
print_help ( ) ;
2010-05-26 19:27:37 +00:00
exit ( 1 ) ;
}
2010-05-29 13:21:30 +00:00
2010-10-01 19:39:15 +00:00
struct option opts [ ] = {
{ " help " , 0 , NULL , ' h ' } ,
{ " save " , 1 , NULL , ' s ' } ,
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
{ " record " , 1 , NULL , ' r ' } ,
# endif
2010-10-01 20:10:28 +00:00
{ " verbose " , 0 , NULL , ' v ' } ,
2011-01-12 17:05:57 +00:00
{ " gameboy " , 1 , NULL , ' g ' } ,
2011-01-19 11:54:19 +00:00
# ifdef HAVE_CONFIGFILE
2010-12-29 20:12:56 +00:00
{ " config " , 0 , NULL , ' c ' } ,
2011-01-19 11:54:19 +00:00
# endif
2011-01-10 13:29:00 +00:00
{ " mouse " , 1 , NULL , ' m ' } ,
2011-01-10 13:39:14 +00:00
{ " scope " , 0 , NULL , ' p ' } ,
2011-01-12 17:07:31 +00:00
{ " savestate " , 1 , NULL , ' S ' } ,
2011-01-12 20:57:55 +00:00
{ " bsx " , 1 , NULL , ' b ' } ,
{ " bsxslot " , 1 , NULL , ' B ' } ,
2011-01-10 15:53:37 +00:00
{ " justifier " , 0 , NULL , ' j ' } ,
2011-01-12 17:07:31 +00:00
{ " justifiers " , 0 , NULL , ' J ' } ,
2011-01-10 15:53:37 +00:00
{ " multitap " , 0 , NULL , ' 4 ' } ,
2011-01-12 20:57:55 +00:00
{ " sufamiA " , 1 , NULL , ' Y ' } ,
{ " sufamiB " , 1 , NULL , ' Z ' } ,
2010-10-01 19:39:15 +00:00
{ NULL , 0 , NULL , 0 }
} ;
int option_index = 0 ;
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
# define FFMPEG_RECORD_ARG "r:"
# else
# define FFMPEG_RECORD_ARG
# endif
2011-01-19 11:54:19 +00:00
# ifdef HAVE_CONFIGFILE
# define CONFIG_FILE_ARG "c:"
# else
# define CONFIG_FILE_ARG
# endif
char optstring [ ] = " hs:vS:m:p4jJg:b:B:Y:Z: " FFMPEG_RECORD_ARG CONFIG_FILE_ARG ;
2010-10-01 19:39:15 +00:00
for ( ; ; )
2010-05-29 13:21:30 +00:00
{
2010-10-01 19:39:15 +00:00
int c = getopt_long ( argc , argv , optstring , opts , & option_index ) ;
2011-01-10 13:29:00 +00:00
int port ;
2010-10-01 19:39:15 +00:00
if ( c = = - 1 )
break ;
switch ( c )
{
case ' h ' :
print_help ( ) ;
exit ( 0 ) ;
2011-01-10 15:53:37 +00:00
case ' 4 ' :
g_extern . has_multitap = true ;
break ;
case ' j ' :
g_extern . has_justifier = true ;
break ;
2011-01-12 17:07:31 +00:00
case ' J ' :
2011-01-10 15:53:37 +00:00
g_extern . has_justifiers = true ;
break ;
2010-10-01 19:39:15 +00:00
case ' s ' :
2011-01-08 18:15:18 +00:00
strncpy ( g_extern . savefile_name_srm , optarg , sizeof ( g_extern . savefile_name_srm ) - 1 ) ;
break ;
2011-01-12 17:05:57 +00:00
case ' g ' :
strncpy ( g_extern . gb_rom_path , optarg , sizeof ( g_extern . gb_rom_path ) - 1 ) ;
g_extern . game_type = SSNES_CART_SGB ;
break ;
2011-01-12 20:57:55 +00:00
case ' b ' :
strncpy ( g_extern . bsx_rom_path , optarg , sizeof ( g_extern . bsx_rom_path ) - 1 ) ;
g_extern . game_type = SSNES_CART_BSX ;
break ;
case ' B ' :
strncpy ( g_extern . bsx_rom_path , optarg , sizeof ( g_extern . bsx_rom_path ) - 1 ) ;
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 ) ;
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 ) ;
g_extern . game_type = SSNES_CART_SUFAMI ;
break ;
2011-01-12 17:07:31 +00:00
case ' S ' :
2011-01-08 18:15:18 +00:00
strncpy ( g_extern . savestate_name , optarg , sizeof ( g_extern . savestate_name ) - 1 ) ;
2010-10-01 19:39:15 +00:00
break ;
2010-10-01 20:10:28 +00:00
case ' v ' :
2010-12-29 18:43:17 +00:00
g_extern . verbose = true ;
2010-10-01 20:10:28 +00:00
break ;
2011-01-10 06:58:11 +00:00
case ' m ' :
2011-01-10 13:29:00 +00:00
port = strtol ( optarg , NULL , 0 ) ;
if ( port < 1 | | port > 2 )
{
SSNES_ERR ( " Connect mouse to port 1 or 2. \n " ) ;
print_help ( ) ;
exit ( 1 ) ;
}
g_extern . has_mouse [ port - 1 ] = true ;
2011-01-10 06:58:11 +00:00
break ;
case ' p ' :
2011-01-10 13:39:14 +00:00
g_extern . has_scope [ 1 ] = true ;
2011-01-10 06:58:11 +00:00
break ;
2011-01-19 11:54:19 +00:00
# ifdef HAVE_CONFIGFILE
2010-12-29 20:12:56 +00:00
case ' c ' :
strncpy ( g_extern . config_path , optarg , sizeof ( g_extern . config_path ) - 1 ) ;
break ;
2011-01-19 11:54:19 +00:00
# endif
2010-12-29 20:12:56 +00:00
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
case ' r ' :
strncpy ( g_extern . record_path , optarg , sizeof ( g_extern . record_path ) - 1 ) ;
g_extern . recording = true ;
break ;
# endif
2010-10-01 19:39:15 +00:00
case ' ? ' :
print_help ( ) ;
exit ( 1 ) ;
default :
2010-10-01 20:10:28 +00:00
SSNES_ERR ( " Error parsing arguments. \n " ) ;
2010-10-01 19:39:15 +00:00
exit ( 1 ) ;
}
2010-05-29 13:21:30 +00:00
}
2010-10-01 19:39:15 +00:00
if ( optind < argc )
{
2010-12-22 14:55:38 +00:00
char tmp [ strlen ( argv [ optind ] ) + 1 ] ;
strcpy ( tmp , argv [ optind ] ) ;
char * dst = strrchr ( tmp , ' . ' ) ;
if ( dst )
* dst = ' \0 ' ;
2010-12-30 13:11:56 +00:00
strncpy ( g_extern . basename , tmp , sizeof ( g_extern . basename ) - 1 ) ;
2010-12-22 14:55:38 +00:00
2010-10-01 20:10:28 +00:00
SSNES_LOG ( " Opening file: \" %s \" \n " , argv [ optind ] ) ;
2010-12-29 18:43:17 +00:00
g_extern . rom_file = fopen ( argv [ optind ] , " rb " ) ;
if ( g_extern . rom_file = = NULL )
2010-10-01 19:39:15 +00:00
{
2011-01-08 13:40:54 +00:00
SSNES_ERR ( " Could not open file: \" %s \" \n " , argv [ optind ] ) ;
2010-10-01 19:39:15 +00:00
exit ( 1 ) ;
}
2011-01-08 18:15:18 +00:00
// strl* would be nice :D
2010-12-29 18:43:17 +00:00
if ( strlen ( g_extern . savefile_name_srm ) = = 0 )
2011-01-08 18:15:18 +00:00
{
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 ) ;
}
2010-10-01 19:39:15 +00:00
}
2010-12-29 18:43:17 +00:00
else if ( strlen ( g_extern . savefile_name_srm ) = = 0 )
2010-10-01 20:10:28 +00:00
{
2011-01-08 18:15:18 +00:00
SSNES_ERR ( " Need savefile path argument (--save) when reading rom from stdin. \n " ) ;
print_help ( ) ;
exit ( 1 ) ;
}
else if ( strlen ( g_extern . savestate_name ) = = 0 )
{
SSNES_ERR ( " Need savestate path argument (--savefile) when reading rom from stdin. \n " ) ;
2010-10-01 20:10:28 +00:00
print_help ( ) ;
exit ( 1 ) ;
}
2010-10-01 19:39:15 +00:00
}
2010-08-15 08:02:04 +00:00
2011-01-10 13:29:00 +00:00
// TODO: Add rest of the controllers.
static void init_controllers ( void )
{
2011-01-10 15:53:37 +00:00
if ( g_extern . has_justifier )
2011-01-10 13:29:00 +00:00
{
2011-01-10 15:53:37 +00:00
SSNES_LOG ( " Connecting Justifier to port 2. \n " ) ;
psnes_set_controller_port_device ( SNES_PORT_2 , SNES_DEVICE_JUSTIFIER ) ;
}
else if ( g_extern . has_justifiers )
{
SSNES_LOG ( " Connecting Justifiers to port 2. \n " ) ;
psnes_set_controller_port_device ( SNES_PORT_2 , SNES_DEVICE_JUSTIFIERS ) ;
}
else if ( g_extern . has_multitap )
{
SSNES_LOG ( " Connecting multitap to port 2. \n " ) ;
psnes_set_controller_port_device ( SNES_PORT_2 , SNES_DEVICE_MULTITAP ) ;
}
else
{
for ( int i = 0 ; i < 2 ; i + + )
2011-01-10 13:29:00 +00:00
{
2011-01-10 15:53:37 +00:00
if ( g_extern . has_mouse [ i ] )
{
SSNES_LOG ( " Connecting mouse to port %d \n " , i + 1 ) ;
psnes_set_controller_port_device ( i , SNES_DEVICE_MOUSE ) ;
}
else if ( g_extern . has_scope [ i ] )
{
SSNES_LOG ( " Connecting scope to port %d \n " , i + 1 ) ;
psnes_set_controller_port_device ( i , SNES_DEVICE_SUPER_SCOPE ) ;
}
2011-01-10 13:29:00 +00:00
}
}
}
2011-01-12 20:57:55 +00:00
static inline void load_save_files ( void )
2011-01-12 18:09:24 +00:00
{
2011-01-12 20:57:55 +00:00
switch ( g_extern . game_type )
2011-01-12 18:09:24 +00:00
{
case SSNES_CART_NORMAL :
2011-01-12 20:57:55 +00:00
case SSNES_CART_SGB :
2011-01-18 14:34:37 +00:00
load_ram_file ( g_extern . savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
load_ram_file ( g_extern . savefile_name_rtc , SNES_MEMORY_CARTRIDGE_RTC ) ;
2011-01-12 20:57:55 +00:00
break ;
case SSNES_CART_BSX :
case SSNES_CART_BSX_SLOTTED :
2011-01-18 14:34:37 +00:00
load_ram_file ( g_extern . savefile_name_srm , SNES_MEMORY_BSX_RAM ) ;
load_ram_file ( g_extern . savefile_name_psrm , SNES_MEMORY_BSX_PRAM ) ;
2011-01-12 20:57:55 +00:00
break ;
case SSNES_CART_SUFAMI :
2011-01-18 14:34:37 +00:00
load_ram_file ( g_extern . savefile_name_asrm , SNES_MEMORY_SUFAMI_TURBO_A_RAM ) ;
load_ram_file ( g_extern . savefile_name_bsrm , SNES_MEMORY_SUFAMI_TURBO_B_RAM ) ;
2011-01-12 20:57:55 +00:00
break ;
default :
break ;
2011-01-12 18:09:24 +00:00
}
}
2011-01-12 20:57:55 +00:00
static inline void save_files ( void )
2011-01-12 18:09:24 +00:00
{
2011-01-12 20:57:55 +00:00
switch ( g_extern . game_type )
2011-01-12 18:09:24 +00:00
{
case SSNES_CART_NORMAL :
2011-01-12 20:57:55 +00:00
case SSNES_CART_SGB :
2011-01-18 14:34:37 +00:00
save_ram_file ( g_extern . savefile_name_srm , SNES_MEMORY_CARTRIDGE_RAM ) ;
save_ram_file ( g_extern . savefile_name_rtc , SNES_MEMORY_CARTRIDGE_RTC ) ;
2011-01-12 20:57:55 +00:00
break ;
2011-01-12 18:09:24 +00:00
2011-01-12 20:57:55 +00:00
case SSNES_CART_BSX :
case SSNES_CART_BSX_SLOTTED :
2011-01-18 14:34:37 +00:00
save_ram_file ( g_extern . savefile_name_srm , SNES_MEMORY_BSX_RAM ) ;
save_ram_file ( g_extern . savefile_name_psrm , SNES_MEMORY_BSX_PRAM ) ;
2011-01-12 20:57:55 +00:00
break ;
2011-01-12 17:05:57 +00:00
2011-01-12 20:57:55 +00:00
case SSNES_CART_SUFAMI :
2011-01-18 14:34:37 +00:00
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 ) ;
2011-01-12 20:57:55 +00:00
break ;
2011-01-12 18:09:24 +00:00
2011-01-12 20:57:55 +00:00
default :
break ;
}
2011-01-12 17:05:57 +00:00
}
2011-01-12 20:57:55 +00:00
2011-01-12 17:05:57 +00:00
# ifdef HAVE_FFMPEG
static void init_recording ( void )
{
// Hardcode these options at the moment. Should be specificed in the config file later on.
if ( g_extern . recording )
{
struct ffemu_rational ntsc_fps = { 60000 , 1001 } ;
struct ffemu_rational pal_fps = { 50000 , 1001 } ;
struct ffemu_params params = {
. vcodec = FFEMU_VIDEO_H264 ,
. acodec = FFEMU_AUDIO_VORBIS ,
. rescaler = FFEMU_RESCALER_POINT ,
. out_width = 512 ,
. out_height = 448 ,
. channels = 2 ,
. samplerate = 32040 ,
. filename = g_extern . record_path ,
. fps = psnes_get_region ( ) = = SNES_REGION_NTSC ? ntsc_fps : pal_fps ,
. aspect_ratio = 4.0 / 3
} ;
SSNES_LOG ( " Recording with FFmpeg to %s. \n " , g_extern . record_path ) ;
g_extern . rec = ffemu_new ( & params ) ;
if ( ! g_extern . rec )
{
SSNES_ERR ( " Failed to start FFmpeg recording. \n " ) ;
g_extern . recording = false ;
}
}
}
static void deinit_recording ( void )
{
if ( g_extern . recording )
{
ffemu_finalize ( g_extern . rec ) ;
ffemu_free ( g_extern . rec ) ;
}
}
# endif
2011-01-23 01:23:20 +00:00
static void init_msg_queue ( void )
{
g_extern . msg_queue = msg_queue_new ( 8 ) ;
assert ( g_extern . msg_queue ) ;
}
static void deinit_msg_queue ( void )
{
if ( g_extern . msg_queue )
msg_queue_free ( g_extern . msg_queue ) ;
}
2011-01-12 20:57:55 +00:00
static void fill_pathnames ( void )
{
switch ( g_extern . game_type )
{
case SSNES_CART_BSX :
case SSNES_CART_BSX_SLOTTED :
// BSX PSRM
fill_pathname ( g_extern . savefile_name_psrm , g_extern . savefile_name_srm , " .psrm " ) ;
break ;
case SSNES_CART_SUFAMI :
// SUFAMI ARAM
fill_pathname ( g_extern . savefile_name_asrm , g_extern . savefile_name_srm , " .asrm " ) ;
// SUFAMI BRAM
fill_pathname ( g_extern . savefile_name_bsrm , g_extern . savefile_name_srm , " .bsrm " ) ;
break ;
default :
// Infer .rtc save path from save ram path.
fill_pathname ( g_extern . savefile_name_rtc , g_extern . savefile_name_srm , " .rtc " ) ;
}
}
2011-01-12 17:05:57 +00:00
2010-10-01 19:39:15 +00:00
int main ( int argc , char * argv [ ] )
{
2010-12-29 20:12:56 +00:00
parse_input ( argc , argv ) ;
2010-12-30 00:33:40 +00:00
parse_config ( ) ;
2010-12-30 12:54:49 +00:00
init_dlsym ( ) ;
psnes_init ( ) ;
2010-12-30 13:11:56 +00:00
if ( strlen ( g_extern . basename ) > 0 )
psnes_set_cartridge_basename ( g_extern . basename ) ;
2010-12-30 12:54:49 +00:00
SSNES_LOG ( " Version of libsnes API: %u.%u \n " , psnes_library_revision_major ( ) , psnes_library_revision_minor ( ) ) ;
2010-10-01 19:39:15 +00:00
2011-01-12 20:57:55 +00:00
fill_pathnames ( ) ;
2011-01-12 17:05:57 +00:00
2011-01-12 20:57:55 +00:00
if ( ! init_rom_file ( g_extern . game_type ) )
2011-01-12 17:05:57 +00:00
goto error ;
2010-10-01 19:39:15 +00:00
init_drivers ( ) ;
2010-12-30 12:54:49 +00: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 ) ;
2011-01-12 17:05:57 +00:00
2011-01-10 13:29:00 +00:00
init_controllers ( ) ;
2011-01-10 06:58:11 +00:00
2011-01-12 17:05:57 +00:00
load_save_files ( ) ;
2010-05-26 19:27:37 +00:00
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
2011-01-12 17:05:57 +00:00
init_recording ( ) ;
2011-01-05 19:07:55 +00:00
# endif
2011-01-03 19:46:50 +00:00
2011-01-23 01:23:20 +00:00
init_msg_queue ( ) ;
2011-01-08 17:37:45 +00:00
// Main loop
2010-05-26 19:27:37 +00:00
for ( ; ; )
{
2011-01-08 18:15:18 +00:00
// Time to drop?
2011-01-08 17:37:45 +00:00
if ( driver . input - > key_pressed ( driver . input_data , SSNES_QUIT_KEY ) | |
2011-01-06 19:01:32 +00:00
! driver . video - > alive ( driver . video_data ) )
2010-05-26 19:27:37 +00:00
break ;
2011-01-08 17:37:45 +00:00
set_fast_forward_button ( driver . input - > key_pressed ( driver . input_data , SSNES_FAST_FORWARD_KEY ) ) ;
2011-01-08 18:15:18 +00:00
// Save or load state here.
2011-01-23 01:23:20 +00:00
static bool old_should_savestate = false ;
bool should_savestate = driver . input - > key_pressed ( driver . input_data , SSNES_SAVE_STATE_KEY ) ;
if ( should_savestate & & ! old_should_savestate )
{
2011-01-23 01:48:06 +00:00
msg_queue_push ( g_extern . msg_queue , " Saving state! ^_^ " , 1 , 180 ) ;
2011-01-18 14:34:37 +00:00
save_state ( g_extern . savestate_name ) ;
2011-01-23 01:23:20 +00:00
}
old_should_savestate = should_savestate ;
static bool old_should_loadstate = false ;
bool should_loadstate = driver . input - > key_pressed ( driver . input_data , SSNES_LOAD_STATE_KEY ) ;
if ( ! should_savestate & & should_loadstate & & ! old_should_loadstate )
{
2011-01-23 01:48:06 +00:00
msg_queue_push ( g_extern . msg_queue , " Loading state! ^_^ " , 1 , 180 ) ;
2011-01-18 14:34:37 +00:00
load_state ( g_extern . savestate_name ) ;
2011-01-23 01:23:20 +00:00
}
old_should_loadstate = should_loadstate ;
2010-05-26 22:39:56 +00:00
2011-01-08 18:15:18 +00:00
// If we go fullscreen we drop all drivers and reinit to be safe.
2011-01-23 01:23:20 +00:00
if ( driver . input - > key_pressed ( driver . input_data , SSNES_FULLSCREEN_TOGGLE_KEY ) )
2010-05-26 19:41:57 +00:00
{
2010-12-29 18:18:37 +00:00
g_settings . video . fullscreen = ! g_settings . video . fullscreen ;
2010-05-28 00:45:18 +00:00
uninit_drivers ( ) ;
init_drivers ( ) ;
2010-05-26 19:41:57 +00:00
}
2010-05-26 19:27:37 +00:00
2011-01-08 18:15:18 +00:00
// Run libsnes for one frame.
2010-12-30 12:54:49 +00:00
psnes_run ( ) ;
2010-05-26 19:27:37 +00:00
}
2011-01-23 01:23:20 +00:00
deinit_msg_queue ( ) ;
2011-01-05 19:07:55 +00:00
# ifdef HAVE_FFMPEG
2011-01-12 17:05:57 +00:00
deinit_recording ( ) ;
2011-01-05 19:07:55 +00:00
# endif
2011-01-03 19:46:50 +00:00
2011-01-12 17:05:57 +00:00
save_files ( ) ;
2010-05-26 19:27:37 +00:00
2010-12-30 12:54:49 +00:00
psnes_unload_cartridge ( ) ;
psnes_term ( ) ;
2010-05-28 00:45:18 +00:00
uninit_drivers ( ) ;
2010-12-30 12:54:49 +00:00
uninit_dlsym ( ) ;
2010-05-26 19:27:37 +00:00
return 0 ;
2010-06-27 13:46:23 +00:00
error :
2010-12-30 12:54:49 +00:00
psnes_unload_cartridge ( ) ;
psnes_term ( ) ;
2010-06-27 13:46:23 +00:00
uninit_drivers ( ) ;
2010-12-30 12:54:49 +00:00
uninit_dlsym ( ) ;
2010-06-27 13:46:23 +00:00
return 1 ;
2010-05-26 19:27:37 +00:00
}
2010-05-26 22:39:56 +00:00