retro-go/retro-core/main/main_lynx.cpp
Alex Duchesne a8565b952c Removed app.options entirely, now rely on the handler
The advantage is that the menu entries are generated on demand, so they'll always be up to date irt language.

I think it would be better if the options_handler was in charge of showing the dialog too.

It would allow in-app menus and it will also avoid the careless unbounded copying.

But this first step preserves the previous behavior.
2024-12-17 16:04:41 -05:00

293 lines
8.5 KiB
C++

extern "C" {
#include "shared.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
}
#include <handy.h>
static CSystem *lynx = NULL;
static int dpad_mapped_up;
static int dpad_mapped_down;
static int dpad_mapped_left;
static int dpad_mapped_right;
static rg_app_t *app;
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
// static bool netplay = false;
// --- MAIN
static void set_display_mode(void)
{
display_rotation_t rotation = rg_display_get_rotation();
int width, height;
if (rotation == RG_DISPLAY_ROTATION_AUTO)
{
rotation = RG_DISPLAY_ROTATION_OFF;
switch (lynx->mCart->CRC32())
{
case 0x97501709: // Centipede
case 0x0271B6E9: // Lexis
case 0x006FD398: // NFL Football
case 0xBCD10C3A: // Raiden
rotation = RG_DISPLAY_ROTATION_LEFT;
break;
case 0x7F0EC7AD: // Gauntlet
case 0xAC564BAA: // Gauntlet - The Third Encounter
case 0xA53649F1: // Klax
rotation = RG_DISPLAY_ROTATION_RIGHT;
break;
default:
if (lynx->mCart->CartGetRotate() == CART_ROTATE_LEFT)
rotation = RG_DISPLAY_ROTATION_LEFT;
if (lynx->mCart->CartGetRotate() == CART_ROTATE_RIGHT)
rotation = RG_DISPLAY_ROTATION_RIGHT;
}
}
switch(rotation)
{
case RG_DISPLAY_ROTATION_LEFT:
width = HANDY_SCREEN_HEIGHT;
height = HANDY_SCREEN_WIDTH;
lynx->mMikie->SetRotation(MIKIE_ROTATE_L);
dpad_mapped_up = BUTTON_RIGHT;
dpad_mapped_down = BUTTON_LEFT;
dpad_mapped_left = BUTTON_UP;
dpad_mapped_right = BUTTON_DOWN;
break;
case RG_DISPLAY_ROTATION_RIGHT:
width = HANDY_SCREEN_HEIGHT;
height = HANDY_SCREEN_WIDTH;
lynx->mMikie->SetRotation(MIKIE_ROTATE_R);
dpad_mapped_up = BUTTON_LEFT;
dpad_mapped_down = BUTTON_RIGHT;
dpad_mapped_left = BUTTON_DOWN;
dpad_mapped_right = BUTTON_UP;
break;
default:
width = HANDY_SCREEN_WIDTH;
height = HANDY_SCREEN_HEIGHT;
lynx->mMikie->SetRotation(MIKIE_NO_ROTATE);
dpad_mapped_up = BUTTON_UP;
dpad_mapped_down = BUTTON_DOWN;
dpad_mapped_left = BUTTON_LEFT;
dpad_mapped_right = BUTTON_RIGHT;
break;
}
updates[0]->width = width;
updates[0]->height = height;
updates[1]->width = width;
updates[1]->height = height;
}
static CSystem *new_lynx(void)
{
if (rg_extension_match(app->romPath, "zip"))
{
void *data;
size_t size;
if (!rg_storage_unzip_file(app->romPath, NULL, &data, &size, 0))
RG_PANIC("ROM file unzipping failed!");
CSystem *lynx = new CSystem((UBYTE*)data, size, MIKIE_PIXEL_FORMAT_16BPP_565_BE, app->sampleRate);
free(data);
return lynx;
}
return new CSystem(app->romPath, MIKIE_PIXEL_FORMAT_16BPP_565_BE, app->sampleRate);
}
static rg_gui_event_t rotation_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
int rotation = (int)rg_display_get_rotation();
if (event == RG_DIALOG_PREV) {
if (--rotation < 0) rotation = RG_DISPLAY_ROTATION_COUNT - 1;
rg_display_set_rotation((display_rotation_t)rotation);
set_display_mode();
return RG_DIALOG_REDRAW;
}
if (event == RG_DIALOG_NEXT) {
if (++rotation > RG_DISPLAY_ROTATION_COUNT - 1) rotation = 0;
rg_display_set_rotation((display_rotation_t)rotation);
set_display_mode();
return RG_DIALOG_REDRAW;
}
strcpy(option->value, _("Off"));
if (rotation == RG_DISPLAY_ROTATION_AUTO) strcpy(option->value, _("Auto"));
if (rotation == RG_DISPLAY_ROTATION_LEFT) strcpy(option->value, _("Left"));
if (rotation == RG_DISPLAY_ROTATION_RIGHT) strcpy(option->value, _("Right"));
return RG_DIALOG_VOID;
}
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)
{
bool ret = false;
FILE *fp;
if ((fp = fopen(filename, "wb")))
{
ret = lynx->ContextSave(fp);
fclose(fp);
}
return ret;
}
static bool load_state_handler(const char *filename)
{
bool ret = false;
FILE *fp;
if ((fp = fopen(filename, "rb")))
{
ret = lynx->ContextLoad(fp);
fclose(fp);
}
if (!ret) lynx->Reset();
return ret;
}
static bool reset_handler(bool hard)
{
// This isn't nice but lynx->Reset() crashes...
delete lynx;
lynx = new_lynx();
return true;
}
static void options_handler(rg_gui_option_t *dest)
{
*dest++ = (rg_gui_option_t){0, _("Rotation"), (char *)"-", RG_DIALOG_FLAG_NORMAL, &rotation_cb};
*dest++ = (rg_gui_option_t)RG_DIALOG_END;
}
extern "C" void lynx_main(void)
{
const rg_handlers_t handlers = {
.loadState = &load_state_handler,
.saveState = &save_state_handler,
.reset = &reset_handler,
.screenshot = &screenshot_handler,
.event = &event_handler,
.memRead = NULL,
.memWrite = NULL,
.options = NULL,
.about = NULL,
};
app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, NULL);
// the HANDY_SCREEN_WIDTH * HANDY_SCREEN_WIDTH is deliberate because of rotation
updates[0] = rg_surface_create(HANDY_SCREEN_WIDTH, HANDY_SCREEN_WIDTH, RG_PIXEL_565_BE, MEM_FAST);
updates[1] = rg_surface_create(HANDY_SCREEN_WIDTH, HANDY_SCREEN_WIDTH, RG_PIXEL_565_BE, MEM_FAST);
currentUpdate = updates[0];
// Init emulator
lynx = new_lynx();
if (lynx->mFileType == HANDY_FILETYPE_ILLEGAL)
{
RG_PANIC("ROM loading failed!");
}
gPrimaryFrameBuffer = (UBYTE*)currentUpdate->data;
gAudioBuffer = new SWORD[AUDIO_BUFFER_LENGTH * 2];
gAudioEnabled = 1;
if (app->bootFlags & RG_BOOT_RESUME)
{
rg_emu_load_state(app->saveSlot);
}
set_display_mode();
long skipFrames = 0;
bool slowFrame = false;
// Start emulation
while (1)
{
uint32_t joystick = rg_input_read_gamepad();
if (joystick & (RG_KEY_MENU|RG_KEY_OPTION))
{
if (joystick & RG_KEY_MENU)
rg_gui_game_menu();
else
rg_gui_options_menu();
}
int64_t startTime = rg_system_timer();
bool drawFrame = !skipFrames;
ULONG buttons = 0;
if (joystick & RG_KEY_UP) buttons |= dpad_mapped_up;
if (joystick & RG_KEY_DOWN) buttons |= dpad_mapped_down;
if (joystick & RG_KEY_LEFT) buttons |= dpad_mapped_left;
if (joystick & RG_KEY_RIGHT) buttons |= dpad_mapped_right;
if (joystick & RG_KEY_A) buttons |= BUTTON_A;
if (joystick & RG_KEY_B) buttons |= BUTTON_B;
if (joystick & RG_KEY_START) buttons |= BUTTON_OPT2; // BUTTON_PAUSE
if (joystick & RG_KEY_SELECT) buttons |= BUTTON_OPT1;
lynx->SetButtonData(buttons);
lynx->UpdateFrame(drawFrame);
if (drawFrame)
{
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
currentUpdate = updates[currentUpdate == updates[0]];
gPrimaryFrameBuffer = (UBYTE*)currentUpdate->data;
}
// The Lynx has a variable tick rate, I don't know of a better way to guess than from audio stream
rg_system_set_tick_rate(AUDIO_SAMPLE_RATE / (gAudioBufferPointer / 2));
rg_system_tick(rg_system_timer() - startTime);
rg_audio_submit((const rg_audio_frame_t *)gAudioBuffer, gAudioBufferPointer / 2);
// See if we need to skip a frame to keep up
if (skipFrames == 0)
{
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
// The Lynx uses a variable framerate so we use the count of generated audio samples as reference instead
else if (elapsed > app->frameTime + 1500)
skipFrames = 1; // (elapsed / frameTime)
else if (drawFrame && slowFrame)
skipFrames = 1;
}
else if (skipFrames > 0)
{
skipFrames--;
}
gAudioBufferPointer = 0;
}
}