2016-03-30 20:07:55 +00:00
# include <stdio.h>
# include <stdbool.h>
# include <unistd.h>
# include <time.h>
# include <assert.h>
2016-07-17 19:43:23 +00:00
# include <signal.h>
2017-05-21 18:44:28 +00:00
# include <SDL2/SDL.h>
2016-08-20 14:51:17 +00:00
# ifndef _WIN32
# define AUDIO_FREQUENCY 96000
# else
/* Windows (well, at least my VM) can't handle 96KHz sound well :( */
# define AUDIO_FREQUENCY 44100
# include <direct.h>
# include <windows.h>
# define snprintf _snprintf
# endif
2016-03-30 20:07:55 +00:00
2017-05-21 18:44:28 +00:00
# if defined(__APPLE__) && SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION == 0 && SDL_PATCHLEVEL == 5
# error You are using SDL2-2.0.5, which contains a bug that prevents 96KHz audio playback on macOS. Use SDL2-2.0.4 or alternatively modify this file to reduce the frequency to 44100 and disable this error.
# endif
2016-03-30 20:07:55 +00:00
# include "gb.h"
2016-07-18 11:37:06 +00:00
static char * filename ;
2017-05-21 18:44:28 +00:00
static char * battery_save_path_ptr ;
2016-07-18 11:37:06 +00:00
static void replace_extension ( const char * src , size_t length , char * dest , const char * ext ) ;
2017-05-21 18:44:28 +00:00
static SDL_Window * window = NULL ;
static SDL_Renderer * renderer = NULL ;
static SDL_Texture * texture = NULL ;
static SDL_PixelFormat * pixel_format = NULL ;
static uint32_t pixels [ 160 * 144 ] ;
2016-07-18 11:37:06 +00:00
GB_gameboy_t gb ;
2016-06-10 12:48:40 +00:00
2017-05-21 18:44:28 +00:00
static enum {
GB_SDL_NO_COMMAND ,
GB_SDL_SAVE_STATE_COMMAND ,
GB_SDL_LOAD_STATE_COMMAND ,
GB_SDL_RESET_COMMAND ,
} pending_command ;
static unsigned command_parameter ;
static void handle_events ( GB_gameboy_t * gb )
2016-03-30 20:07:55 +00:00
{
static bool ctrl = false ;
2016-07-18 11:37:06 +00:00
static bool shift = false ;
# ifdef __APPLE__
static bool cmd = false ;
2017-05-21 18:44:28 +00:00
# define MODIFIER cmd
# else
# define MODIFIER ctrl
2016-07-18 11:37:06 +00:00
# endif
2016-03-30 20:07:55 +00:00
SDL_Event event ;
while ( SDL_PollEvent ( & event ) )
{
switch ( event . type ) {
case SDL_QUIT :
2017-05-21 18:44:28 +00:00
GB_save_battery ( gb , battery_save_path_ptr ) ;
exit ( 0 ) ;
2016-03-30 20:07:55 +00:00
case SDL_KEYDOWN :
2017-05-21 18:44:28 +00:00
switch ( event . key . keysym . sym ) {
case SDLK_c :
if ( ctrl & & event . type = = SDL_KEYDOWN ) {
ctrl = false ;
GB_debugger_break ( gb ) ;
}
break ;
case SDLK_r :
if ( MODIFIER ) {
pending_command = GB_SDL_RESET_COMMAND ;
}
break ;
case SDLK_m :
if ( MODIFIER ) {
# ifdef __APPLE__
// Can't over CMD+M (Minimize) in SDL
if ( ! shift ) {
break ;
}
# endif
SDL_PauseAudio ( SDL_GetAudioStatus ( ) = = SDL_AUDIO_PLAYING ? true : false ) ;
}
break ;
default :
/* Save states */
if ( event . key . keysym . sym > = SDLK_0 & & event . key . keysym . sym < = SDLK_9 ) {
if ( MODIFIER ) {
command_parameter = event . key . keysym . sym - SDLK_0 ;
if ( shift ) {
pending_command = GB_SDL_LOAD_STATE_COMMAND ;
}
else {
pending_command = GB_SDL_SAVE_STATE_COMMAND ;
}
}
}
break ;
}
case SDL_KEYUP : // Fallthrough
2016-03-30 20:07:55 +00:00
switch ( event . key . keysym . sym ) {
case SDLK_RIGHT :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_RIGHT , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_LEFT :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_LEFT , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_UP :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_UP , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_DOWN :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_DOWN , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_x :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_A , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_z :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_B , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_BACKSPACE :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_SELECT , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_RETURN :
2017-04-17 17:16:17 +00:00
GB_set_key_state ( gb , GB_KEY_START , event . type = = SDL_KEYDOWN ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_SPACE :
2017-04-19 18:55:58 +00:00
GB_set_turbo_mode ( gb , event . type = = SDL_KEYDOWN , false ) ;
2016-03-30 20:07:55 +00:00
break ;
case SDLK_LCTRL :
2016-07-18 11:37:06 +00:00
case SDLK_RCTRL :
2016-03-30 20:07:55 +00:00
ctrl = event . type = = SDL_KEYDOWN ;
break ;
2016-07-18 11:37:06 +00:00
case SDLK_LSHIFT :
case SDLK_RSHIFT :
shift = event . type = = SDL_KEYDOWN ;
break ;
# ifdef __APPLE__
2017-05-21 18:44:28 +00:00
case SDLK_LGUI :
case SDLK_RGUI :
2016-07-18 11:37:06 +00:00
cmd = event . type = = SDL_KEYDOWN ;
break ;
# endif
2016-03-30 20:07:55 +00:00
}
break ;
default :
break ;
}
}
}
2016-08-20 14:51:17 +00:00
static void vblank ( GB_gameboy_t * gb )
2016-03-30 20:07:55 +00:00
{
2017-05-21 18:44:28 +00:00
SDL_UpdateTexture ( texture , NULL , pixels , 160 * sizeof ( uint32_t ) ) ;
SDL_RenderClear ( renderer ) ;
SDL_RenderCopy ( renderer , texture , NULL , NULL ) ;
SDL_RenderPresent ( renderer ) ;
handle_events ( gb ) ;
2016-03-30 20:07:55 +00:00
}
# ifdef __APPLE__
# include <mach-o/dyld.h>
# endif
static const char * executable_folder ( void )
{
static char path [ 1024 ] = { 0 , } ;
if ( path [ 0 ] ) {
return path ;
}
/* Ugly unportable code! :( */
# ifdef __APPLE__
unsigned int length = sizeof ( path ) - 1 ;
_NSGetExecutablePath ( & path [ 0 ] , & length ) ;
# else
# ifdef __linux__
ssize_t length = readlink ( " /proc/self/exe " , & path [ 0 ] , sizeof ( path ) - 1 ) ;
assert ( length ! = - 1 ) ;
# else
2016-08-20 14:51:17 +00:00
# ifdef _WIN32
HMODULE hModule = GetModuleHandle ( NULL ) ;
GetModuleFileName ( hModule , path , sizeof ( path ) - 1 ) ;
# else
2016-03-30 20:07:55 +00:00
/* No OS-specific way, assume running from CWD */
getcwd ( & path [ 0 ] , sizeof ( path ) - 1 ) ;
return path ;
# endif
2016-08-20 14:51:17 +00:00
# endif
2016-03-30 20:07:55 +00:00
# endif
size_t pos = strlen ( path ) ;
while ( pos ) {
pos - - ;
2016-08-20 14:51:17 +00:00
# ifdef _WIN32
if ( path [ pos ] = = ' \\ ' ) {
# else
2016-03-30 20:07:55 +00:00
if ( path [ pos ] = = ' / ' ) {
2016-08-20 14:51:17 +00:00
# endif
2016-03-30 20:07:55 +00:00
path [ pos ] = 0 ;
break ;
}
}
return path ;
}
static char * executable_relative_path ( const char * filename )
{
static char path [ 1024 ] ;
snprintf ( path , sizeof ( path ) , " %s/%s " , executable_folder ( ) , filename ) ;
return path ;
}
2017-05-21 18:44:28 +00:00
2016-06-18 17:29:11 +00:00
static uint32_t rgb_encode ( GB_gameboy_t * gb , uint8_t r , uint8_t g , uint8_t b )
2016-03-30 20:07:55 +00:00
{
2017-05-21 18:44:28 +00:00
return SDL_MapRGB ( pixel_format , r , g , b ) ;
2016-03-30 20:07:55 +00:00
}
2016-04-06 19:58:30 +00:00
static void debugger_interrupt ( int ignore )
{
2016-07-18 10:10:19 +00:00
/* ^C twice to exit */
2017-04-17 17:16:17 +00:00
if ( GB_debugger_is_stopped ( & gb ) ) {
2016-07-18 10:10:19 +00:00
exit ( 0 ) ;
}
2017-04-17 17:16:17 +00:00
GB_debugger_break ( & gb ) ;
2016-04-06 19:58:30 +00:00
}
2016-05-23 19:22:09 +00:00
static void audio_callback ( void * gb , Uint8 * stream , int len )
{
2016-06-18 17:29:11 +00:00
GB_apu_copy_buffer ( gb , ( GB_sample_t * ) stream , len / sizeof ( GB_sample_t ) ) ;
2016-05-23 19:22:09 +00:00
}
2016-07-17 20:08:07 +00:00
static void replace_extension ( const char * src , size_t length , char * dest , const char * ext )
{
memcpy ( dest , src , length ) ;
dest [ length ] = 0 ;
/* Remove extension */
for ( size_t i = length ; i - - ; ) {
if ( dest [ i ] = = ' / ' ) break ;
if ( dest [ i ] = = ' . ' ) {
dest [ i ] = 0 ;
break ;
}
}
/* Add new extension */
strcat ( dest , ext ) ;
}
2016-03-30 20:07:55 +00:00
int main ( int argc , char * * argv )
{
bool dmg = false ;
2016-04-01 20:36:43 +00:00
# define str(x) #x
# define xstr(x) str(x)
fprintf ( stderr , " SameBoy v " xstr ( VERSION ) " \n " ) ;
2016-03-30 20:07:55 +00:00
if ( argc = = 1 | | argc > 3 ) {
usage :
fprintf ( stderr , " Usage: %s [--dmg] rom \n " , argv [ 0 ] ) ;
exit ( 1 ) ;
}
if ( argc = = 3 ) {
if ( strcmp ( argv [ 1 ] , " --dmg " ) = = 0 ) {
dmg = true ;
}
else {
goto usage ;
}
}
if ( dmg ) {
2016-06-18 17:29:11 +00:00
GB_init ( & gb ) ;
if ( GB_load_boot_rom ( & gb , executable_relative_path ( " dmg_boot.bin " ) ) ) {
2016-03-30 20:07:55 +00:00
perror ( " Failed to load boot ROM " ) ;
exit ( 1 ) ;
}
}
else {
2016-06-18 17:29:11 +00:00
GB_init_cgb ( & gb ) ;
if ( GB_load_boot_rom ( & gb , executable_relative_path ( " cgb_boot.bin " ) ) ) {
2016-03-30 20:07:55 +00:00
perror ( " Failed to load boot ROM " ) ;
exit ( 1 ) ;
}
}
2016-07-18 11:37:06 +00:00
filename = argv [ argc - 1 ] ;
if ( GB_load_rom ( & gb , filename ) ) {
2016-03-30 20:07:55 +00:00
perror ( " Failed to load ROM " ) ;
exit ( 1 ) ;
}
2016-04-06 19:58:30 +00:00
signal ( SIGINT , debugger_interrupt ) ;
2016-03-30 20:07:55 +00:00
SDL_Init ( SDL_INIT_EVERYTHING ) ;
2017-05-21 18:44:28 +00:00
window = SDL_CreateWindow ( " SameBoy v " xstr ( VERSION ) , SDL_WINDOWPOS_UNDEFINED , SDL_WINDOWPOS_UNDEFINED ,
160 , 144 , SDL_WINDOW_OPENGL ) ;
renderer = SDL_CreateRenderer ( window , - 1 , 0 ) ;
texture = SDL_CreateTexture ( renderer , SDL_GetWindowPixelFormat ( window ) , SDL_TEXTUREACCESS_STREAMING , 160 , 144 ) ;
pixel_format = SDL_AllocFormat ( SDL_GetWindowPixelFormat ( window ) ) ;
2016-05-23 19:22:09 +00:00
/* Configure Screen */
2016-06-18 17:29:11 +00:00
GB_set_vblank_callback ( & gb , ( GB_vblank_callback_t ) vblank ) ;
2017-05-21 18:44:28 +00:00
GB_set_pixels_output ( & gb , pixels ) ;
2016-06-18 17:29:11 +00:00
GB_set_rgb_encode_callback ( & gb , rgb_encode ) ;
2016-03-30 20:07:55 +00:00
2016-07-18 11:37:06 +00:00
size_t path_length = strlen ( filename ) ;
2016-06-10 12:48:40 +00:00
2016-07-17 20:08:07 +00:00
/* Configure battery */
char battery_save_path [ path_length + 5 ] ; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */
2016-07-18 11:37:06 +00:00
replace_extension ( filename , path_length , battery_save_path , " .sav " ) ;
2017-05-21 18:44:28 +00:00
battery_save_path_ptr = battery_save_path ;
2016-06-18 17:29:11 +00:00
GB_load_battery ( & gb , battery_save_path ) ;
2016-06-10 12:48:40 +00:00
2016-07-17 20:08:07 +00:00
/* Configure symbols */
2017-02-20 12:37:15 +00:00
GB_debugger_load_symbol_file ( & gb , executable_relative_path ( " registers.sym " ) ) ;
2016-07-17 20:08:07 +00:00
char symbols_path [ path_length + 5 ] ;
2016-07-18 11:37:06 +00:00
replace_extension ( filename , path_length , symbols_path , " .sym " ) ;
2016-07-17 20:08:07 +00:00
GB_debugger_load_symbol_file ( & gb , symbols_path ) ;
2016-05-23 19:22:09 +00:00
/* Configure Audio */
SDL_AudioSpec want , have ;
SDL_memset ( & want , 0 , sizeof ( want ) ) ;
2016-08-20 14:51:17 +00:00
want . freq = AUDIO_FREQUENCY ;
2016-05-23 19:22:09 +00:00
want . format = AUDIO_S16SYS ;
2016-06-10 12:28:50 +00:00
want . channels = 2 ;
2016-05-23 19:22:09 +00:00
want . samples = 512 ;
want . callback = audio_callback ;
want . userdata = & gb ;
SDL_OpenAudio ( & want , & have ) ;
2017-05-21 18:44:28 +00:00
GB_set_sample_rate ( & gb , have . freq ) ;
2016-05-23 19:22:09 +00:00
/* Start Audio */
2017-05-21 18:44:28 +00:00
SDL_PauseAudio ( false ) ;
2016-04-08 09:37:09 +00:00
2016-05-23 19:22:09 +00:00
/* Run emulation */
2017-05-21 18:44:28 +00:00
while ( true ) {
2016-06-18 17:29:11 +00:00
GB_run ( & gb ) ;
2017-05-21 18:44:28 +00:00
switch ( pending_command ) {
case GB_SDL_LOAD_STATE_COMMAND :
case GB_SDL_SAVE_STATE_COMMAND : {
char save_path [ strlen ( filename ) + 4 ] ;
char save_extension [ ] = " .s0 " ;
save_extension [ 2 ] + = command_parameter ;
replace_extension ( filename , strlen ( filename ) , save_path , save_extension ) ;
if ( pending_command = = GB_SDL_LOAD_STATE_COMMAND ) {
GB_load_state ( & gb , save_path ) ;
}
else {
GB_save_state ( & gb , save_path ) ;
}
break ;
}
case GB_SDL_RESET_COMMAND :
GB_reset ( & gb ) ;
break ;
case GB_SDL_NO_COMMAND :
break ;
}
pending_command = GB_SDL_NO_COMMAND ;
2016-03-30 20:07:55 +00:00
}
}