#include #include #include #include #include #include #include #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 #else /* Windows (well, at least my VM) can't handle 96KHz sound well :( */ #define AUDIO_FREQUENCY 44100 #include #include #define snprintf _snprintf #endif #include "gb.h" #include "debugger.h" static bool running = false; static char *filename; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); GB_gameboy_t gb; static void GB_update_keys_status(GB_gameboy_t *gb) { static bool ctrl = false; static bool shift = false; #ifdef __APPLE__ static bool cmd = false; #endif SDL_Event event; while (SDL_PollEvent(&event)) { switch( event.type ){ case SDL_QUIT: running = false; case SDL_KEYDOWN: case SDL_KEYUP: gb->stopped = false; switch (event.key.keysym.sym) { case SDLK_RIGHT: gb->keys[0] = event.type == SDL_KEYDOWN; break; case SDLK_LEFT: gb->keys[1] = event.type == SDL_KEYDOWN; break; case SDLK_UP: gb->keys[2] = event.type == SDL_KEYDOWN; break; case SDLK_DOWN: gb->keys[3] = event.type == SDL_KEYDOWN; break; case SDLK_x: gb->keys[4] = event.type == SDL_KEYDOWN; break; case SDLK_z: gb->keys[5] = event.type == SDL_KEYDOWN; break; case SDLK_BACKSPACE: gb->keys[6] = event.type == SDL_KEYDOWN; break; case SDLK_RETURN: gb->keys[7] = event.type == SDL_KEYDOWN; break; case SDLK_SPACE: gb->turbo = event.type == SDL_KEYDOWN; break; case SDLK_LCTRL: case SDLK_RCTRL: ctrl = event.type == SDL_KEYDOWN; break; case SDLK_LSHIFT: case SDLK_RSHIFT: shift = event.type == SDL_KEYDOWN; break; #ifdef __APPLE__ case SDLK_LMETA: case SDLK_RMETA: cmd = event.type == SDL_KEYDOWN; break; #endif case SDLK_c: if (ctrl && event.type == SDL_KEYDOWN) { ctrl = false; gb->debug_stopped = true; } break; default: /* Save states */ if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { #ifdef __APPLE__ if (cmd) { #else if (ctrl) { #endif char save_path[strlen(filename) + 4]; char save_extension[] =".s0"; save_extension[2] += event.key.keysym.sym - SDLK_0; replace_extension(filename, strlen(filename), save_path, save_extension); if (shift) { GB_load_state(gb, save_path); } else { GB_save_state(gb, save_path); } } } break; } break; default: break; } } } static void vblank(GB_gameboy_t *gb) { SDL_Surface *screen = gb->user_data; SDL_Flip(screen); GB_update_keys_status(gb); GB_set_pixels_output(gb, screen->pixels); } #ifdef __APPLE__ #include #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 #ifdef _WIN32 HMODULE hModule = GetModuleHandle(NULL); GetModuleFileName(hModule, path, sizeof(path) - 1); #else /* No OS-specific way, assume running from CWD */ getcwd(&path[0], sizeof(path) - 1); return path; #endif #endif #endif size_t pos = strlen(path); while (pos) { pos--; #ifdef _WIN32 if (path[pos] == '\\') { #else if (path[pos] == '/') { #endif 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; } static SDL_Surface *screen = NULL; static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return SDL_MapRGB(screen->format, r, g, b); } static void debugger_interrupt(int ignore) { /* ^C twice to exit */ if (gb.debug_stopped) { exit(0); } gb.debug_stopped = true; } static void audio_callback(void *gb, Uint8 *stream, int len) { GB_apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); } 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); } #ifdef __APPLE__ extern void cocoa_disable_filtering(void); #endif int main(int argc, char **argv) { bool dmg = false; #define str(x) #x #define xstr(x) str(x) fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); 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) { GB_init(&gb); if (GB_load_boot_rom(&gb, executable_relative_path("dmg_boot.bin"))) { perror("Failed to load boot ROM"); exit(1); } } else { GB_init_cgb(&gb); if (GB_load_boot_rom(&gb, executable_relative_path("cgb_boot.bin"))) { perror("Failed to load boot ROM"); exit(1); } } filename = argv[argc - 1]; if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); exit(1); } signal(SIGINT, debugger_interrupt); SDL_Init( SDL_INIT_EVERYTHING ); screen = SDL_SetVideoMode(160, 144, 32, SDL_SWSURFACE ); SDL_WM_SetCaption("SameBoy v" xstr(VERSION), "SameBoy v" xstr(VERSION)); #ifdef __APPLE__ cocoa_disable_filtering(); #endif /* Configure Screen */ SDL_LockSurface(screen); GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); gb.user_data = screen; GB_set_pixels_output(&gb, screen->pixels); GB_set_rgb_encode_callback(&gb, rgb_encode); size_t path_length = strlen(filename); /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ replace_extension(filename, path_length, battery_save_path, ".sav"); GB_load_battery(&gb, battery_save_path); /* Configure symbols */ char symbols_path[path_length + 5]; replace_extension(filename, path_length, symbols_path, ".sym"); GB_debugger_load_symbol_file(&gb, symbols_path); /* Configure Audio */ SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); want.freq = AUDIO_FREQUENCY; want.format = AUDIO_S16SYS; want.channels = 2; want.samples = 512; want.callback = audio_callback; want.userdata = &gb; SDL_OpenAudio(&want, &have); GB_set_sample_rate(&gb, AUDIO_FREQUENCY); /* Start Audio */ SDL_PauseAudio(0); /* Run emulation */ running = true; while (running) { GB_run(&gb); } SDL_CloseAudio(); GB_save_battery(&gb, battery_save_path); return 0; }