cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

main.cpp (21833B)


      1/*
      2 * Gearboy - Nintendo Game Boy Emulator
      3 * Copyright (C) 2012  Ignacio Sanchez
      4
      5 * This program is free software: you can redistribute it and/or modify
      6 * it under the terms of the GNU General Public License as published by
      7 * the Free Software Foundation, either version 3 of the License, or
      8 * any later version.
      9
     10 * This program is distributed in the hope that it will be useful,
     11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13 * GNU General Public License for more details.
     14
     15 * You should have received a copy of the GNU General Public License
     16 * along with this program.  If not, see http://www.gnu.org/licenses/
     17 *
     18 */
     19
     20#include <stdio.h>
     21#include <stdlib.h>
     22#include <string.h>
     23#include <math.h>
     24#include <assert.h>
     25#include <unistd.h>
     26#include <iostream>
     27#include <iomanip>
     28#include <stdlib.h>
     29#include <sys/time.h>
     30#include <SDL2/SDL.h>
     31#include <libconfig.h++>
     32#include "bcm_host.h"
     33#include "GLES/gl.h"
     34#include "EGL/egl.h"
     35#include "EGL/eglext.h"
     36
     37#include "gearboy.h"
     38#include "../audio-shared/Sound_Queue.h"
     39
     40using namespace std;
     41using namespace libconfig;
     42
     43bool running = true;
     44bool paused = false;
     45
     46EGLDisplay display;
     47EGLSurface surface;
     48EGLContext context;
     49
     50static const char *output_file = "gearboy.cfg";
     51
     52const float kGB_Width = 160.0f;
     53const float kGB_Height = 144.0f;
     54const float kGB_TexWidth = kGB_Width / 256.0f;
     55const float kGB_TexHeight = kGB_Height / 256.0f;
     56const GLfloat kQuadTex[8] = { 0, 0, kGB_TexWidth, 0, kGB_TexWidth, kGB_TexHeight, 0, kGB_TexHeight};
     57GLshort quadVerts[8];
     58
     59GearboyCore* theGearboyCore;
     60Sound_Queue* theSoundQueue;
     61u16* theFrameBuffer;
     62GLuint theGBTexture;
     63s16 theSampleBufffer[AUDIO_BUFFER_SIZE];
     64
     65bool audioEnabled = true;
     66
     67struct palette_color
     68{
     69    int red;
     70    int green;
     71    int blue;
     72    int alpha;
     73};
     74
     75palette_color dmg_palette[4];
     76
     77SDL_Joystick* game_pad = NULL;
     78SDL_Keycode kc_keypad_left, kc_keypad_right, kc_keypad_up, kc_keypad_down, kc_keypad_a, kc_keypad_b, kc_keypad_start, kc_keypad_select, kc_emulator_pause, kc_emulator_quit;
     79bool jg_x_axis_invert, jg_y_axis_invert;
     80int jg_a, jg_b, jg_start, jg_select, jg_x_axis, jg_y_axis;
     81
     82uint32_t screen_width, screen_height;
     83
     84SDL_Window* theWindow;
     85
     86void update(void)
     87{
     88    SDL_Event keyevent;
     89
     90    while (SDL_PollEvent(&keyevent))
     91    {
     92        switch(keyevent.type)
     93        {
     94            case SDL_QUIT:
     95            running = false;
     96            break;
     97
     98            case SDL_JOYBUTTONDOWN:
     99            {
    100                if (keyevent.jbutton.button == jg_b)
    101                    theGearboyCore->KeyPressed(B_Key);
    102                else if (keyevent.jbutton.button == jg_a)
    103                    theGearboyCore->KeyPressed(A_Key);
    104                else if (keyevent.jbutton.button == jg_select)
    105                    theGearboyCore->KeyPressed(Select_Key);
    106                else if (keyevent.jbutton.button == jg_start)
    107                    theGearboyCore->KeyPressed(Start_Key);
    108            }
    109            break;
    110
    111            case SDL_JOYBUTTONUP:
    112            {
    113                if (keyevent.jbutton.button == jg_b)
    114                    theGearboyCore->KeyReleased(B_Key);
    115                else if (keyevent.jbutton.button == jg_a)
    116                    theGearboyCore->KeyReleased(A_Key);
    117                else if (keyevent.jbutton.button == jg_select)
    118                    theGearboyCore->KeyReleased(Select_Key);
    119                else if (keyevent.jbutton.button == jg_start)
    120                    theGearboyCore->KeyReleased(Start_Key);
    121            }
    122            break;
    123
    124            case SDL_JOYAXISMOTION:
    125            {
    126                if(keyevent.jaxis.axis == jg_x_axis)
    127                {
    128                    int x_motion = keyevent.jaxis.value * (jg_x_axis_invert ? -1 : 1);
    129                    if (x_motion < 0)
    130                        theGearboyCore->KeyPressed(Left_Key);
    131                    else if (x_motion > 0)
    132                        theGearboyCore->KeyPressed(Right_Key);
    133                    else
    134                    {
    135                        theGearboyCore->KeyReleased(Left_Key);
    136                        theGearboyCore->KeyReleased(Right_Key);
    137                    }
    138                }
    139                else if(keyevent.jaxis.axis == jg_y_axis)
    140                {
    141                    int y_motion = keyevent.jaxis.value * (jg_y_axis_invert ? -1 : 1);
    142                    if (y_motion < 0)
    143                        theGearboyCore->KeyPressed(Up_Key);
    144                    else if (y_motion > 0)
    145                        theGearboyCore->KeyPressed(Down_Key);
    146                    else
    147                    {
    148                        theGearboyCore->KeyReleased(Up_Key);
    149                        theGearboyCore->KeyReleased(Down_Key);
    150                    }
    151                }
    152            }
    153            break;
    154
    155            case SDL_KEYDOWN:
    156            {
    157                if (keyevent.key.keysym.sym == kc_keypad_left)
    158                    theGearboyCore->KeyPressed(Left_Key);
    159                else if (keyevent.key.keysym.sym == kc_keypad_right)
    160                    theGearboyCore->KeyPressed(Right_Key);
    161                else if (keyevent.key.keysym.sym == kc_keypad_up)
    162                    theGearboyCore->KeyPressed(Up_Key);
    163                else if (keyevent.key.keysym.sym == kc_keypad_down)
    164                    theGearboyCore->KeyPressed(Down_Key);
    165                else if (keyevent.key.keysym.sym == kc_keypad_b)
    166                    theGearboyCore->KeyPressed(B_Key);
    167                else if (keyevent.key.keysym.sym == kc_keypad_a)
    168                    theGearboyCore->KeyPressed(A_Key);
    169                else if (keyevent.key.keysym.sym == kc_keypad_select)
    170                    theGearboyCore->KeyPressed(Select_Key);
    171                else if (keyevent.key.keysym.sym == kc_keypad_start)
    172                    theGearboyCore->KeyPressed(Start_Key);
    173
    174                if (keyevent.key.keysym.sym == kc_emulator_quit)
    175                    running = false;
    176                else if (keyevent.key.keysym.sym == kc_emulator_pause)
    177                {
    178                    paused = !paused;
    179                    theGearboyCore->Pause(paused);
    180                }
    181            }
    182            break;
    183
    184            case SDL_KEYUP:
    185            {
    186                if (keyevent.key.keysym.sym == kc_keypad_left)
    187                    theGearboyCore->KeyReleased(Left_Key);
    188                else if (keyevent.key.keysym.sym == kc_keypad_right)
    189                    theGearboyCore->KeyReleased(Right_Key);
    190                else if (keyevent.key.keysym.sym == kc_keypad_up)
    191                    theGearboyCore->KeyReleased(Up_Key);
    192                else if (keyevent.key.keysym.sym == kc_keypad_down)
    193                    theGearboyCore->KeyReleased(Down_Key);
    194                else if (keyevent.key.keysym.sym == kc_keypad_b)
    195                    theGearboyCore->KeyReleased(B_Key);
    196                else if (keyevent.key.keysym.sym == kc_keypad_a)
    197                    theGearboyCore->KeyReleased(A_Key);
    198                else if (keyevent.key.keysym.sym == kc_keypad_select)
    199                    theGearboyCore->KeyReleased(Select_Key);
    200                else if (keyevent.key.keysym.sym == kc_keypad_start)
    201                    theGearboyCore->KeyReleased(Start_Key);
    202            }
    203            break;
    204        }
    205    }
    206
    207    int sampleCount = 0;
    208
    209    theGearboyCore->RunToVBlank(theFrameBuffer, theSampleBufffer, &sampleCount);
    210
    211    if (audioEnabled && (sampleCount > 0))
    212    {
    213        theSoundQueue->write(theSampleBufffer, sampleCount);
    214    }
    215
    216    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 160, 144, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (GLvoid*) theFrameBuffer);
    217    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    218    eglSwapBuffers(display, surface);
    219}
    220
    221void init_sdl(void)
    222{
    223    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0)
    224    {
    225        Log("SDL Error Init: %s", SDL_GetError());
    226    }
    227
    228    theWindow = SDL_CreateWindow(GEARBOY_TITLE, 0, 0, 0, 0, 0);
    229
    230    if (theWindow == NULL)
    231    {
    232        Log("SDL Error Video: %s", SDL_GetError());
    233    }
    234
    235    SDL_ShowCursor(SDL_DISABLE);
    236
    237    game_pad = SDL_JoystickOpen(0);
    238
    239    if(game_pad == NULL)
    240    {
    241        Log("Warning: Unable to open game controller! SDL Error: %s\n", SDL_GetError());
    242    }
    243
    244    kc_keypad_left = SDLK_LEFT;
    245    kc_keypad_right = SDLK_RIGHT;
    246    kc_keypad_up = SDLK_UP;
    247    kc_keypad_down = SDLK_DOWN;
    248    kc_keypad_a = SDLK_a;
    249    kc_keypad_b = SDLK_s;
    250    kc_keypad_start = SDLK_RETURN;
    251    kc_keypad_select = SDLK_SPACE;
    252    kc_emulator_pause = SDLK_p;
    253    kc_emulator_quit = SDLK_ESCAPE;
    254
    255    jg_x_axis_invert = false;
    256    jg_y_axis_invert = false;
    257    jg_a = 1;
    258    jg_b = 2;
    259    jg_start = 9;
    260    jg_select = 8;
    261    jg_x_axis = 0;
    262    jg_y_axis = 1;
    263
    264    dmg_palette[0].red = 0xEF;
    265    dmg_palette[0].green = 0xF3;
    266    dmg_palette[0].blue = 0xD5;
    267    dmg_palette[0].alpha = 0xFF;
    268
    269    dmg_palette[1].red = 0xA3;
    270    dmg_palette[1].green = 0xB6;
    271    dmg_palette[1].blue = 0x7A;
    272    dmg_palette[1].alpha = 0xFF;
    273
    274    dmg_palette[2].red = 0x37;
    275    dmg_palette[2].green = 0x61;
    276    dmg_palette[2].blue = 0x3B;
    277    dmg_palette[2].alpha = 0xFF;
    278
    279    dmg_palette[3].red = 0x04;
    280    dmg_palette[3].green = 0x1C;
    281    dmg_palette[3].blue = 0x16;
    282    dmg_palette[3].alpha = 0xFF;
    283
    284    Config cfg;
    285
    286    try
    287    {
    288        cfg.readFile(output_file);
    289
    290        try
    291        {
    292            const Setting& root = cfg.getRoot();
    293            const Setting &gearboy = root[GEARBOY_TITLE];
    294
    295            string keypad_left, keypad_right, keypad_up, keypad_down, keypad_a, keypad_b,
    296            keypad_start, keypad_select, emulator_pause, emulator_quit;
    297            gearboy.lookupValue("keypad_left", keypad_left);
    298            gearboy.lookupValue("keypad_right", keypad_right);
    299            gearboy.lookupValue("keypad_up", keypad_up);
    300            gearboy.lookupValue("keypad_down", keypad_down);
    301            gearboy.lookupValue("keypad_a", keypad_a);
    302            gearboy.lookupValue("keypad_b", keypad_b);
    303            gearboy.lookupValue("keypad_start", keypad_start);
    304            gearboy.lookupValue("keypad_select", keypad_select);
    305
    306            gearboy.lookupValue("joystick_gamepad_a", jg_a);
    307            gearboy.lookupValue("joystick_gamepad_b", jg_b);
    308            gearboy.lookupValue("joystick_gamepad_start", jg_start);
    309            gearboy.lookupValue("joystick_gamepad_select", jg_select);
    310            gearboy.lookupValue("joystick_gamepad_x_axis", jg_x_axis);
    311            gearboy.lookupValue("joystick_gamepad_y_axis", jg_y_axis);
    312            gearboy.lookupValue("joystick_gamepad_x_axis_invert", jg_x_axis_invert);
    313            gearboy.lookupValue("joystick_gamepad_y_axis_invert", jg_y_axis_invert);
    314
    315            gearboy.lookupValue("emulator_pause", emulator_pause);
    316            gearboy.lookupValue("emulator_quit", emulator_quit);
    317
    318            gearboy.lookupValue("emulator_pal0_red", dmg_palette[0].red);
    319            gearboy.lookupValue("emulator_pal0_green", dmg_palette[0].green);
    320            gearboy.lookupValue("emulator_pal0_blue", dmg_palette[0].blue);
    321            gearboy.lookupValue("emulator_pal1_red", dmg_palette[1].red);
    322            gearboy.lookupValue("emulator_pal1_green", dmg_palette[1].green);
    323            gearboy.lookupValue("emulator_pal1_blue", dmg_palette[1].blue);
    324            gearboy.lookupValue("emulator_pal2_red", dmg_palette[2].red);
    325            gearboy.lookupValue("emulator_pal2_green", dmg_palette[2].green);
    326            gearboy.lookupValue("emulator_pal2_blue", dmg_palette[2].blue);
    327            gearboy.lookupValue("emulator_pal3_red", dmg_palette[3].red);
    328            gearboy.lookupValue("emulator_pal3_green", dmg_palette[3].green);
    329            gearboy.lookupValue("emulator_pal3_blue", dmg_palette[3].blue);
    330
    331            kc_keypad_left = SDL_GetKeyFromName(keypad_left.c_str());
    332            kc_keypad_right = SDL_GetKeyFromName(keypad_right.c_str());
    333            kc_keypad_up = SDL_GetKeyFromName(keypad_up.c_str());
    334            kc_keypad_down = SDL_GetKeyFromName(keypad_down.c_str());
    335            kc_keypad_a = SDL_GetKeyFromName(keypad_a.c_str());
    336            kc_keypad_b = SDL_GetKeyFromName(keypad_b.c_str());
    337            kc_keypad_start = SDL_GetKeyFromName(keypad_start.c_str());
    338            kc_keypad_select = SDL_GetKeyFromName(keypad_select.c_str());
    339
    340            kc_emulator_pause = SDL_GetKeyFromName(emulator_pause.c_str());
    341            kc_emulator_quit = SDL_GetKeyFromName(emulator_quit.c_str());
    342        }
    343        catch(const SettingNotFoundException &nfex)
    344        {
    345            std::cerr << "Setting not found" << std::endl;
    346        }
    347    }
    348    catch(const FileIOException &fioex)
    349    {
    350        Log("I/O error while reading file: %s", output_file);
    351    }
    352    catch(const ParseException &pex)
    353    {
    354        std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine()
    355              << " - " << pex.getError() << std::endl;
    356    }
    357}
    358
    359void init_ogl(void)
    360{
    361    int32_t success = 0;
    362    EGLBoolean result;
    363    EGLint num_config;
    364
    365    static EGL_DISPMANX_WINDOW_T nativewindow;
    366
    367    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    368    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    369    DISPMANX_UPDATE_HANDLE_T dispman_update;
    370    VC_DISPMANX_ALPHA_T alpha;
    371    VC_RECT_T dst_rect;
    372    VC_RECT_T src_rect;
    373
    374    static const EGLint attribute_list[] =
    375    {
    376        EGL_RED_SIZE, 8,
    377        EGL_GREEN_SIZE, 8,
    378        EGL_BLUE_SIZE, 8,
    379        EGL_ALPHA_SIZE, 8,
    380        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    381        EGL_NONE
    382    };
    383
    384    EGLConfig config;
    385
    386    // Get an EGL display connection
    387    display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    388    assert(display!=EGL_NO_DISPLAY);
    389
    390    // Initialize the EGL display connection
    391    result = eglInitialize(display, NULL, NULL);
    392    assert(EGL_FALSE != result);
    393
    394    // Get an appropriate EGL frame buffer configuration
    395    result = eglChooseConfig(display, attribute_list, &config, 1, &num_config);
    396    assert(EGL_FALSE != result);
    397
    398    // Create an EGL rendering context
    399    context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);
    400    assert(context!=EGL_NO_CONTEXT);
    401
    402    // Create an EGL window surface
    403    success = graphics_get_display_size(0 /* LCD */, &screen_width, &screen_height);
    404    assert( success >= 0 );
    405
    406    int32_t zoom = screen_width / GAMEBOY_WIDTH;
    407    int32_t zoom2 = screen_height / GAMEBOY_HEIGHT;
    408
    409    if (zoom2 < zoom)
    410        zoom = zoom2;
    411
    412    int32_t display_width = GAMEBOY_WIDTH * zoom;
    413    int32_t display_height = GAMEBOY_HEIGHT * zoom;
    414    int32_t display_offset_x = (screen_width / 2) - (display_width / 2);
    415    int32_t display_offset_y = (screen_height / 2) - (display_height / 2);
    416
    417    dst_rect.x = 0;
    418    dst_rect.y = 0;
    419    dst_rect.width = screen_width;
    420    dst_rect.height = screen_height;
    421
    422    src_rect.x = 0;
    423    src_rect.y = 0;
    424    src_rect.width = screen_width << 16;
    425    src_rect.height = screen_height << 16;
    426
    427    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    428    dispman_update = vc_dispmanx_update_start( 0 );
    429
    430    alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS;
    431    alpha.opacity = 255;
    432    alpha.mask = 0;
    433
    434    dispman_element = vc_dispmanx_element_add ( dispman_update, dispman_display,
    435        0/*layer*/, &dst_rect, 0/*src*/,
    436        &src_rect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE/*transform*/);
    437
    438    nativewindow.element = dispman_element;
    439    nativewindow.width = screen_width;
    440    nativewindow.height = screen_height;
    441    vc_dispmanx_update_submit_sync( dispman_update );
    442
    443    surface = eglCreateWindowSurface( display, config, &nativewindow, NULL );
    444    assert(surface != EGL_NO_SURFACE);
    445
    446    // Connect the context to the surface
    447    result = eglMakeCurrent(display, surface, surface, context);
    448    assert(EGL_FALSE != result);
    449
    450    eglSwapInterval(display, 1);
    451
    452    glGenTextures(1, &theGBTexture);
    453
    454    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    455
    456    glEnable(GL_TEXTURE_2D);
    457    glBindTexture(GL_TEXTURE_2D, theGBTexture);
    458    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (GLvoid*) NULL);
    459    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    460    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    461
    462    glMatrixMode(GL_PROJECTION);
    463    glLoadIdentity();
    464    glOrthof(0.0f, screen_width, screen_height, 0.0f, -1.0f, 1.0f);
    465    glMatrixMode(GL_MODELVIEW);
    466    glLoadIdentity();
    467    glViewport(0.0f, 0.0f, screen_width, screen_height);
    468
    469    quadVerts[0] = display_offset_x;
    470    quadVerts[1] = display_offset_y;
    471    quadVerts[2] = display_offset_x + display_width;
    472    quadVerts[3] = display_offset_y;
    473    quadVerts[4] = display_offset_x + display_width;
    474    quadVerts[5] = display_offset_y + display_height;
    475    quadVerts[6] = display_offset_x;
    476    quadVerts[7] = display_offset_y + display_height;
    477
    478    glVertexPointer(2, GL_SHORT, 0, quadVerts);
    479    glEnableClientState(GL_VERTEX_ARRAY);
    480
    481    glTexCoordPointer(2, GL_FLOAT, 0, kQuadTex);
    482    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    483
    484    glClear(GL_COLOR_BUFFER_BIT);
    485}
    486
    487void init(void)
    488{
    489
    490    bcm_host_init();
    491    init_ogl();
    492    init_sdl();
    493
    494    theGearboyCore = new GearboyCore();
    495    theGearboyCore->Init();
    496
    497    theSoundQueue = new Sound_Queue();
    498    theSoundQueue->start(44100, 2);
    499
    500    theFrameBuffer = new u16[GAMEBOY_WIDTH * GAMEBOY_HEIGHT];
    501
    502    for (int i = 0; i < (GAMEBOY_WIDTH * GAMEBOY_HEIGHT); ++i)
    503    {
    504        theFrameBuffer[i] = 0;
    505    }
    506}
    507
    508void end(void)
    509{
    510    Config cfg;
    511
    512    Setting &root = cfg.getRoot();
    513    Setting &address = root.add(GEARBOY_TITLE, Setting::TypeGroup);
    514
    515    address.add("keypad_left", Setting::TypeString) = SDL_GetKeyName(kc_keypad_left);
    516    address.add("keypad_right", Setting::TypeString) = SDL_GetKeyName(kc_keypad_right);
    517    address.add("keypad_up", Setting::TypeString) = SDL_GetKeyName(kc_keypad_up);
    518    address.add("keypad_down", Setting::TypeString) = SDL_GetKeyName(kc_keypad_down);
    519    address.add("keypad_a", Setting::TypeString) = SDL_GetKeyName(kc_keypad_a);
    520    address.add("keypad_b", Setting::TypeString) = SDL_GetKeyName(kc_keypad_b);
    521    address.add("keypad_start", Setting::TypeString) = SDL_GetKeyName(kc_keypad_start);
    522    address.add("keypad_select", Setting::TypeString) = SDL_GetKeyName(kc_keypad_select);
    523
    524    address.add("joystick_gamepad_a", Setting::TypeInt) = jg_a;
    525    address.add("joystick_gamepad_b", Setting::TypeInt) = jg_b;
    526    address.add("joystick_gamepad_start", Setting::TypeInt) = jg_start;
    527    address.add("joystick_gamepad_select", Setting::TypeInt) = jg_select;
    528    address.add("joystick_gamepad_x_axis", Setting::TypeInt) = jg_x_axis;
    529    address.add("joystick_gamepad_y_axis", Setting::TypeInt) = jg_y_axis;
    530    address.add("joystick_gamepad_x_axis_invert", Setting::TypeBoolean) = jg_x_axis_invert;
    531    address.add("joystick_gamepad_y_axis_invert", Setting::TypeBoolean) = jg_y_axis_invert;
    532
    533    address.add("emulator_pause", Setting::TypeString) = SDL_GetKeyName(kc_emulator_pause);
    534    address.add("emulator_quit", Setting::TypeString) = SDL_GetKeyName(kc_emulator_quit);
    535
    536    address.add("emulator_pal0_red", Setting::TypeInt) = dmg_palette[0].red;
    537    address.add("emulator_pal0_green", Setting::TypeInt) = dmg_palette[0].green;
    538    address.add("emulator_pal0_blue", Setting::TypeInt) = dmg_palette[0].blue;
    539    address.add("emulator_pal1_red", Setting::TypeInt) = dmg_palette[1].red;
    540    address.add("emulator_pal1_green", Setting::TypeInt) = dmg_palette[1].green;
    541    address.add("emulator_pal1_blue", Setting::TypeInt) = dmg_palette[1].blue;
    542    address.add("emulator_pal2_red", Setting::TypeInt) = dmg_palette[2].red;
    543    address.add("emulator_pal2_green", Setting::TypeInt) = dmg_palette[2].green;
    544    address.add("emulator_pal2_blue", Setting::TypeInt) = dmg_palette[2].blue;
    545    address.add("emulator_pal3_red", Setting::TypeInt) = dmg_palette[3].red;
    546    address.add("emulator_pal3_green", Setting::TypeInt) = dmg_palette[3].green;
    547    address.add("emulator_pal3_blue", Setting::TypeInt) = dmg_palette[3].blue;
    548
    549    try
    550    {
    551        cfg.writeFile(output_file);
    552    }
    553    catch(const FileIOException &fioex)
    554    {
    555        Log("I/O error while writing file: %s", output_file);
    556    }
    557
    558    SDL_JoystickClose(game_pad);
    559
    560    SafeDeleteArray(theFrameBuffer);
    561    SafeDelete(theSoundQueue);
    562    SafeDelete(theGearboyCore);
    563    SDL_DestroyWindow(theWindow);
    564    SDL_Quit();
    565    bcm_host_deinit();
    566}
    567
    568int main(int argc, char** argv)
    569{
    570    init();
    571
    572    if (argc < 2 || argc > 4)
    573    {
    574        end();
    575        printf("usage: %s rom_path [options]\n", argv[0]);
    576        printf("options:\n-nosound\n-forcedmg\n");
    577        return -1;
    578    }
    579
    580    bool forcedmg = false;
    581
    582    if (argc > 2)
    583    {
    584        for (int i = 2; i < argc; i++)
    585        {
    586            if (strcmp("-nosound", argv[i]) == 0)
    587                audioEnabled = false;
    588            else if (strcmp("-forcedmg", argv[i]) == 0)
    589                forcedmg = true;
    590            else
    591            {
    592                end();
    593                printf("invalid option: %s\n", argv[i]);
    594                return -1;
    595            }
    596        }
    597    }
    598
    599    if (theGearboyCore->LoadROM(argv[1], forcedmg))
    600    {
    601        GB_Color color1;
    602        GB_Color color2;
    603        GB_Color color3;
    604        GB_Color color4;
    605
    606        color1.red = dmg_palette[0].red;
    607        color1.green = dmg_palette[0].green;
    608        color1.blue = dmg_palette[0].blue;
    609        color2.red = dmg_palette[1].red;
    610        color2.green = dmg_palette[1].green;
    611        color2.blue = dmg_palette[1].blue;
    612        color3.red = dmg_palette[2].red;
    613        color3.green = dmg_palette[2].green;
    614        color3.blue = dmg_palette[2].blue;
    615        color4.red = dmg_palette[3].red;
    616        color4.green = dmg_palette[3].green;
    617        color4.blue = dmg_palette[3].blue;
    618
    619        theGearboyCore->SetDMGPalette(color1, color2, color3, color4);
    620        theGearboyCore->LoadRam();
    621
    622        while (running)
    623        {
    624            update();
    625        }
    626
    627        theGearboyCore->SaveRam();
    628    }
    629
    630    end();
    631
    632    return 0;
    633}