retro-go/retro-core/main/main_gbc.c

362 lines
10 KiB
C

#include "shared.h"
#include <sys/time.h>
#include <gnuboy.h>
static int skipFrames = 20; // The 20 is to hide startup flicker in some games
static bool slowFrame = false;
static int video_time;
static int audio_time;
static const char *sramFile;
static int autoSaveSRAM = 0;
static int autoSaveSRAM_Timer = 0;
static int useSystemTime = true;
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
static const char *SETTING_SAVESRAM = "SaveSRAM";
static const char *SETTING_PALETTE = "Palette";
static const char *SETTING_SYSTIME = "SysTime";
// --- MAIN
static void update_rtc_time(void)
{
if (!useSystemTime)
return;
time_t timer = time(NULL);
struct tm *info = localtime(&timer);
gnuboy_set_time(info->tm_yday, info->tm_hour, info->tm_min, info->tm_sec);
}
static void event_handler(int event, void *arg)
{
if (event == RG_EVENT_REDRAW)
{
rg_display_submit(currentUpdate, 0);
}
}
static bool screenshot_handler(const char *filename, int width, int height)
{
return rg_surface_save_image_file(currentUpdate, filename, width, height);
}
static bool save_state_handler(const char *filename)
{
return gnuboy_save_state(filename) == 0;
}
static bool load_state_handler(const char *filename)
{
if (gnuboy_load_state(filename) != 0)
{
// If a state fails to load then we should behave as we do on boot
// which is a hard reset and load sram if present
gnuboy_reset(true);
gnuboy_load_sram(sramFile);
update_rtc_time();
return false;
}
update_rtc_time();
skipFrames = 0;
autoSaveSRAM_Timer = 0;
// TO DO: Call rtc_sync() if a physical RTC is present
return true;
}
static bool reset_handler(bool hard)
{
gnuboy_reset(hard);
update_rtc_time();
skipFrames = 20;
autoSaveSRAM_Timer = 0;
return true;
}
static rg_gui_event_t palette_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (gnuboy_get_hwtype() == GB_HW_CGB)
{
strcpy(option->value, "GBC");
return RG_DIALOG_VOID;
}
int pal = gnuboy_get_palette();
int max = GB_PALETTE_COUNT - 1;
if (event == RG_DIALOG_PREV)
pal = pal > 0 ? pal - 1 : max;
if (event == RG_DIALOG_NEXT)
pal = pal < max ? pal + 1 : 0;
if (pal != gnuboy_get_palette())
{
rg_settings_set_number(NS_APP, SETTING_PALETTE, pal);
gnuboy_set_palette(pal);
gnuboy_run(true);
return RG_DIALOG_REDRAW;
}
if (pal == GB_PALETTE_DMG)
strcpy(option->value, "DMG ");
else if (pal == GB_PALETTE_MGB0)
strcpy(option->value, "Pocket");
else if (pal == GB_PALETTE_MGB1)
strcpy(option->value, "Light ");
else if (pal == GB_PALETTE_CGB)
strcpy(option->value, "GBC ");
else if (pal == GB_PALETTE_SGB)
strcpy(option->value, "SGB ");
else
sprintf(option->value, "%d/%d ", pal + 1, max - 1);
return RG_DIALOG_VOID;
}
static rg_gui_event_t sram_autosave_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV) autoSaveSRAM--;
if (event == RG_DIALOG_NEXT) autoSaveSRAM++;
autoSaveSRAM = RG_MIN(RG_MAX(0, autoSaveSRAM), 999);
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
rg_settings_set_number(NS_APP, SETTING_SAVESRAM, autoSaveSRAM);
}
if (autoSaveSRAM == 0) strcpy(option->value, "Off ");
else sprintf(option->value, "%3ds", autoSaveSRAM);
return RG_DIALOG_VOID;
}
static rg_gui_event_t rtc_t_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
int d, h, m, s;
gnuboy_get_time(&d, &h, &m, &s);
if (option->arg == 'd') {
if (event == RG_DIALOG_PREV && --d < 0) d = 364;
if (event == RG_DIALOG_NEXT && ++d > 364) d = 0;
sprintf(option->value, "%02d", d);
}
if (option->arg == 'h') {
if (event == RG_DIALOG_PREV && --h < 0) h = 23;
if (event == RG_DIALOG_NEXT && ++h > 23) h = 0;
sprintf(option->value, "%02d", h);
}
if (option->arg == 'm') {
if (event == RG_DIALOG_PREV && --m < 0) m = 59;
if (event == RG_DIALOG_NEXT && ++m > 59) m = 0;
sprintf(option->value, "%02d", m);
}
if (option->arg == 's') {
if (event == RG_DIALOG_PREV && --s < 0) s = 59;
if (event == RG_DIALOG_NEXT && ++s > 59) s = 0;
sprintf(option->value, "%02d", s);
}
if (option->arg == 'x') {
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) {
useSystemTime = !useSystemTime;
rg_settings_set_number(NS_APP, SETTING_SYSTIME, useSystemTime);
}
strcpy(option->value, useSystemTime ? "Yes" : "No ");
}
gnuboy_set_time(d, h, m, s);
// TO DO: Update system clock
return RG_DIALOG_VOID;
}
static rg_gui_event_t rtc_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_ENTER) {
const rg_gui_option_t choices[] = {
{'d', "Day ", "-", RG_DIALOG_FLAG_NORMAL, &rtc_t_update_cb},
{'h', "Hour", "-", RG_DIALOG_FLAG_NORMAL, &rtc_t_update_cb},
{'m', "Min ", "-", RG_DIALOG_FLAG_NORMAL, &rtc_t_update_cb},
{'s', "Sec ", "-", RG_DIALOG_FLAG_NORMAL, &rtc_t_update_cb},
{'x', "Sync", "-", RG_DIALOG_FLAG_NORMAL, &rtc_t_update_cb},
RG_DIALOG_END
};
rg_gui_dialog("RTC config", choices, 0);
}
int h, m;
gnuboy_get_time(NULL, &h, &m, NULL);
sprintf(option->value, "%02d:%02d", h, m);
return RG_DIALOG_VOID;
}
static void video_callback(void *buffer)
{
int64_t startTime = rg_system_timer();
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
video_time += rg_system_timer() - startTime;
}
static void audio_callback(void *buffer, size_t length)
{
int64_t startTime = rg_system_timer();
rg_audio_submit(buffer, length >> 1);
audio_time += rg_system_timer() - startTime;
}
void gbc_main(void)
{
const rg_handlers_t handlers = {
.loadState = &load_state_handler,
.saveState = &save_state_handler,
.reset = &reset_handler,
.screenshot = &screenshot_handler,
.event = &event_handler,
};
const rg_gui_option_t options[] = {
{0, "Palette ", "-", RG_DIALOG_FLAG_NORMAL, &palette_update_cb},
{0, "RTC config ", "-", RG_DIALOG_FLAG_NORMAL, &rtc_update_cb},
{0, "SRAM autosave", "-", RG_DIALOG_FLAG_NORMAL, &sram_autosave_cb},
RG_DIALOG_END
};
app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, options);
updates[0] = rg_surface_create(GB_WIDTH, GB_HEIGHT, RG_PIXEL_565_BE, MEM_ANY);
updates[1] = rg_surface_create(GB_WIDTH, GB_HEIGHT, RG_PIXEL_565_BE, MEM_ANY);
currentUpdate = updates[0];
useSystemTime = (int)rg_settings_get_number(NS_APP, SETTING_SYSTIME, 1);
autoSaveSRAM = (int)rg_settings_get_number(NS_APP, SETTING_SAVESRAM, 0);
sramFile = rg_emu_get_path(RG_PATH_SAVE_SRAM, app->romPath);
if (!rg_storage_mkdir(rg_dirname(sramFile)))
RG_LOGE("Unable to create SRAM folder...");
// Initialize the emulator
if (gnuboy_init(app->sampleRate, GB_AUDIO_STEREO_S16, GB_PIXEL_565_BE, &video_callback, &audio_callback) < 0)
RG_PANIC("Emulator init failed!");
gnuboy_set_framebuffer(currentUpdate->data);
gnuboy_set_soundbuffer((void *)audioBuffer, sizeof(audioBuffer) / 2);
// Load ROM
if (gnuboy_load_rom(app->romPath) < 0)
RG_PANIC("ROM Loading failed!");
// Load BIOS
if (gnuboy_get_hwtype() == GB_HW_CGB)
gnuboy_load_bios(RG_BASE_PATH_BIOS "/gbc_bios.bin");
else
gnuboy_load_bios(RG_BASE_PATH_BIOS "/gb_bios.bin");
gnuboy_set_palette(rg_settings_get_number(NS_APP, SETTING_PALETTE, GB_PALETTE_DMG));
// Hard reset to have a clean slate
gnuboy_reset(true);
// Load saved state or SRAM
if (app->bootFlags & RG_BOOT_RESUME)
rg_emu_load_state(app->saveSlot);
else
gnuboy_load_sram(sramFile);
update_rtc_time();
// Ready!
uint32_t joystick_old = -1;
uint32_t joystick = 0;
while (true)
{
joystick = rg_input_read_gamepad();
if (joystick & (RG_KEY_MENU|RG_KEY_OPTION))
{
if (joystick & RG_KEY_MENU)
{
if (gnuboy_sram_dirty()) // save in case the user quits
gnuboy_save_sram(sramFile, false);
rg_gui_game_menu();
}
else
rg_gui_options_menu();
}
else if (joystick != joystick_old)
{
int pad = 0;
if (joystick & RG_KEY_UP) pad |= GB_PAD_UP;
if (joystick & RG_KEY_RIGHT) pad |= GB_PAD_RIGHT;
if (joystick & RG_KEY_DOWN) pad |= GB_PAD_DOWN;
if (joystick & RG_KEY_LEFT) pad |= GB_PAD_LEFT;
if (joystick & RG_KEY_SELECT) pad |= GB_PAD_SELECT;
if (joystick & RG_KEY_START) pad |= GB_PAD_START;
if (joystick & RG_KEY_A) pad |= GB_PAD_A;
if (joystick & RG_KEY_B) pad |= GB_PAD_B;
gnuboy_set_pad(pad); // That call is somewhat costly, that's why we try to avoid it
joystick_old = joystick;
}
int64_t startTime = rg_system_timer();
bool drawFrame = !skipFrames;
video_time = audio_time = 0;
if (drawFrame)
{
currentUpdate = updates[currentUpdate == updates[0]];
gnuboy_set_framebuffer(currentUpdate->data);
}
gnuboy_run(drawFrame);
if (autoSaveSRAM > 0)
{
if (autoSaveSRAM_Timer <= 0)
{
if (gnuboy_sram_dirty())
{
autoSaveSRAM_Timer = autoSaveSRAM * 60;
}
}
else if (--autoSaveSRAM_Timer == 0)
{
gnuboy_save_sram(sramFile, true);
}
}
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime - audio_time);
if (skipFrames == 0)
{
int frameTime = 1000000 / (app->tickRate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = 1; // (elapsed / frameTime)
else if (drawFrame && slowFrame)
skipFrames = 1;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
}