retro-go/retro-core/main/main_snes.c
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

445 lines
13 KiB
C

#include "shared.h"
#include <snes9x.h>
#include <math.h>
typedef struct
{
char name[16];
struct {
uint16_t snes9x_mask;
uint16_t local_mask;
uint16_t mod_mask;
} keys[16];
} keymap_t;
static const keymap_t KEYMAPS[] = {
{"Type A", {
{SNES_A_MASK, RG_KEY_A, 0},
{SNES_B_MASK, RG_KEY_B, 0},
{SNES_X_MASK, RG_KEY_START, 0},
{SNES_Y_MASK, RG_KEY_SELECT, 0},
{SNES_TL_MASK, RG_KEY_B, RG_KEY_MENU},
{SNES_TR_MASK, RG_KEY_A, RG_KEY_MENU},
{SNES_START_MASK, RG_KEY_START, RG_KEY_MENU},
{SNES_SELECT_MASK, RG_KEY_SELECT, RG_KEY_MENU},
{SNES_UP_MASK, RG_KEY_UP, 0},
{SNES_DOWN_MASK, RG_KEY_DOWN, 0},
{SNES_LEFT_MASK, RG_KEY_LEFT, 0},
{SNES_RIGHT_MASK, RG_KEY_RIGHT, 0},
}},
{"Type B", {
{SNES_A_MASK, RG_KEY_START, 0},
{SNES_B_MASK, RG_KEY_A, 0},
{SNES_X_MASK, RG_KEY_SELECT, 0},
{SNES_Y_MASK, RG_KEY_B, 0},
{SNES_TL_MASK, RG_KEY_B, RG_KEY_MENU},
{SNES_TR_MASK, RG_KEY_A, RG_KEY_MENU},
{SNES_START_MASK, RG_KEY_START, RG_KEY_MENU},
{SNES_SELECT_MASK, RG_KEY_SELECT, RG_KEY_MENU},
{SNES_UP_MASK, RG_KEY_UP, 0},
{SNES_DOWN_MASK, RG_KEY_DOWN, 0},
{SNES_LEFT_MASK, RG_KEY_LEFT, 0},
{SNES_RIGHT_MASK, RG_KEY_RIGHT, 0},
}},
{"Type C", {
{SNES_A_MASK, RG_KEY_A, 0},
{SNES_B_MASK, RG_KEY_B, 0},
{SNES_X_MASK, 0, 0},
{SNES_Y_MASK, 0, 0},
{SNES_TL_MASK, 0, 0},
{SNES_TR_MASK, 0, 0},
{SNES_START_MASK, RG_KEY_START, 0},
{SNES_SELECT_MASK, RG_KEY_SELECT, 0},
{SNES_UP_MASK, RG_KEY_UP, 0},
{SNES_DOWN_MASK, RG_KEY_DOWN, 0},
{SNES_LEFT_MASK, RG_KEY_LEFT, 0},
{SNES_RIGHT_MASK, RG_KEY_RIGHT, 0},
}},
};
static const size_t KEYMAPS_COUNT = (sizeof(KEYMAPS) / sizeof(keymap_t));
static const char *SNES_BUTTONS[] = {
"None", "None", "None", "None", "R", "L", "X", "A", "Right", "Left", "Down", "Up", "Start", "Select", "Y", "B"
};
#define AUDIO_LOW_PASS_RANGE ((60 * 65536) / 100)
static rg_app_t *app;
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
static rg_audio_sample_t *audioBuffer;
static bool apu_enabled = true;
static bool lowpass_filter = false;
static int keymap_id = 0;
static keymap_t keymap;
static const char *SETTING_KEYMAP = "keymap";
static const char *SETTING_APU_EMULATION = "apu";
// --- MAIN
static void update_keymap(int id)
{
keymap_id = id % KEYMAPS_COUNT;
keymap = KEYMAPS[keymap_id];
}
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 S9xSaveState(filename);
}
static bool load_state_handler(const char *filename)
{
return S9xLoadState(filename);
}
static bool reset_handler(bool hard)
{
S9xReset();
return true;
}
static void event_handler(int event, void *arg)
{
if (event == RG_EVENT_REDRAW)
{
rg_display_submit(currentUpdate, 0);
}
}
static rg_gui_event_t apu_toggle_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
apu_enabled = !apu_enabled;
rg_settings_set_number(NS_APP, SETTING_APU_EMULATION, apu_enabled);
}
strcpy(option->value, apu_enabled ? _("On") : _("Off"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t lowpass_filter_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
lowpass_filter = !lowpass_filter;
strcpy(option->value, lowpass_filter ? _("On") : _("Off"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t change_keymap_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
if (event == RG_DIALOG_PREV && --keymap_id < 0)
keymap_id = KEYMAPS_COUNT - 1;
if (event == RG_DIALOG_NEXT && ++keymap_id > KEYMAPS_COUNT - 1)
keymap_id = 0;
update_keymap(keymap_id);
rg_settings_set_number(NS_APP, SETTING_KEYMAP, keymap_id);
return RG_DIALOG_REDRAW;
}
if (event == RG_DIALOG_ENTER)
{
return RG_DIALOG_CANCEL;
}
if (option->arg == -1)
{
strcat(strcat(strcpy(option->value, "< "), keymap.name), " >");
}
else if (option->arg >= 0)
{
int local_button = keymap.keys[option->arg].local_mask;
int mod_button = keymap.keys[option->arg].mod_mask;
int snes9x_button = log2(keymap.keys[option->arg].snes9x_mask); // convert bitmask to bit number
if (snes9x_button < 4 || (local_button & (RG_KEY_UP|RG_KEY_DOWN|RG_KEY_LEFT|RG_KEY_RIGHT)))
{
option->flags = RG_DIALOG_FLAG_HIDDEN;
return RG_DIALOG_VOID;
}
if (keymap.keys[option->arg].mod_mask)
sprintf(option->value, "%s + %s", rg_input_get_key_name(mod_button), rg_input_get_key_name(local_button));
else
sprintf(option->value, "%s", rg_input_get_key_name(local_button));
option->label = SNES_BUTTONS[snes9x_button];
option->flags = RG_DIALOG_FLAG_NORMAL;
}
return RG_DIALOG_VOID;
}
static rg_gui_event_t menu_keymap_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_ENTER)
{
const rg_gui_option_t options[] = {
{-1, _("Profile"), "-", RG_DIALOG_FLAG_NORMAL, &change_keymap_cb},
{-2, "", NULL, RG_DIALOG_FLAG_MESSAGE, NULL},
{-3, "snes9x ", "handheld", RG_DIALOG_FLAG_MESSAGE, NULL},
{0, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{1, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{2, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{3, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{4, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{5, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{6, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{7, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{8, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{9, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{10, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{11, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{12, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{13, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{14, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
{15, "-", "-", RG_DIALOG_FLAG_HIDDEN, &change_keymap_cb},
RG_DIALOG_END,
};
rg_gui_dialog(option->label, options, 0);
return RG_DIALOG_REDRAW;
}
strcpy(option->value, keymap.name);
return RG_DIALOG_VOID;
}
bool S9xInitDisplay(void)
{
GFX.Pitch = SNES_WIDTH * 2;
GFX.ZPitch = SNES_WIDTH;
GFX.Screen = currentUpdate->data;
GFX.SubScreen = malloc(GFX.Pitch * SNES_HEIGHT_EXTENDED);
GFX.ZBuffer = malloc(GFX.ZPitch * SNES_HEIGHT_EXTENDED);
GFX.SubZBuffer = malloc(GFX.ZPitch * SNES_HEIGHT_EXTENDED);
return GFX.Screen && GFX.SubScreen && GFX.ZBuffer && GFX.SubZBuffer;
}
void S9xDeinitDisplay(void)
{
}
uint32_t S9xReadJoypad(int32_t port)
{
if (port != 0)
return 0;
uint32_t joystick = rg_input_read_gamepad();
uint32_t joypad = 0;
for (int i = 0; i < RG_COUNT(keymap.keys); ++i)
{
uint32_t bitmask = keymap.keys[i].local_mask | keymap.keys[i].mod_mask;
if (bitmask && bitmask == (joystick & bitmask))
{
joypad |= keymap.keys[i].snes9x_mask;
}
}
return joypad;
}
bool S9xReadMousePosition(int32_t which1, int32_t *x, int32_t *y, uint32_t *buttons)
{
return false;
}
bool S9xReadSuperScopePosition(int32_t *x, int32_t *y, uint32_t *buttons)
{
return false;
}
bool JustifierOffscreen(void)
{
return true;
}
void JustifierButtons(uint32_t *justifiers)
{
(void)justifiers;
}
#ifdef USE_BLARGG_APU
static void S9xAudioCallback(void)
{
S9xFinalizeSamples();
size_t available_samples = S9xGetSampleCount();
S9xMixSamples((void *)audioBuffer, available_samples);
rg_audio_submit(audioBuffer, available_samples >> 1);
}
#endif
static void options_handler(rg_gui_option_t *dest)
{
*dest++ = (rg_gui_option_t){0, _("Audio enable"), "-", RG_DIALOG_FLAG_NORMAL, &apu_toggle_cb};
*dest++ = (rg_gui_option_t){0, _("Audio filter"), "-", RG_DIALOG_FLAG_NORMAL, &lowpass_filter_cb};
*dest++ = (rg_gui_option_t){0, _("Controls"), "-", RG_DIALOG_FLAG_NORMAL, &menu_keymap_cb};
*dest++ = (rg_gui_option_t)RG_DIALOG_END;
}
void snes_main(void)
{
const rg_handlers_t handlers = {
.loadState = &load_state_handler,
.saveState = &save_state_handler,
.reset = &reset_handler,
.screenshot = &screenshot_handler,
.event = &event_handler,
.options = &options_handler,
};
app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, NULL);
apu_enabled = rg_settings_get_number(NS_APP, SETTING_APU_EMULATION, 1);
updates[0] = rg_surface_create(SNES_WIDTH, SNES_HEIGHT_EXTENDED, RG_PIXEL_565_LE, 0);
updates[0]->height = SNES_HEIGHT;
currentUpdate = updates[0];
audioBuffer = (rg_audio_sample_t *)malloc(AUDIO_BUFFER_LENGTH * 4);
update_keymap(rg_settings_get_number(NS_APP, SETTING_KEYMAP, 0));
Settings.CyclesPercentage = 100;
Settings.H_Max = SNES_CYCLES_PER_SCANLINE;
Settings.FrameTimePAL = 20000;
Settings.FrameTimeNTSC = 16667;
Settings.ControllerOption = SNES_JOYPAD;
Settings.HBlankStart = (256 * Settings.H_Max) / SNES_HCOUNTER_MAX;
Settings.SoundPlaybackRate = AUDIO_SAMPLE_RATE;
Settings.DisableSoundEcho = false;
Settings.InterpolatedSound = true;
#ifdef USE_BLARGG_APU
Settings.SoundInputRate = AUDIO_SAMPLE_RATE;
#endif
if (!S9xInitDisplay())
RG_PANIC("Display init failed!");
if (!S9xInitMemory())
RG_PANIC("Memory init failed!");
if (!S9xInitAPU())
RG_PANIC("APU init failed!");
if (!S9xInitSound(0, 0))
RG_PANIC("Sound init failed!");
if (!S9xInitGFX())
RG_PANIC("Graphics init failed!");
const char *filename = app->romPath;
if (rg_extension_match(filename, "zip"))
{
if (!rg_storage_unzip_file(filename, NULL, (void **)&Memory.ROM, &Memory.ROM_AllocSize, RG_FILE_USER_BUFFER))
RG_PANIC("ROM file unzipping failed!");
filename = NULL;
}
if (!LoadROM(filename))
RG_PANIC("ROM loading failed!");
#ifdef USE_BLARGG_APU
S9xSetSamplesAvailableCallback(S9xAudioCallback);
#else
S9xSetPlaybackRate(Settings.SoundPlaybackRate);
#endif
if (app->bootFlags & RG_BOOT_RESUME)
{
rg_emu_load_state(app->saveSlot);
}
rg_system_set_tick_rate(Memory.ROMFramesPerSecond);
app->frameskip = 3;
bool menuCancelled = false;
bool menuPressed = false;
int skipFrames = 0;
while (1)
{
uint32_t joystick = rg_input_read_gamepad();
if (menuPressed && !(joystick & RG_KEY_MENU))
{
if (!menuCancelled)
{
rg_task_delay(50);
rg_gui_game_menu();
}
menuCancelled = false;
}
else if (joystick & RG_KEY_OPTION)
{
rg_gui_options_menu();
}
menuPressed = joystick & RG_KEY_MENU;
if (menuPressed && joystick & ~RG_KEY_MENU)
{
menuCancelled = true;
}
int64_t startTime = rg_system_timer();
bool drawFrame = (skipFrames == 0);
bool slowFrame = false;
IPPU.RenderThisFrame = drawFrame;
GFX.Screen = currentUpdate->data;
S9xMainLoop();
if (drawFrame)
{
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
}
#ifndef USE_BLARGG_APU
if (apu_enabled && lowpass_filter)
S9xMixSamplesLowPass((void *)audioBuffer, AUDIO_BUFFER_LENGTH << 1, AUDIO_LOW_PASS_RANGE);
else if (apu_enabled)
S9xMixSamples((void *)audioBuffer, AUDIO_BUFFER_LENGTH << 1);
#endif
rg_system_tick(rg_system_timer() - startTime);
#ifndef USE_BLARGG_APU
if (apu_enabled)
rg_audio_submit(audioBuffer, AUDIO_BUFFER_LENGTH);
#endif
if (skipFrames == 0)
{
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > app->frameTime + 1500) // Allow some jitter
skipFrames = 1; // (elapsed / frameTime)
else if (drawFrame && slowFrame)
skipFrames = 1;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
}