git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

SDL/main.c

      1 #include <stdbool.h>
      2 #include <stdio.h>
      3 #include <signal.h>
      4 #include <ctype.h>
      5 #include <unistd.h>
      6 #include <errno.h>
      7 #include <limits.h>
      8 #include <fcntl.h>
      9 #include <SDL.h>
     10 #include <Core/gb.h>
     11 #include "open_dialog/open_dialog.h"
     12 #include "utils.h"
     13 #include "gui.h"
     14 #include "shader.h"
     15 #include "audio/audio.h"
     16 #include "console.h"
     17 #include "save_png/save_png.h"
     18 
     19 #ifdef _WIN32
     20 #include <Windows.h>
     21 #include "windows_associations.h"
     22 #endif
     23 
     24 static bool stop_on_start = false;
     25 GB_gameboy_t gb;
     26 static bool paused = false;
     27 static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224];
     28 static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2;
     29 static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false;
     30 static bool rapid_a = false, rapid_b = false;
     31 static uint8_t rapid_a_count = 0, rapid_b_count = 0;
     32 static double clock_mutliplier = 1.0;
     33 static bool pending_screenshot = false;
     34 
     35 char *filename = NULL;
     36 static typeof(free) *free_function = NULL;
     37 static char *battery_save_path_ptr = NULL;
     38 static SDL_GLContext gl_context = NULL;
     39 static bool console_supported = false;
     40 static bool battery_dirty = false;
     41 static unsigned battery_timer = 0;
     42 
     43 bool uses_gl(void)
     44 {
     45     return gl_context;
     46 }
     47 
     48 void rerender_screen(void)
     49 {
     50     render_texture(active_pixel_buffer, configuration.blending_mode? previous_pixel_buffer : NULL);
     51 #ifdef _WIN32
     52     /* Required for some Windows 10 machines, god knows why */
     53     render_texture(active_pixel_buffer, configuration.blending_mode? previous_pixel_buffer : NULL);
     54 #endif
     55 }
     56 
     57 void set_filename(const char *new_filename, typeof(free) *new_free_function)
     58 {
     59     if (filename && free_function) {
     60         free_function(filename);
     61     }
     62     filename = (char *) new_filename;
     63     free_function = new_free_function;
     64     GB_rewind_reset(&gb);
     65 }
     66 
     67 static char *completer(const char *substring, uintptr_t *context)
     68 {
     69     if (!GB_is_inited(&gb)) return NULL;
     70     char *temp = strdup(substring);
     71     char *ret = GB_debugger_complete_substring(&gb, temp, context);
     72     free(temp);
     73     return ret;
     74 }
     75 
     76 static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes_t attributes)
     77 {
     78     CON_attributes_t con_attributes = {0,};
     79     con_attributes.bold = attributes & GB_LOG_BOLD;
     80     con_attributes.underline = attributes & GB_LOG_UNDERLINE;
     81     if (attributes & GB_LOG_DASHED_UNDERLINE) {
     82         while (*string) {
     83             con_attributes.underline ^= true;
     84             CON_attributed_printf("%c", &con_attributes, *string);
     85             string++;
     86         }
     87     }
     88     else {
     89         CON_attributed_print(string, &con_attributes);
     90     }
     91 }
     92 
     93 static void handle_eof(void)
     94 {
     95     CON_set_async_prompt("");
     96     char *line = CON_readline("Quit? [y]/n > ");
     97     if (line[0] == 'n' || line[0] == 'N') {
     98         free(line);
     99         CON_set_async_prompt("> ");
    100     }
    101     else {
    102         exit(0);
    103     }
    104 }
    105 
    106 static char *input_callback(GB_gameboy_t *gb)
    107 {
    108     if (CON_no_csi_mode()) {
    109         fprintf(stdout, "> ");
    110         fflush(stdout);
    111     }
    112 #ifdef _WIN32
    113     DWORD pid;
    114     GetWindowThreadProcessId(GetForegroundWindow(), &pid);
    115     if (pid == GetCurrentProcessId()) {
    116         BringWindowToTop(GetConsoleWindow());
    117     }
    118 #endif
    119 retry: {
    120     CON_set_async_prompt("Stopped> ");
    121     char *ret = CON_readline_async();
    122     if (!ret) {
    123 #ifdef _WIN32
    124         HWND window = GetConsoleWindow();
    125         if (pending_command == GB_SDL_HIDE_DEBUGGER_COMMAND || !window) return strdup("c");
    126         ShowWindow(window, SW_SHOW);
    127 #endif
    128         SDL_Event event;
    129         SDL_WaitEvent(&event);
    130         if (pending_command == GB_SDL_QUIT_COMMAND) {
    131             return strdup("c");
    132         }
    133         switch (event.type) {
    134             case SDL_DISPLAYEVENT:
    135                 update_swap_interval();
    136                 break;
    137                 
    138             case SDL_WINDOWEVENT: {
    139                 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
    140                     screen_manually_resized = true;
    141                     update_viewport();
    142                 }
    143                 if (event.window.type == SDL_WINDOWEVENT_MOVED
    144 #if SDL_COMPILEDVERSION > 2018
    145                     || event.window.type == SDL_WINDOWEVENT_DISPLAY_CHANGED
    146 #endif
    147                     ) {
    148                     update_swap_interval();
    149                 }
    150                 rerender_screen();
    151                 break;
    152             }
    153             case SDL_QUIT:
    154                 pending_command = GB_SDL_QUIT_COMMAND;
    155                 return strdup("c");
    156             case SDL_KEYDOWN:
    157                 fputc('\a', stdout);
    158                 fflush(stdout);
    159                 break;
    160             default:
    161                 break;
    162         }
    163         goto retry;
    164     }
    165     if (strcmp(ret, CON_EOF) == 0) {
    166         free(ret);
    167         handle_eof();
    168         goto retry;
    169     }
    170     else if (!CON_no_csi_mode()) {
    171         CON_attributes_t attributes = {.bold = true};
    172         CON_attributed_printf("> %s\n", &attributes, ret);
    173     }
    174     CON_set_async_prompt("> ");
    175     return ret;
    176 }
    177 }
    178 
    179 static char *async_input_callback(GB_gameboy_t *gb)
    180 {
    181 retry: {
    182     char *ret = CON_readline_async();
    183     if (ret && strcmp(ret, CON_EOF) == 0) {
    184         handle_eof();
    185         free(ret);
    186         goto retry;
    187     }
    188     else if (ret) {
    189         CON_attributes_t attributes = {.bold = true};
    190         CON_attributed_printf("> %s\n", &attributes, ret);
    191     }
    192     return ret;
    193 }
    194 }
    195 
    196 
    197 static char *captured_log = NULL;
    198 
    199 static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes_t attributes)
    200 {
    201     size_t current_len = strlen(captured_log);
    202     size_t len_to_add = strlen(string);
    203     captured_log = realloc(captured_log, current_len + len_to_add + 1);
    204     memcpy(captured_log + current_len, string, len_to_add);
    205     captured_log[current_len + len_to_add] = 0;
    206 }
    207 
    208 static void *start_capturing_logs(void)
    209 {
    210     void *previous = captured_log;
    211     captured_log = malloc(1);
    212     captured_log[0] = 0;
    213     GB_set_log_callback(&gb, log_capture_callback);
    214     return previous;
    215 }
    216 
    217 static void end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title, void *previous)
    218 {
    219     if (!previous) {
    220         GB_set_log_callback(&gb, console_supported? log_callback : NULL);
    221     }
    222     if (captured_log[0] != 0) {
    223         if (show_popup) {
    224             SDL_ShowSimpleMessageBox(popup_flags, title, captured_log, window);
    225         }
    226         if (should_exit) {
    227             exit(1);
    228         }
    229     }
    230     free(captured_log);
    231     captured_log = previous;
    232 }
    233 
    234 static void update_palette(void)
    235 {
    236     GB_set_palette(&gb, current_dmg_palette());
    237 }
    238 
    239 static void screen_size_changed(bool resize_window)
    240 {
    241     SDL_DestroyTexture(texture);
    242     texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING,
    243                                 GB_get_screen_width(&gb), GB_get_screen_height(&gb));
    244     
    245     SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb));
    246     
    247     if (resize_window) {
    248         signed current_window_width, current_window_height;
    249         SDL_GetWindowSize(window, &current_window_width, &current_window_height);
    250 
    251         signed width = GB_get_screen_width(&gb) * configuration.default_scale;
    252         signed height = GB_get_screen_height(&gb) * configuration.default_scale;
    253         signed x, y;
    254         SDL_GetWindowPosition(window, &x, &y);
    255         SDL_SetWindowSize(window, width, height);
    256         SDL_SetWindowPosition(window, x - (width - current_window_width) / 2, y - (height - current_window_height) / 2);
    257     }
    258     
    259     update_viewport();
    260 }
    261 
    262 static void open_menu(void)
    263 {
    264     bool audio_playing = GB_audio_is_playing();
    265     if (audio_playing) {
    266         GB_audio_set_paused(true);
    267     }
    268     size_t previous_width = GB_get_screen_width(&gb);
    269     size_t previous_height = GB_get_screen_height(&gb);
    270     run_gui(true);
    271     rerender_screen();
    272     SDL_ShowCursor(SDL_DISABLE);
    273     if (audio_playing) {
    274         GB_audio_set_paused(false);
    275     }
    276     GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
    277     GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
    278     GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
    279     GB_set_border_mode(&gb, configuration.border_mode);
    280     update_palette();
    281     GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
    282     GB_set_rewind_length(&gb, configuration.rewind_length);
    283     GB_set_rtc_mode(&gb, configuration.rtc_mode);
    284     GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0);
    285     if (previous_width != GB_get_screen_width(&gb)) {
    286         signed current_window_width, current_window_height;
    287         SDL_GetWindowSize(window, &current_window_width, &current_window_height);
    288 
    289         screen_size_changed(current_window_width == previous_width * configuration.default_scale &&
    290                             current_window_height == previous_height * configuration.default_scale);
    291     }
    292 }
    293 
    294 static void console_line_ready(void)
    295 {
    296     static SDL_Event event = {
    297         .type = SDL_USEREVENT
    298     };
    299     SDL_PushEvent(&event);
    300 }
    301 
    302 static void configure_console(void)
    303 {
    304     CON_set_async_prompt("> ");
    305     CON_set_repeat_empty(true);
    306     CON_set_line_ready_callback(console_line_ready);
    307     GB_set_log_callback(&gb, log_callback);
    308     GB_set_input_callback(&gb, input_callback);
    309     GB_set_async_input_callback(&gb, async_input_callback);
    310 }
    311 
    312 static void save_screenshot(void)
    313 {
    314     static char png_path[PATH_MAX] = {0,};
    315     time_t now = time(NULL);
    316     struct tm *local = localtime(&now);
    317     char timestring[20];
    318     strftime(timestring, sizeof(timestring), "%Y-%m-%d %H.%M.%S", local);
    319     
    320     replace_extension(filename, strlen(filename), png_path, "");
    321     snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s.png", timestring);
    322     
    323     unsigned i = 2;
    324     while (access(png_path, F_OK) == 0) {
    325         replace_extension(filename, strlen(filename), png_path, "");
    326         snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s %u.png", timestring, i);
    327         i++;
    328     }
    329     
    330     if (save_png(png_path, GB_get_screen_width(&gb), GB_get_screen_height(&gb), active_pixel_buffer, pixel_format)) {
    331         show_osd_text("Screenshot saved");
    332     }
    333     else {
    334         show_osd_text("Failed to save screenshot");
    335     }
    336 }
    337 
    338 static void handle_events(GB_gameboy_t *gb)
    339 {
    340     SDL_Event event;
    341     while (SDL_PollEvent(&event)) {
    342         switch (event.type) {
    343             case SDL_DISPLAYEVENT:
    344                 update_swap_interval();
    345                 break;
    346             case SDL_QUIT:
    347                 pending_command = GB_SDL_QUIT_COMMAND;
    348                 break;
    349                 
    350             case SDL_DROPFILE: {
    351                 if (GB_is_save_state(event.drop.file)) {
    352                     dropped_state_file = event.drop.file;
    353                     pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;
    354                 }
    355                 else {
    356                     set_filename(event.drop.file, SDL_free);
    357                     pending_command = GB_SDL_NEW_FILE_COMMAND;
    358                 }
    359                 break;
    360             }
    361                 
    362             case SDL_WINDOWEVENT: {
    363                 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
    364                     screen_manually_resized = true;
    365                     update_viewport();
    366                 }
    367                 if (event.window.type == SDL_WINDOWEVENT_MOVED
    368 #if SDL_COMPILEDVERSION > 2018
    369                     || event.window.type == SDL_WINDOWEVENT_DISPLAY_CHANGED
    370 #endif
    371                     ) {
    372                     update_swap_interval();
    373                 }
    374                 break;
    375             }
    376             case SDL_MOUSEBUTTONDOWN:
    377             case SDL_MOUSEBUTTONUP: {
    378                 if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) {
    379                     GB_set_key_state(gb, GB_KEY_A, event.type == SDL_MOUSEBUTTONDOWN);
    380                 }
    381                 break;
    382             }
    383                 
    384             case SDL_MOUSEMOTION: {
    385                 if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) {
    386                     signed x = event.motion.x;
    387                     signed y = event.motion.y;
    388                     convert_mouse_coordinates(&x, &y);
    389                     x = SDL_max(SDL_min(x, 160), 0);
    390                     y = SDL_max(SDL_min(y, 144), 0);
    391                     GB_set_accelerometer_values(gb, (x - 80) / -80.0, (y - 72) / -72.0);
    392                 }
    393                 break;
    394             }
    395                 
    396             case SDL_JOYDEVICEREMOVED:
    397                 if (joystick && event.jdevice.which == SDL_JoystickInstanceID(joystick)) {
    398                     SDL_JoystickClose(joystick);
    399                     joystick = NULL;
    400                 }
    401             case SDL_JOYDEVICEADDED:
    402                 connect_joypad();
    403                 break;
    404                 
    405             case SDL_JOYBUTTONUP:
    406             case SDL_JOYBUTTONDOWN: {
    407                 joypad_button_t button = get_joypad_button(event.jbutton.button);
    408                 if ((GB_key_t) button < GB_KEY_MAX) {
    409                     GB_set_key_state(gb, (GB_key_t) button, event.type == SDL_JOYBUTTONDOWN);
    410                 }
    411                 else if (button == JOYPAD_BUTTON_TURBO) {
    412                     GB_audio_clear_queue();
    413                     turbo_down = event.type == SDL_JOYBUTTONDOWN;
    414                     GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
    415                     SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
    416                 }
    417                 else if (button == JOYPAD_BUTTON_SLOW_MOTION) {
    418                     underclock_down = event.type == SDL_JOYBUTTONDOWN;
    419                 }
    420                 else if (button == JOYPAD_BUTTON_REWIND) {
    421                     rewind_down = event.type == SDL_JOYBUTTONDOWN;
    422                     if (event.type == SDL_JOYBUTTONUP) {
    423                         rewind_paused = false;
    424                     }
    425                     GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
    426                     SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
    427                 }
    428                 else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) {
    429                     open_menu();
    430                 }
    431                 else if ((button == JOYPAD_BUTTON_HOTKEY_1 || button == JOYPAD_BUTTON_HOTKEY_2) && event.type == SDL_JOYBUTTONDOWN) {
    432                     hotkey_action_t action = configuration.hotkey_actions[button - JOYPAD_BUTTON_HOTKEY_1];
    433                     switch (action) {
    434                         case HOTKEY_NONE:
    435                             break;
    436                         case HOTKEY_PAUSE:
    437                             paused = !paused;
    438                             if (paused) {
    439                                 GB_save_battery(gb, battery_save_path_ptr);
    440                             }
    441                             break;
    442                         case HOTKEY_MUTE:
    443                             GB_audio_set_paused(GB_audio_is_playing());
    444                             break;
    445                         case HOTKEY_RESET:
    446                             pending_command = GB_SDL_RESET_COMMAND;
    447                             break;
    448                         case HOTKEY_QUIT:
    449                             pending_command = GB_SDL_QUIT_COMMAND;
    450                             break;
    451                         default:
    452                             command_parameter = (action - HOTKEY_SAVE_STATE_1) / 2 + 1;
    453                             pending_command = ((action - HOTKEY_SAVE_STATE_1) % 2)? GB_SDL_LOAD_STATE_COMMAND:GB_SDL_SAVE_STATE_COMMAND;
    454                             break;
    455                         case HOTKEY_SAVE_STATE_10:
    456                             command_parameter = 0;
    457                             pending_command = GB_SDL_SAVE_STATE_COMMAND;
    458                             break;
    459                         case HOTKEY_LOAD_STATE_10:
    460                             command_parameter = 0;
    461                             pending_command = GB_SDL_LOAD_STATE_COMMAND;
    462                             break;
    463                     }
    464                 }
    465                 else if (button == JOYPAD_BUTTON_RAPID_A) {
    466                     rapid_a = event.type == SDL_JOYBUTTONDOWN;
    467                     rapid_a_count = 0;
    468                     GB_set_key_state(gb, GB_KEY_A, event.type == SDL_JOYBUTTONDOWN);
    469                 }
    470                 else if (button == JOYPAD_BUTTON_RAPID_B) {
    471                     rapid_b = event.type == SDL_JOYBUTTONDOWN;
    472                     rapid_b_count = 0;
    473                     GB_set_key_state(gb, GB_KEY_B, event.type == SDL_JOYBUTTONDOWN);
    474                 }
    475             }
    476             break;
    477                 
    478             case SDL_JOYAXISMOTION: {
    479                 static bool axis_active[2] = {false, false};
    480                 static double accel_values[2] = {0, 0};
    481                 static double axis_values[2] = {0, 0};
    482                 joypad_axis_t axis = get_joypad_axis(event.jaxis.axis);
    483                 if (axis == JOYPAD_AXISES_X) {
    484                     if (GB_has_accelerometer(gb)) {
    485                         accel_values[0] = event.jaxis.value / (double)32768.0;
    486                         GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]);
    487                     }
    488                     else if (configuration.use_faux_analog_inputs) {
    489                         axis_values[0] = event.jaxis.value / (double)32768.0;
    490                     }
    491                     else if (event.jaxis.value > JOYSTICK_HIGH) {
    492                         axis_active[0] = true;
    493                         GB_set_use_faux_analog_inputs(gb, 0, false);
    494                         GB_set_key_state(gb, GB_KEY_RIGHT, true);
    495                         GB_set_key_state(gb, GB_KEY_LEFT, false);
    496                     }
    497                     else if (event.jaxis.value < -JOYSTICK_HIGH) {
    498                         axis_active[0] = true;
    499                         GB_set_use_faux_analog_inputs(gb, 0, false);
    500                         GB_set_key_state(gb, GB_KEY_RIGHT, false);
    501                         GB_set_key_state(gb, GB_KEY_LEFT, true);
    502                     }
    503                     else if (axis_active[0] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) {
    504                         axis_active[0] = false;
    505                         GB_set_key_state(gb, GB_KEY_RIGHT, false);
    506                         GB_set_key_state(gb, GB_KEY_LEFT, false);
    507                     }
    508                 }
    509                 else if (axis == JOYPAD_AXISES_Y) {
    510                     if (GB_has_accelerometer(gb)) {
    511                         accel_values[1] = event.jaxis.value / (double)32768.0;
    512                         GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]);
    513                     }
    514                     else if (configuration.use_faux_analog_inputs) {
    515                         axis_values[1] = event.jaxis.value / (double)32768.0;
    516                     }
    517                     else if (event.jaxis.value > JOYSTICK_HIGH) {
    518                         axis_active[1] = true;
    519                         GB_set_use_faux_analog_inputs(gb, 0, false);
    520                         GB_set_key_state(gb, GB_KEY_DOWN, true);
    521                         GB_set_key_state(gb, GB_KEY_UP, false);
    522                     }
    523                     else if (event.jaxis.value < -JOYSTICK_HIGH) {
    524                         axis_active[1] = true;
    525                         GB_set_use_faux_analog_inputs(gb, 0, false);
    526                         GB_set_key_state(gb, GB_KEY_DOWN, false);
    527                         GB_set_key_state(gb, GB_KEY_UP, true);
    528                     }
    529                     else if (axis_active[1] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) {
    530                         axis_active[1] = false;
    531                         GB_set_key_state(gb, GB_KEY_DOWN, false);
    532                         GB_set_key_state(gb, GB_KEY_UP, false);
    533                     }
    534                 }
    535                 if (configuration.use_faux_analog_inputs && !GB_has_accelerometer(gb)) {
    536                     GB_set_use_faux_analog_inputs(gb, 0, true);
    537                     GB_set_faux_analog_inputs(gb, 0, axis_values[0], axis_values[1]);
    538                 }
    539                 break;
    540             }
    541                 
    542             case SDL_JOYHATMOTION: {
    543                 uint8_t value = event.jhat.value;
    544                 int8_t updown =
    545                 value == SDL_HAT_LEFTUP || value == SDL_HAT_UP || value == SDL_HAT_RIGHTUP ? -1 : (value == SDL_HAT_LEFTDOWN || value == SDL_HAT_DOWN || value == SDL_HAT_RIGHTDOWN ? 1 : 0);
    546                 int8_t leftright =
    547                 value == SDL_HAT_LEFTUP || value == SDL_HAT_LEFT || value == SDL_HAT_LEFTDOWN ? -1 : (value == SDL_HAT_RIGHTUP || value == SDL_HAT_RIGHT || value == SDL_HAT_RIGHTDOWN ? 1 : 0);
    548                 
    549                 GB_set_use_faux_analog_inputs(gb, 0, false);
    550                 GB_set_key_state(gb, GB_KEY_LEFT, leftright == -1);
    551                 GB_set_key_state(gb, GB_KEY_RIGHT, leftright == 1);
    552                 GB_set_key_state(gb, GB_KEY_UP, updown == -1);
    553                 GB_set_key_state(gb, GB_KEY_DOWN, updown == 1);
    554                 break;
    555             };
    556                 
    557             case SDL_KEYDOWN:
    558                 switch (event_hotkey_code(&event)) {
    559                     case SDL_SCANCODE_ESCAPE: {
    560                         open_menu();
    561                         break;
    562                     }
    563                     case SDL_SCANCODE_C:
    564                         if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
    565                             pending_command = GB_SDL_DEBUGGER_INTERRUPT_COMMAND;
    566                         }
    567                         break;
    568                         
    569                     case SDL_SCANCODE_R:
    570                         if (event.key.keysym.mod & MODIFIER) {
    571                             pending_command = GB_SDL_RESET_COMMAND;
    572                             paused = false;
    573                         }
    574                         break;
    575                         
    576                     case SDL_SCANCODE_O: {
    577                         if (event.key.keysym.mod & MODIFIER) {
    578                             char *filename = do_open_rom_dialog();
    579                             if (filename) {
    580                                 set_filename(filename, free);
    581                                 pending_command = GB_SDL_NEW_FILE_COMMAND;
    582                             }
    583                         }
    584                         break;
    585                     }
    586                         
    587                     case SDL_SCANCODE_P:
    588                         if (event.key.keysym.mod & MODIFIER) {
    589                             paused = !paused;
    590                             if (paused) {
    591                                 GB_save_battery(gb, battery_save_path_ptr);
    592                             }
    593                         }
    594                         break;
    595                     case SDL_SCANCODE_M:
    596                         if (event.key.keysym.mod & MODIFIER) {
    597 #ifdef __APPLE__
    598                             // Can't override CMD+M (Minimize) in SDL
    599                             if (!(event.key.keysym.mod & KMOD_SHIFT)) {
    600                                 break;
    601                             }
    602 #endif
    603                             GB_audio_set_paused(GB_audio_is_playing());
    604                         }
    605                         break;
    606 #ifdef __APPLE__
    607                     case SDL_SCANCODE_S:
    608                         if (!(event.key.keysym.mod & MODIFIER)) break;
    609 #else
    610                     case SDL_SCANCODE_F12:
    611 #endif
    612                         if (osd_countdown && configuration.osd) {
    613                             pending_screenshot = true;
    614                             break;
    615                         }
    616                         
    617                         save_screenshot();
    618                         break;
    619                         
    620                     case SDL_SCANCODE_F:
    621                         if (event.key.keysym.mod & MODIFIER) {
    622                             if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
    623                                 SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
    624                             }
    625                             else {
    626                                 SDL_SetWindowFullscreen(window, 0);
    627                             }
    628                             update_swap_interval();
    629                             update_viewport();
    630                             screen_manually_resized = true;
    631                         }
    632                         break;
    633                         
    634                     default:
    635                         /* Save states */
    636                         if (event.key.keysym.scancode >= SDL_SCANCODE_1 && event.key.keysym.scancode <= SDL_SCANCODE_0) {
    637                             if (event.key.keysym.mod & MODIFIER) {
    638                                 command_parameter = (event.key.keysym.scancode - SDL_SCANCODE_1 + 1) % 10;
    639                                 
    640                                 if (event.key.keysym.mod & KMOD_SHIFT) {
    641                                     pending_command = GB_SDL_LOAD_STATE_COMMAND;
    642                                 }
    643                                 else {
    644                                     pending_command = GB_SDL_SAVE_STATE_COMMAND;
    645                                 }
    646                             }
    647                             else if ((event.key.keysym.mod & KMOD_ALT) && event.key.keysym.scancode <= SDL_SCANCODE_4) {
    648                                 GB_channel_t channel = event.key.keysym.scancode - SDL_SCANCODE_1;
    649                                 bool state = !GB_is_channel_muted(gb, channel);
    650                                 
    651                                 GB_set_channel_muted(gb, channel, state);
    652                                 
    653                                 static char message[18];
    654                                 sprintf(message, "Channel %d %smuted", channel + 1, state? "" : "un");
    655                                 show_osd_text(message);
    656                             }
    657                         }
    658                         break;
    659                 }
    660             case SDL_KEYUP: // Fallthrough
    661                 if (event.key.keysym.scancode == configuration.keys[GB_CONF_KEYS_TURBO]) {
    662                     turbo_down = event.type == SDL_KEYDOWN;
    663                     GB_audio_clear_queue();
    664                     GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
    665                     SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
    666                 }
    667                 else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_REWIND]) {
    668                     rewind_down = event.type == SDL_KEYDOWN;
    669                     if (event.type == SDL_KEYUP) {
    670                         rewind_paused = false;
    671                     }
    672                     GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
    673                     SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
    674                 }
    675                 else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_UNDERCLOCK]) {
    676                     underclock_down = event.type == SDL_KEYDOWN;
    677                 }
    678                 else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_RAPID_A]) {
    679                     rapid_a = event.type == SDL_KEYDOWN;
    680                     rapid_a_count = 0;
    681                     GB_set_key_state(gb, GB_KEY_A, event.type == SDL_KEYDOWN);
    682                 }
    683                 else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_RAPID_B]) {
    684                     rapid_b = event.type == SDL_KEYDOWN;
    685                     rapid_b_count = 0;
    686                     GB_set_key_state(gb, GB_KEY_B, event.type == SDL_KEYDOWN);
    687                 }
    688                 else {
    689                     for (unsigned i = 0; i < GB_KEY_MAX; i++) {
    690                         if (event.key.keysym.scancode == configuration.keys[i]) {
    691                             if (i <= GB_KEY_DOWN) {
    692                                 GB_set_use_faux_analog_inputs(gb, 0, false);
    693                             }
    694                             GB_set_key_state(gb, i, event.type == SDL_KEYDOWN);
    695                         }
    696                     }
    697                 }
    698                 break;
    699             default:
    700                 break;
    701         }
    702     }
    703 }
    704 
    705 static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
    706 {
    707     return SDL_MapRGB(pixel_format, r, g, b);
    708 }
    709 
    710 static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
    711 {
    712     if (underclock_down && clock_mutliplier > 0.5) {
    713         clock_mutliplier -= 1.0/16;
    714         GB_set_clock_multiplier(gb, clock_mutliplier);
    715     }
    716     else if (!underclock_down && clock_mutliplier < 1.0) {
    717         clock_mutliplier += 1.0/16;
    718         GB_set_clock_multiplier(gb, clock_mutliplier);
    719     }
    720     
    721     if (rapid_a) {
    722         rapid_a_count++;
    723         GB_set_key_state(gb, GB_KEY_A, !(rapid_a_count & 2));
    724     }
    725     if (rapid_b) {
    726         rapid_b_count++;
    727         GB_set_key_state(gb, GB_KEY_B, !(rapid_b_count & 2));
    728     }
    729     
    730     if (turbo_down) {
    731         show_osd_text("Fast forward...");
    732     }
    733     else if (underclock_down) {
    734         show_osd_text("Slow motion...");
    735     }
    736     else if (rewind_down) {
    737         show_osd_text("Rewinding...");
    738     }
    739     
    740     if (pending_screenshot) {
    741         pending_screenshot = false;
    742         save_screenshot();
    743     }
    744     
    745     if (osd_countdown && configuration.osd) {
    746         if (osd_countdown != 1) {
    747             unsigned width = GB_get_screen_width(gb);
    748             unsigned height = GB_get_screen_height(gb);
    749             draw_text(active_pixel_buffer,
    750                       width, height, 8, height - 8 - osd_text_lines * 12, osd_text,
    751                       rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0),
    752                       true);
    753         }
    754         osd_countdown--;
    755     }
    756     if (type != GB_VBLANK_TYPE_REPEAT) {
    757         if (configuration.blending_mode) {
    758             render_texture(active_pixel_buffer, previous_pixel_buffer);
    759             uint32_t *temp = active_pixel_buffer;
    760             active_pixel_buffer = previous_pixel_buffer;
    761             previous_pixel_buffer = temp;
    762             GB_set_pixels_output(gb, active_pixel_buffer);
    763         }
    764         else {
    765             render_texture(active_pixel_buffer, NULL);
    766         }
    767     }
    768     do_rewind = rewind_down;
    769     
    770     battery_timer++;
    771     if (battery_timer == 15) {
    772         battery_timer = 0;
    773         
    774         if (battery_dirty && !GB_get_battery_dirty(gb)) {
    775             GB_save_battery(gb, battery_save_path_ptr);
    776             GB_log(gb, "Saved\n");
    777         }
    778         
    779         battery_dirty = GB_get_battery_dirty(gb);
    780         GB_clear_battery_dirty(gb);
    781     }
    782     
    783     handle_events(gb);
    784 }
    785 
    786 static void rumble(GB_gameboy_t *gb, double amp)
    787 {
    788     if (configuration.rumble_strength != 8) {
    789         double strength = configuration.rumble_strength / 8.0;
    790         amp = pow(amp, strength) * strength;
    791     }
    792     SDL_HapticRumblePlay(haptic, amp, 250);
    793 }
    794 
    795 static void debugger_interrupt(int ignore)
    796 {
    797 #ifndef _WIN32
    798     if (!GB_is_inited(&gb)) {
    799         exit(0);
    800     }
    801     if (GB_debugger_is_stopped(&gb)) {
    802         pending_command = GB_SDL_QUIT_COMMAND;
    803         console_line_ready(); // Force the debugger wait-loop to process the command
    804         return;
    805     }
    806 #endif
    807     pending_command = GB_SDL_DEBUGGER_INTERRUPT_COMMAND;
    808 
    809 }
    810 
    811 #ifndef _WIN32
    812 static void debugger_reset(int ignore)
    813 {
    814     pending_command = GB_SDL_RESET_COMMAND;
    815 }
    816 #endif
    817 
    818 static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
    819 {
    820     if (turbo_down) {
    821         static unsigned skip = 0;
    822         skip++;
    823         if (skip == GB_audio_get_frequency() / 8) {
    824             skip = 0;
    825         }
    826         if (configuration.turbo_cap) {
    827             if (skip > GB_audio_get_frequency() / 8 * 4 / configuration.turbo_cap) {
    828                 return;
    829             }
    830         }
    831         else {
    832 
    833             if (skip > GB_audio_get_frequency() / 16) {
    834                 return;
    835             }
    836         }
    837     }
    838     
    839     if (GB_audio_get_queue_length() > GB_audio_get_frequency() / 8) { // Maximum lag of 0.125s
    840         return;
    841     }
    842     
    843     if (configuration.volume != 100) {
    844         sample->left = sample->left * configuration.volume / 100;
    845         sample->right = sample->right * configuration.volume / 100;
    846     }
    847     
    848     GB_audio_queue_sample(sample);
    849     
    850 }
    851 
    852 #ifdef _WIN32
    853 static BOOL windows_console_handler(DWORD signal)
    854 {
    855     /*
    856      Hack: prevents process termination on console close
    857      https://twitter.com/yo_yo_yo_jbo/status/1904592584326218069
    858      Thanks JBO!
    859     */
    860     if (signal == CTRL_C_EVENT) {
    861         /* Only happens in no-csi mode */
    862         pending_command = GB_SDL_DEBUGGER_INTERRUPT_COMMAND;
    863         TerminateThread(GetCurrentThread(), 0);
    864     }
    865     
    866     pending_command = GB_SDL_HIDE_DEBUGGER_COMMAND;
    867     console_line_ready();
    868     TerminateThread(GetCurrentThread(), 0);
    869     return false;
    870 }
    871 
    872 static void initialize_windows_console(void)
    873 {
    874     if (AllocConsole()) {
    875         SetConsoleTitle("SameBoy Debugger Console");
    876         freopen("CONIN$", "r", stdin);
    877         setvbuf(stdin, NULL, _IONBF, 0);
    878         freopen("CONOUT$", "w", stdout);
    879         setvbuf(stdout, NULL, _IONBF, 0);
    880         freopen("CONOUT$", "w", stderr);
    881         setvbuf(stderr, NULL, _IONBF, 0);
    882         
    883         console_supported = CON_start(completer);
    884         if (console_supported) {
    885             configure_console();
    886         }
    887         
    888         /* I would set a callback via SetConsoleCtrlHandler, but the function (CtrlRoutine) that
    889            eventually calls our callback takes a lock and  doesn't release it (as it expects the
    890            process to exit afterwards). The solution is to take a more violent approach and hook
    891            it instead. */
    892         
    893 #if defined(__x86_64__) || defined(__i386__)
    894         uint8_t *patch_address = (void *)(GetProcAddress(GetModuleHandleA("KernelBase.dll"), "CtrlRoutine") ?:
    895                                           GetProcAddress(GetModuleHandleA("Kernel32.dll"), "CtrlRoutine"));
    896 #else
    897         uint8_t *patch_address = NULL;
    898 #endif
    899         if (!patch_address) {
    900             EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
    901         }
    902         else {
    903             DWORD old_protection;
    904             VirtualProtect(patch_address, 0x20, PAGE_READWRITE, &old_protection);
    905             if (sizeof(&windows_console_handler) == 8) {
    906                 *(patch_address++) = 0x48; // movabs
    907             }
    908             *(patch_address++) = 0xb8; // mov
    909             (*(uintptr_t *)patch_address) = (uintptr_t)&windows_console_handler;
    910             patch_address += sizeof(&windows_console_handler);
    911             // jmp rax/eax
    912             *(patch_address++) = 0xff;
    913             *(patch_address++) = 0xe0;
    914             VirtualProtect(patch_address, 0x20, old_protection, &old_protection);
    915         }
    916     }
    917 }
    918 
    919 #endif
    920     
    921 static bool doing_hot_swap = false;
    922 static bool handle_pending_command(void)
    923 {
    924     switch (pending_command) {
    925         case GB_SDL_LOAD_STATE_COMMAND:
    926         case GB_SDL_SAVE_STATE_COMMAND: {
    927             char save_path[strlen(filename) + 5];
    928             char save_extension[] = ".s0";
    929             save_extension[2] += command_parameter;
    930             replace_extension(filename, strlen(filename), save_path, save_extension);
    931             
    932             void *previous = start_capturing_logs();
    933             bool success;
    934             if (pending_command == GB_SDL_LOAD_STATE_COMMAND) {
    935                 int result = GB_load_state(&gb, save_path);
    936                 if (result == ENOENT) {
    937                     char save_extension[] = ".sn0";
    938                     save_extension[3] += command_parameter;
    939                     replace_extension(filename, strlen(filename), save_path, save_extension);
    940                     start_capturing_logs();
    941                     result = GB_load_state(&gb, save_path);
    942                 }
    943                 success = result == 0;
    944             }
    945             else {
    946                 success = GB_save_state(&gb, save_path) == 0;
    947             }
    948             end_capturing_logs(true,
    949                                false,
    950                                success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR,
    951                                success? "Notice" : "Error",
    952                                previous);
    953             if (success) {
    954                 show_osd_text(pending_command == GB_SDL_LOAD_STATE_COMMAND? "State loaded" : "State saved");
    955             }
    956             return false;
    957         }
    958     
    959         case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: {
    960             void *previous = start_capturing_logs();
    961             bool success = GB_load_state(&gb, dropped_state_file) == 0;
    962             end_capturing_logs(true,
    963                                false,
    964                                success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR,
    965                                success? "Notice" : "Error",
    966                                previous);
    967             SDL_free(dropped_state_file);
    968             if (success) {
    969                 show_osd_text("State loaded");
    970             }
    971             return false;
    972         }
    973             
    974         case GB_SDL_NO_COMMAND:
    975             return false;
    976             
    977         case GB_SDL_CART_SWAP_COMMAND:
    978             doing_hot_swap = true;
    979         case GB_SDL_RESET_COMMAND:
    980         case GB_SDL_NEW_FILE_COMMAND:
    981             GB_save_battery(&gb, battery_save_path_ptr);
    982             return true;
    983             
    984         case GB_SDL_QUIT_COMMAND:
    985             GB_save_battery(&gb, battery_save_path_ptr);
    986             exit(0);
    987         case GB_SDL_DEBUGGER_INTERRUPT_COMMAND:
    988             if (!GB_is_inited(&gb)) exit(0);
    989             
    990 #ifdef _WIN32
    991             initialize_windows_console();
    992 #endif
    993 
    994             /* ^C twice to exit */
    995             if (GB_debugger_is_stopped(&gb)) {
    996 #ifndef _WIN32
    997                 GB_save_battery(&gb, battery_save_path_ptr);
    998                 exit(0);
    999 #else
   1000                 break;
   1001 #endif
   1002             }
   1003             if (console_supported) {
   1004                 CON_print("^C\n");
   1005             }
   1006             GB_debugger_break(&gb);
   1007             break;
   1008 #if _WIN32
   1009         case GB_SDL_HIDE_DEBUGGER_COMMAND:
   1010             HWND console_window = GetConsoleWindow();
   1011             ShowWindow(console_window, SW_HIDE);
   1012             FreeConsole();
   1013             SDL_RaiseWindow(window);
   1014             break;
   1015 #endif
   1016 
   1017     }
   1018     return false;
   1019 }
   1020 
   1021 static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
   1022 {
   1023     static const char *const names[] = {
   1024         [GB_BOOT_ROM_DMG_0] = "dmg0_boot.bin",
   1025         [GB_BOOT_ROM_DMG] = "dmg_boot.bin",
   1026         [GB_BOOT_ROM_MGB] = "mgb_boot.bin",
   1027         [GB_BOOT_ROM_SGB] = "sgb_boot.bin",
   1028         [GB_BOOT_ROM_SGB2] = "sgb2_boot.bin",
   1029         [GB_BOOT_ROM_CGB_0] = "cgb0_boot.bin",
   1030         [GB_BOOT_ROM_CGB] = "cgb_boot.bin",
   1031         [GB_BOOT_ROM_CGB_E] = "cgbE_boot.bin",
   1032         [GB_BOOT_ROM_AGB_0] = "agb0_boot.bin",
   1033         [GB_BOOT_ROM_AGB] = "agb_boot.bin",
   1034     };
   1035     bool use_built_in = true;
   1036     if (configuration.bootrom_path[0]) {
   1037         static char path[PATH_MAX + 1];
   1038         snprintf(path, sizeof(path), "%s/%s", configuration.bootrom_path, names[type]);
   1039         use_built_in = GB_load_boot_rom(gb, path);
   1040     }
   1041     if (use_built_in) {
   1042         void *previous = start_capturing_logs();
   1043         if (GB_load_boot_rom(gb, resource_path(names[type]))) {
   1044             if (type == GB_BOOT_ROM_CGB_E) {
   1045                 end_capturing_logs(false, false, 0, NULL, previous);
   1046                 load_boot_rom(gb, GB_BOOT_ROM_CGB);
   1047                 return;
   1048             }
   1049             if (type == GB_BOOT_ROM_AGB_0) {
   1050                 end_capturing_logs(false, false, 0, NULL, previous);
   1051                 load_boot_rom(gb, GB_BOOT_ROM_AGB);
   1052                 return;
   1053             }
   1054         }
   1055         end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR, "Error", previous);
   1056     }
   1057 }
   1058 
   1059 static bool is_path_writeable(const char *path)
   1060 {
   1061     if (!access(path, W_OK)) return true;
   1062     int fd = creat(path, 0644);
   1063     if (fd == -1) return false;
   1064     close(fd);
   1065     unlink(path);
   1066     return true;
   1067 }
   1068 
   1069 static void debugger_reload_callback(GB_gameboy_t *gb)
   1070 {
   1071     size_t path_length = strlen(filename);
   1072     char extension[4] = {0,};
   1073     if (path_length > 4) {
   1074         if (filename[path_length - 4] == '.') {
   1075             extension[0] = tolower((unsigned char)filename[path_length - 3]);
   1076             extension[1] = tolower((unsigned char)filename[path_length - 2]);
   1077             extension[2] = tolower((unsigned char)filename[path_length - 1]);
   1078         }
   1079     }
   1080     if (strcmp(extension, "isx") == 0) {
   1081         GB_load_isx(gb, filename);
   1082     }
   1083     else {
   1084         GB_load_rom(gb, filename);
   1085     }
   1086     
   1087     GB_load_battery(gb, battery_save_path_ptr);
   1088     
   1089     GB_debugger_clear_symbols(gb);
   1090     GB_debugger_load_symbol_file(gb, resource_path("registers.sym"));
   1091     
   1092     char symbols_path[path_length + 5];
   1093     replace_extension(filename, path_length, symbols_path, ".sym");
   1094     GB_debugger_load_symbol_file(gb, symbols_path);
   1095     
   1096     GB_reset(gb);
   1097 }
   1098 
   1099 static GB_model_t model_to_use(void)
   1100 {
   1101     typeof(configuration.model) gui_model = configuration.model;
   1102     if (gui_model == MODEL_AUTO) {
   1103         uint8_t *rom = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, NULL, NULL);
   1104         if (!rom) {
   1105             gui_model = MODEL_CGB;
   1106         }
   1107         else if (rom[0x143] & 0x80) { // Has CGB features
   1108             gui_model = MODEL_CGB;
   1109         }
   1110         else if (rom[0x146] == 3) { // Has SGB features
   1111             gui_model = MODEL_SGB;
   1112         }
   1113         else if (rom[0x14B] == 1) { // Nintendo-licensed (most likely has boot ROM palettes)
   1114             gui_model = MODEL_CGB;
   1115         }
   1116         else if (rom[0x14B] == 0x33 &&
   1117                  rom[0x144] == '0' &&
   1118                  rom[0x145] == '1') { // Ditto
   1119             gui_model = MODEL_CGB;
   1120         }
   1121         else {
   1122             gui_model = MODEL_DMG;
   1123         }
   1124     }
   1125     
   1126     return (GB_model_t [])
   1127     {
   1128         [MODEL_DMG] = GB_MODEL_DMG_B,
   1129         [MODEL_CGB] = GB_MODEL_CGB_0 + configuration.cgb_revision,
   1130         [MODEL_AGB] = configuration.agb_revision,
   1131         [MODEL_MGB] = GB_MODEL_MGB,
   1132         [MODEL_SGB] = (GB_model_t [])
   1133         {
   1134             [SGB_NTSC] = GB_MODEL_SGB_NTSC,
   1135             [SGB_PAL] = GB_MODEL_SGB_PAL,
   1136             [SGB_2] = GB_MODEL_SGB2,
   1137         }[configuration.sgb_revision],
   1138     }[gui_model];
   1139 }
   1140 
   1141 static void run(void)
   1142 {
   1143     SDL_ShowCursor(SDL_DISABLE);
   1144     GB_model_t model;
   1145     pending_command = GB_SDL_NO_COMMAND;
   1146 restart:;
   1147     model = model_to_use();
   1148     bool should_resize = !screen_manually_resized;
   1149     signed current_window_width, current_window_height;
   1150     SDL_GetWindowSize(window, &current_window_width, &current_window_height);
   1151 
   1152     
   1153     if (GB_is_inited(&gb)) {
   1154         should_resize =
   1155             current_window_width == GB_get_screen_width(&gb) * configuration.default_scale &&
   1156             current_window_height == GB_get_screen_height(&gb) * configuration.default_scale;
   1157         
   1158         if (doing_hot_swap) {
   1159             doing_hot_swap = false;
   1160         }
   1161         else {
   1162             GB_switch_model_and_reset(&gb, model);
   1163         }
   1164     }
   1165     else {
   1166         GB_init(&gb, model);
   1167         
   1168         GB_set_boot_rom_load_callback(&gb, load_boot_rom);
   1169         GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
   1170         GB_set_pixels_output(&gb, active_pixel_buffer);
   1171         GB_set_rgb_encode_callback(&gb, rgb_encode);
   1172         GB_set_rumble_callback(&gb, rumble);
   1173         GB_set_rumble_mode(&gb, configuration.rumble_mode);
   1174         GB_set_sample_rate(&gb, GB_audio_get_frequency());
   1175         GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
   1176         GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
   1177         GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
   1178         update_palette();
   1179         if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) {
   1180             GB_set_border_mode(&gb, configuration.border_mode);
   1181         }
   1182         GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
   1183         GB_set_rewind_length(&gb, configuration.rewind_length);
   1184         GB_set_rtc_mode(&gb, configuration.rtc_mode);
   1185         GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0);
   1186         GB_set_update_input_hint_callback(&gb, handle_events);
   1187         GB_apu_set_sample_callback(&gb, gb_audio_callback);
   1188         
   1189         if (console_supported) {
   1190             configure_console();
   1191         }
   1192         
   1193         GB_debugger_set_reload_callback(&gb, debugger_reload_callback);
   1194     }
   1195     if (stop_on_start) {
   1196         stop_on_start = false;
   1197         GB_debugger_break(&gb);
   1198     }
   1199 
   1200     bool error = false;
   1201     GB_debugger_clear_symbols(&gb);
   1202     void *previous = start_capturing_logs();
   1203     size_t path_length = strlen(filename);
   1204     char extension[4] = {0,};
   1205     if (path_length > 4) {
   1206         if (filename[path_length - 4] == '.') {
   1207             extension[0] = tolower((unsigned char)filename[path_length - 3]);
   1208             extension[1] = tolower((unsigned char)filename[path_length - 2]);
   1209             extension[2] = tolower((unsigned char)filename[path_length - 1]);
   1210         }
   1211     }
   1212     if (strcmp(extension, "isx") == 0) {
   1213         error = GB_load_isx(&gb, filename);
   1214         /* Configure battery */
   1215         char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */
   1216         replace_extension(filename, path_length, battery_save_path, ".ram");
   1217         battery_save_path_ptr = battery_save_path;
   1218         GB_load_battery(&gb, battery_save_path);
   1219     }
   1220     else {
   1221         GB_load_rom(&gb, filename);
   1222     }
   1223     GB_model_t updated_model = model_to_use(); // Could change after loading ROM with auto setting
   1224     if (model != updated_model) {
   1225         model = updated_model;
   1226         GB_switch_model_and_reset(&gb, model);
   1227     }
   1228     
   1229     if (should_resize) {
   1230         signed width = GB_get_screen_width(&gb) * configuration.default_scale;
   1231         signed height = GB_get_screen_height(&gb) * configuration.default_scale;
   1232         signed x, y;
   1233         SDL_GetWindowPosition(window, &x, &y);
   1234         SDL_SetWindowSize(window, width, height);
   1235         SDL_SetWindowPosition(window, x - (width - current_window_width) / 2, y - (height - current_window_height) / 2);
   1236     }
   1237     
   1238     /* Configure battery */
   1239     char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */
   1240     replace_extension(filename, path_length, battery_save_path, ".sav");
   1241     battery_save_path_ptr = battery_save_path;
   1242     GB_load_battery(&gb, battery_save_path);
   1243     if (GB_save_battery_size(&gb)) {
   1244         if (!is_path_writeable(battery_save_path)) {
   1245             GB_log(&gb, "The save path for this ROM is not writeable, progress will not be saved.\n");
   1246         }
   1247     }
   1248     
   1249     char cheat_path[path_length + 5];
   1250     replace_extension(filename, path_length, cheat_path, ".cht");
   1251     // Remove all cheats before loading, so they're cleared even if loading fails.
   1252     GB_remove_all_cheats(&gb);
   1253     GB_load_cheats(&gb, cheat_path, false);
   1254     
   1255     end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning", previous);
   1256     
   1257     static char start_text[64];
   1258     static char title[17];
   1259     GB_get_rom_title(&gb, title);
   1260     sprintf(start_text, "SameBoy v" GB_VERSION "\n%s\n%08X", title, GB_get_rom_crc32(&gb));
   1261     show_osd_text(start_text);
   1262 
   1263     /* Configure symbols */
   1264     GB_debugger_load_symbol_file(&gb, resource_path("registers.sym"));
   1265     
   1266     char symbols_path[path_length + 5];
   1267     replace_extension(filename, path_length, symbols_path, ".sym");
   1268     GB_debugger_load_symbol_file(&gb, symbols_path);
   1269         
   1270     screen_size_changed(false);
   1271 
   1272     /* Run emulation */
   1273     while (true) {
   1274         if (paused || rewind_paused) {
   1275             SDL_WaitEvent(NULL);
   1276             handle_events(&gb);
   1277         }
   1278         else {
   1279             if (do_rewind) {
   1280                 GB_rewind_pop(&gb);
   1281                 if (turbo_down) {
   1282                     GB_rewind_pop(&gb);
   1283                 }
   1284                 if (!GB_rewind_pop(&gb)) {
   1285                     rewind_paused = true;
   1286                 }
   1287                 do_rewind = false;
   1288             }
   1289             GB_run(&gb);
   1290         }
   1291         
   1292         /* These commands can't run in the handle_event function, because they're not safe in a vblank context. */
   1293         if (handle_pending_command()) {
   1294             pending_command = GB_SDL_NO_COMMAND;
   1295             goto restart;
   1296         }
   1297         pending_command = GB_SDL_NO_COMMAND;
   1298     }
   1299 }
   1300 
   1301 static char prefs_path[PATH_MAX + 1] = {0, };
   1302 
   1303 static void save_configuration(void)
   1304 {
   1305     FILE *prefs_file = fopen(prefs_path, "wb");
   1306     if (prefs_file) {
   1307         fwrite(&configuration, 1, sizeof(configuration), prefs_file);
   1308         fclose(prefs_file);
   1309     }
   1310 }
   1311 
   1312 static void stop_recording(void)
   1313 {
   1314     GB_stop_audio_recording(&gb);
   1315 }
   1316 
   1317 static bool get_arg_flag(const char *flag, int *argc, char **argv)
   1318 {
   1319     for (unsigned i = 1; i < *argc; i++) {
   1320         if (strcmp(argv[i], flag) == 0) {
   1321             (*argc)--;
   1322             argv[i] = argv[*argc];
   1323             return true;
   1324         }
   1325     }
   1326     return false;
   1327 }
   1328 
   1329 static const char *get_arg_option(const char *option, int *argc, char **argv)
   1330 {
   1331     for (unsigned i = 1; i < *argc - 1; i++) {
   1332         if (strcmp(argv[i], option) == 0) {
   1333             const char *ret = argv[i + 1];
   1334             memmove(argv + i, argv + i + 2, (*argc - i - 2) * sizeof(argv[0]));
   1335             (*argc) -= 2;
   1336             return ret;
   1337         }
   1338     }
   1339     return NULL;
   1340 }
   1341 
   1342 #ifdef __APPLE__
   1343 #include <CoreFoundation/CoreFoundation.h>
   1344 static void enable_smooth_scrolling(void)
   1345 {
   1346     CFPreferencesSetAppValue(CFSTR("AppleMomentumScrollSupported"), kCFBooleanTrue, kCFPreferencesCurrentApplication);
   1347 }
   1348 #endif
   1349 
   1350 static void handle_model_option(const char *model_string)
   1351 {
   1352     static const struct {
   1353         const char *name;
   1354         GB_model_t model;
   1355         const char *description;
   1356     } name_to_model[] = {
   1357         {"auto", -1, "Pick automatically"},
   1358         {"dmg-b", GB_MODEL_DMG_B, "Game Boy, DMG-CPU B"},
   1359         {"dmg", GB_MODEL_DMG_B, "Alias of dmg-b"},
   1360         {"sgb-ntsc", GB_MODEL_SGB_NTSC, "Super Game Boy (NTSC)"},
   1361         {"sgb-pal", GB_MODEL_SGB_PAL, "Super Game Boy (PAL)"},
   1362         {"sgb2", GB_MODEL_SGB2, "Super Game Boy 2"},
   1363         {"sgb", GB_MODEL_SGB, "Alias of sgb-ntsc"},
   1364         {"mgb", GB_MODEL_MGB, "Game Boy Pocket/Light"},
   1365         {"cgb-0", GB_MODEL_CGB_0, "Game Boy Color, CPU CGB"},
   1366         {"cgb-a", GB_MODEL_CGB_A, "Game Boy Color, CPU CGB A"},
   1367         {"cgb-b", GB_MODEL_CGB_B, "Game Boy Color, CPU CGB B"},
   1368         {"cgb-c", GB_MODEL_CGB_C, "Game Boy Color, CPU CGB C"},
   1369         {"cgb-d", GB_MODEL_CGB_D, "Game Boy Color, CPU CGB D"},
   1370         {"cgb-e", GB_MODEL_CGB_E, "Game Boy Color, CPU CGB E"},
   1371         {"cgb", GB_MODEL_CGB_E, "Alias of cgb-e"},
   1372         {"agb-a", GB_MODEL_AGB_A, "Game Boy Advance, CPU AGB A"},
   1373         {"agb", GB_MODEL_AGB_A, "Alias of agb-a"},
   1374         {"gbp-a", GB_MODEL_GBP_A, "Game Boy Player, CPU AGB A"},
   1375         {"gbp", GB_MODEL_GBP_A, "Alias of gbp-a"},
   1376     };
   1377     
   1378     GB_model_t model = -1;
   1379     for (unsigned i = 0; i < sizeof(name_to_model) / sizeof(name_to_model[0]); i++) {
   1380         if (strcmp(model_string, name_to_model[i].name) == 00) {
   1381             model = name_to_model[i].model;
   1382             break;
   1383         }
   1384     }
   1385     if (model == -1) {
   1386         fprintf(stderr, "'%s' is not a valid model. Valid options are:\n", model_string);
   1387         for (unsigned i = 0; i < sizeof(name_to_model) / sizeof(name_to_model[0]); i++) {
   1388             fprintf(stderr, "%s - %s\n", name_to_model[i].name, name_to_model[i].description);
   1389         }
   1390         exit(1);
   1391     }
   1392     
   1393     switch (model) {
   1394         case GB_MODEL_DMG_B:
   1395             configuration.model = MODEL_DMG;
   1396             break;
   1397         case GB_MODEL_SGB_NTSC:
   1398             configuration.model = MODEL_SGB;
   1399             configuration.sgb_revision = SGB_NTSC;
   1400             break;
   1401         case GB_MODEL_SGB_PAL:
   1402             configuration.model = MODEL_SGB;
   1403             configuration.sgb_revision = SGB_PAL;
   1404             break;
   1405         case GB_MODEL_SGB2:
   1406             configuration.model = MODEL_SGB;
   1407             configuration.sgb_revision = SGB_2;
   1408             break;
   1409         case GB_MODEL_MGB:
   1410             configuration.model = MODEL_DMG;
   1411             break;
   1412         case GB_MODEL_CGB_0:
   1413         case GB_MODEL_CGB_A:
   1414         case GB_MODEL_CGB_B:
   1415         case GB_MODEL_CGB_C:
   1416         case GB_MODEL_CGB_D:
   1417         case GB_MODEL_CGB_E:
   1418             configuration.model = MODEL_CGB;
   1419             configuration.cgb_revision = model - GB_MODEL_CGB_0;
   1420             break;
   1421         case GB_MODEL_AGB_A:
   1422         case GB_MODEL_GBP_A:
   1423             configuration.model = MODEL_AGB;
   1424             configuration.agb_revision = model;
   1425             break;
   1426             
   1427         default:
   1428             configuration.model = MODEL_AUTO;
   1429             break;
   1430     }
   1431 }
   1432 
   1433 #ifdef _WIN32
   1434 /* raise is buggy and for some reason not always go through our signal handler, so
   1435    let's just place the implementation with a direct call to debugger_interrupt. */
   1436 int raise(int signal)
   1437 {
   1438     debugger_interrupt(signal);
   1439     return 0;
   1440 }
   1441 #endif
   1442 
   1443 int main(int argc, char **argv)
   1444 {
   1445 #ifdef _WIN32
   1446     SetProcessDPIAware();
   1447 #endif
   1448 #ifdef __APPLE__
   1449     enable_smooth_scrolling();
   1450 #endif
   1451 
   1452     const char *model_string = get_arg_option("--model", &argc, argv);
   1453     bool fullscreen = get_arg_flag("--fullscreen", &argc, argv) || get_arg_flag("-f", &argc, argv);
   1454     bool nogl = get_arg_flag("--nogl", &argc, argv);
   1455     stop_on_start = get_arg_flag("--stop-debugger", &argc, argv) || get_arg_flag("-s", &argc, argv);
   1456     
   1457 
   1458     if (argc > 2 || (argc == 2 && argv[1][0] == '-')) {
   1459         fprintf(stderr, "SameBoy v" GB_VERSION "\n");
   1460         fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [--model <model>] <rom>\n", argv[0]);
   1461         exit(1);
   1462     }
   1463     
   1464     if (argc == 2) {
   1465         filename = argv[1];
   1466     }
   1467 
   1468     signal(SIGINT, debugger_interrupt);
   1469 #ifndef _WIN32
   1470     signal(SIGUSR1, debugger_reset);
   1471 #endif
   1472 
   1473     if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO) < 0) {
   1474         fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
   1475     }
   1476     // This is, essentially, best-effort.
   1477     // This function will not be called if the process is terminated in any way, anyhow.
   1478     atexit(SDL_Quit);
   1479 
   1480     if ((console_supported = CON_start(completer))) {
   1481         CON_set_repeat_empty(true);
   1482         CON_printf("SameBoy v" GB_VERSION "\n");
   1483     }
   1484     else {
   1485         fprintf(stderr, "SameBoy v" GB_VERSION "\n");
   1486     }
   1487     
   1488     strcpy(prefs_path, resource_path("prefs.bin"));
   1489     if (access(prefs_path, R_OK | W_OK) != 0) {
   1490         char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
   1491         snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
   1492         SDL_free(prefs_dir);
   1493     }
   1494     
   1495     FILE *prefs_file = fopen(prefs_path, "rb");
   1496     if (prefs_file) {
   1497         fread(&configuration, 1, sizeof(configuration), prefs_file);
   1498         fclose(prefs_file);
   1499         
   1500         /* Sanitize for stability */
   1501         configuration.color_correction_mode %= GB_COLOR_CORRECTION_MODERN_ACCURATE + 1;
   1502         configuration.scaling_mode %= GB_SDL_SCALING_MAX;
   1503         configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1;
   1504         configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1;
   1505         configuration.highpass_mode %= GB_HIGHPASS_MAX;
   1506         configuration.sgb_revision %= SGB_MAX;
   1507         configuration.dmg_palette %= 5;
   1508         if (configuration.dmg_palette) {
   1509             configuration.gui_palette_enabled = true;
   1510         }
   1511         configuration.border_mode %= GB_BORDER_ALWAYS + 1;
   1512         configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1;
   1513         configuration.color_temperature %= 21;
   1514         configuration.bootrom_path[sizeof(configuration.bootrom_path) - 1] = 0;
   1515         configuration.cgb_revision %= GB_MODEL_CGB_E - GB_MODEL_CGB_0 + 1;
   1516         configuration.audio_driver[15] = 0;
   1517         configuration.dmg_palette_name[24] = 0;
   1518         // Fix broken defaults, keys 12-31 should be unmapped by default
   1519         if (configuration.joypad_configuration[31] == 0) {
   1520             memset(configuration.joypad_configuration + 12 , -1, 32 - 12);
   1521         }
   1522         if ((configuration.agb_revision & ~GB_MODEL_GBP_BIT) != GB_MODEL_AGB_A) {
   1523             configuration.agb_revision = GB_MODEL_AGB_A;
   1524         }
   1525         
   1526         if (configuration.rumble_strength > 8 || configuration.rumble_strength == 0) {
   1527             configuration.rumble_strength = 8;
   1528         }
   1529     }
   1530     
   1531     if (configuration.model >= MODEL_MAX) {
   1532         configuration.model = MODEL_CGB;
   1533     }
   1534 
   1535     if (configuration.default_scale == 0) {
   1536         configuration.default_scale = 2;
   1537     }
   1538     
   1539     if (model_string) {
   1540         handle_model_option(model_string);
   1541     }
   1542     
   1543     atexit(save_configuration);
   1544     atexit(stop_recording);
   1545     
   1546     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
   1547     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
   1548     SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
   1549     
   1550     SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
   1551                 configuration.allow_background_controllers? "1" : "0");
   1552 
   1553     window = SDL_CreateWindow("SameBoy v" GB_VERSION, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
   1554                               (configuration.border_mode == GB_BORDER_ALWAYS? 256 : 160) * configuration.default_scale,
   1555                               (configuration.border_mode == GB_BORDER_ALWAYS? 224 : 144) * configuration.default_scale,
   1556                               SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
   1557     if (window == NULL) {
   1558         fputs(SDL_GetError(), stderr);
   1559         exit(1);
   1560     }
   1561     SDL_SetWindowMinimumSize(window, 160, 144);
   1562     
   1563     if (fullscreen) {
   1564         SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
   1565     }
   1566 
   1567 #ifdef _WIN32
   1568     configure_window_corners();
   1569 #endif
   1570     
   1571     gl_context = nogl? NULL : SDL_GL_CreateContext(window);
   1572     
   1573     GLint major = 0, minor = 0;
   1574     if (gl_context) {
   1575         glGetIntegerv(GL_MAJOR_VERSION, &major);
   1576         glGetIntegerv(GL_MINOR_VERSION, &minor);
   1577         update_swap_interval();
   1578     }
   1579     
   1580     if (gl_context && major * 0x100 + minor < 0x302) {
   1581         SDL_GL_DeleteContext(gl_context);
   1582         gl_context = NULL;
   1583     }
   1584     
   1585     if (gl_context == NULL) {
   1586         renderer = SDL_CreateRenderer(window, -1, 0);
   1587         texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144);
   1588         pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window));
   1589     }
   1590     else {
   1591         pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
   1592     }
   1593     
   1594     GB_audio_init();
   1595 
   1596     SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
   1597     
   1598     if (!init_shader_with_name(&shader, configuration.filter)) {
   1599         init_shader_with_name(&shader, "NearestNeighbor");
   1600     }
   1601     update_viewport();
   1602     
   1603 #ifdef _WIN32
   1604     if (!configuration.windows_associations_prompted) {
   1605         configuration.windows_associations_prompted = true;
   1606         save_configuration();
   1607         SDL_MessageBoxButtonData buttons[2] = {
   1608             {
   1609                 .flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
   1610                 .buttonid = 0,
   1611                 .text = "No",
   1612             },
   1613             {
   1614                 .flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
   1615                 .buttonid = 1,
   1616                 .text = "Yes",
   1617             },
   1618         };
   1619         SDL_MessageBoxData box = {
   1620             .title = "Associate SameBoy with Game Boy ROMs",
   1621             .message = "Would you like to associate SameBoy with Game Boy ROMs?\nThis can be also done later in the Options menu.",
   1622             .numbuttons = 2,
   1623             .buttons = buttons,
   1624         };
   1625         int button;
   1626         SDL_ShowMessageBox(&box, &button);
   1627         if (button) {
   1628             GB_do_windows_association();
   1629         }
   1630     }
   1631 #endif
   1632     
   1633     SDL_GL_SetSwapInterval(configuration.vsync_mode);
   1634     
   1635     if (filename == NULL) {
   1636         stop_on_start = false;
   1637         run_gui(false);
   1638     }
   1639     else {
   1640         connect_joypad();
   1641     }
   1642     GB_audio_set_paused(false);
   1643     run(); // Never returns
   1644     return 0;
   1645 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.