retro-go/fmsx/main/main.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

485 lines
12 KiB
C

#include <rg_system.h>
#include <string.h>
#define AUDIO_SAMPLE_RATE (32000)
#define AUDIO_BUFFER_LENGTH (AUDIO_SAMPLE_RATE / 60 + 1)
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
static rg_task_t *audioQueue;
static rg_app_t *app;
static int JoyState, LastKey, InMenu, InKeyboard;
static int KeyboardCol, KeyboardRow, KeyboardKey;
static int64_t KeyboardDebounce = 0;
static int FrameStartTime;
static int KeyboardEmulation, CropPicture;
static char *PendingLoadSTA = NULL;
#define BPS16
#define BPP16
#define UNIX
#define GenericSetVideo SetVideo
#define LSB_FIRST
#define NARROW
#define WIDTH 256
#define HEIGHT 228
#define XKEYS 12
#define YKEYS 6
void PutImage(void);
static uint16_t BPal[256];
static uint16_t XPal[80];
static uint16_t XPal0;
static uint16_t *XBuf;
#include <fmsx.h>
static Image NormScreen;
const char *Title = "fMSX 6.0";
const char *Disks[2][MAXDISKS + 1];
static const unsigned char KBDKeys[YKEYS][XKEYS] = {
{0x1B, CON_F1, CON_F2, CON_F3, CON_F4, CON_F5, CON_F6, CON_F7, CON_F8, CON_INSERT, CON_DELETE, CON_STOP},
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='},
{CON_TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', CON_BS},
{'^', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', CON_ENTER},
{'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 0, 0},
{'[', ']', ' ', ' ', ' ', ' ', ' ', '\\', '\'', 0, 0, 0}};
static const char *BiosFolder = RG_BASE_PATH_BIOS "/msx";
// We only check for absolutely essential files to avoid slowing down boot too much!
static const char *BiosFiles[] = {
"MSX.ROM",
"MSX2.ROM",
"MSX2EXT.ROM",
// "MSX2P.ROM",
// "MSX2PEXT.ROM",
// "FMPAC.ROM",
"DISK.ROM",
"MSXDOS2.ROM",
// "PAINTER.ROM",
// "KANJI.ROM",
};
static inline void SubmitFrame(void)
{
int crop_v = CropPicture ? (ScanLines212 ? 8 : 18) : 0;
currentUpdate->offset = crop_v * currentUpdate->stride;
currentUpdate->height = HEIGHT - crop_v * 2;
rg_display_submit(currentUpdate, 0);
}
int ProcessEvents(int Wait)
{
for (int i = 0; i < 16; ++i)
KeyState[i] = 0xFF;
JoyState = 0;
uint32_t joystick = rg_input_read_gamepad();
if (joystick == RG_KEY_MENU)
{
rg_gui_game_menu();
return 0;
}
else if (joystick == RG_KEY_OPTION)
{
rg_gui_options_menu();
return 0;
}
else if (joystick == RG_KEY_SELECT)
{
InKeyboard = !InKeyboard;
rg_input_wait_for_key(RG_KEY_ANY, false, 500);
}
else if (joystick == RG_KEY_START)
{
// I think this key could be better used for something else
// but for now the feedback is to keep a key for fMSX menu...
InMenu = 2;
return 0;
}
if (InMenu == 2)
{
InMenu = 1;
rg_audio_set_mute(true);
MenuMSX();
rg_audio_set_mute(false);
rg_input_wait_for_key(RG_KEY_ANY, false, 500);
InMenu = 0;
}
else if (InMenu)
{
if (joystick == RG_KEY_LEFT)
LastKey = CON_LEFT;
if (joystick == RG_KEY_RIGHT)
LastKey = CON_RIGHT;
if (joystick == RG_KEY_UP)
LastKey = CON_UP;
if (joystick == RG_KEY_DOWN)
LastKey = CON_DOWN;
if (joystick == RG_KEY_A)
LastKey = CON_OK;
if (joystick == RG_KEY_B)
LastKey = CON_EXIT;
}
else if (InKeyboard)
{
if (joystick & (RG_KEY_LEFT | RG_KEY_RIGHT | RG_KEY_UP | RG_KEY_DOWN))
{
if (rg_system_timer() > KeyboardDebounce)
{
if (joystick == RG_KEY_LEFT)
KeyboardCol--;
if (joystick == RG_KEY_RIGHT)
KeyboardCol++;
if (joystick == RG_KEY_UP)
KeyboardRow--;
if (joystick == RG_KEY_DOWN)
KeyboardRow++;
KeyboardCol = RG_MIN(RG_MAX(KeyboardCol, 0), XKEYS - 1);
KeyboardRow = RG_MIN(RG_MAX(KeyboardRow, 0), YKEYS - 1);
PutImage();
KeyboardDebounce = rg_system_timer() + 250000;
}
}
else if (joystick == RG_KEY_A)
{
KeyboardKey = KBDKeys[KeyboardRow][KeyboardCol];
KBD_SET(KeyboardKey);
}
else if (joystick == RG_KEY_B)
{
rg_input_wait_for_key(RG_KEY_ANY, false, 500);
InKeyboard = false;
}
}
else if (KeyboardEmulation)
{
if (joystick & RG_KEY_LEFT)
KBD_SET(KBD_LEFT);
if (joystick & RG_KEY_RIGHT)
KBD_SET(KBD_RIGHT);
if (joystick & RG_KEY_UP)
KBD_SET(KBD_UP);
if (joystick & RG_KEY_DOWN)
KBD_SET(KBD_DOWN);
if (joystick & RG_KEY_A)
KBD_SET(KBD_SPACE);
if (joystick & RG_KEY_B)
KBD_SET(KBD_ENTER);
}
else
{
if (joystick & RG_KEY_LEFT)
JoyState |= JST_LEFT;
if (joystick & RG_KEY_RIGHT)
JoyState |= JST_RIGHT;
if (joystick & RG_KEY_UP)
JoyState |= JST_UP;
if (joystick & RG_KEY_DOWN)
JoyState |= JST_DOWN;
if (joystick & RG_KEY_A)
JoyState |= JST_FIREA;
if (joystick & RG_KEY_B)
JoyState |= JST_FIREB;
}
return 0;
}
int InitMachine(void)
{
NormScreen = (Image){
.Data = currentUpdate->data,
.W = WIDTH,
.H = HEIGHT,
.L = WIDTH,
.D = 16,
};
XBuf = NormScreen.Data;
SetScreenDepth(NormScreen.D);
SetVideo(&NormScreen, 0, 0, WIDTH, HEIGHT);
for (int J = 0; J < 80; J++)
SetColor(J, 0, 0, 0);
for (int J = 0; J < 256; J++)
{
uint16_t color = C_RGB(((J >> 2) & 0x07) * 255 / 7, ((J >> 5) & 0x07) * 255 / 7, (J & 0x03) * 255 / 3);
BPal[J] = ((color >> 8) | (color << 8)) & 0xFFFF;
}
InitSound(AUDIO_SAMPLE_RATE, 150);
SetChannels(64, 0xFFFFFFFF);
RPLInit(SaveState, LoadState, MAX_STASIZE);
RPLRecord(RPL_RESET);
return 1;
}
void TrashMachine(void)
{
RPLTrash();
TrashSound();
}
void SetColor(byte N, byte R, byte G, byte B)
{
uint16_t color = C_RGB(R, G, B);
color = (color >> 8) | (color << 8);
if (N)
XPal[N] = color;
else
XPal0 = color;
}
void PutImage(void)
{
if (InKeyboard)
DrawKeyboard(&NormScreen, KBDKeys[KeyboardRow][KeyboardCol]);
SubmitFrame();
currentUpdate = updates[currentUpdate == updates[0]];
NormScreen.Data = currentUpdate->data;
XBuf = NormScreen.Data;
}
unsigned int Joystick(void)
{
ProcessEvents(0);
return JoyState;
}
void Keyboard(void)
{
// Keyboard() is a convenient place to do our vsync stuff :)
rg_system_tick(rg_system_timer() - FrameStartTime);
FrameStartTime = rg_system_timer();
if (PendingLoadSTA)
{
LoadSTA(PendingLoadSTA);
free(PendingLoadSTA);
PendingLoadSTA = NULL;
}
}
unsigned int Mouse(byte N)
{
return 0;
}
int ShowVideo(void)
{
SubmitFrame();
rg_system_tick(0);
return 1;
}
unsigned int GetJoystick(void)
{
ProcessEvents(0);
return 0;
}
unsigned int GetMouse(void)
{
return 0;
}
unsigned int GetKey(void)
{
unsigned int J;
ProcessEvents(0);
J = LastKey;
LastKey = 0;
return J;
}
unsigned int WaitKey(void)
{
GetKey();
rg_input_wait_for_key(RG_KEY_ANY, false, 200);
while (!rg_input_wait_for_key(RG_KEY_ANY, true, 100))
continue;
return GetKey();
}
unsigned int WaitKeyOrMouse(void)
{
LastKey = WaitKey();
return 0;
}
unsigned int InitAudio(unsigned int Rate, unsigned int Latency)
{
return AUDIO_SAMPLE_RATE;
}
void TrashAudio(void)
{
//
}
unsigned int GetFreeAudio(void)
{
return 1024;
}
void PlayAllSound(int uSec)
{
int64_t start = rg_system_timer();
unsigned int samples = 2 * uSec * AUDIO_SAMPLE_RATE / 1000000;
rg_task_send(audioQueue, &(rg_task_msg_t){.dataInt = samples});
FrameStartTime += rg_system_timer() - start;
}
unsigned int WriteAudio(sample *Data, unsigned int Length)
{
rg_audio_submit((void *)Data, Length >> 1);
return Length;
}
static bool save_state_handler(const char *filename)
{
return SaveSTA(filename);
}
static bool load_state_handler(const char *filename)
{
PendingLoadSTA = strdup(filename);
return true;
}
static bool reset_handler(bool hard)
{
ResetMSX(Mode,RAMPages,VRAMPages);
return true;
}
static bool screenshot_handler(const char *filename, int width, int height)
{
return rg_surface_save_image_file(currentUpdate, filename, width, height);
}
static void event_handler(int event, void *arg)
{
if (event == RG_EVENT_REDRAW)
{
SubmitFrame();
}
}
static rg_gui_event_t crop_select_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
CropPicture = !CropPicture;
rg_settings_set_number(NS_APP, "Crop", CropPicture);
return RG_DIALOG_REDRAW;
}
strcpy(option->value, CropPicture ? _("On") : _("Off"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t input_select_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
KeyboardEmulation = !KeyboardEmulation;
rg_settings_set_number(NS_APP, "Input", KeyboardEmulation);
}
strcpy(option->value, KeyboardEmulation ? _("Keyboard") : _("Joystick"));
return RG_DIALOG_VOID;
}
static void audioTask(void *arg)
{
RG_LOGI("task started");
rg_task_msg_t msg;
while (rg_task_peek(&msg))
{
RenderAndPlayAudio(msg.dataInt);
rg_task_receive(&msg);
}
}
static void options_handler(rg_gui_option_t *dest)
{
*dest++ = (rg_gui_option_t){0, _("Input"), "-", RG_DIALOG_FLAG_NORMAL, &input_select_cb};
*dest++ = (rg_gui_option_t){0, _("Crop"), "-", RG_DIALOG_FLAG_NORMAL, &crop_select_cb};
*dest++ = (rg_gui_option_t)RG_DIALOG_END;
}
void app_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_init(AUDIO_SAMPLE_RATE, &handlers, NULL);
// This is probably not right, but the emulator outputs 440 samples per frame??
rg_system_set_tick_rate(55);
updates[0] = rg_surface_create(WIDTH, HEIGHT, RG_PIXEL_565_BE, MEM_FAST);
updates[1] = rg_surface_create(WIDTH, HEIGHT, RG_PIXEL_565_BE, MEM_FAST);
currentUpdate = updates[0];
KeyboardEmulation = rg_settings_get_number(NS_APP, "Input", 1);
CropPicture = rg_settings_get_number(NS_APP, "Crop", 0);
for (size_t i = 0; i < RG_COUNT(BiosFiles); ++i)
{
char pathbuf[RG_PATH_MAX + 1];
snprintf(pathbuf, RG_PATH_MAX, "%s/%s", BiosFolder, BiosFiles[i]);
if (!rg_storage_exists(pathbuf))
{
char message[512];
snprintf(message, 512, "File: %s\nYou can find it at:\n%s",
rg_relpath(pathbuf), "https://fms.komkon.org/fMSX/");
rg_gui_alert(_("BIOS file missing!"), message);
}
}
if (app->bootFlags & RG_BOOT_RESUME)
{
PendingLoadSTA = rg_emu_get_path(RG_PATH_SAVE_STATE + app->saveSlot, app->romPath);
}
const char *argv[] = {
"fmsx",
"-ram", "2",
"-vram", "2",
"-skip", "50",
"-home", BiosFolder,
"-joy", "1",
NULL, NULL, NULL,
};
int argc = RG_COUNT(argv) - 3;
if (rg_extension_match(app->romPath, "dsk"))
{
argv[argc++] = "-diska";
}
argv[argc++] = app->romPath;
audioQueue = rg_task_create("audioTask", &audioTask, NULL, 4096, RG_TASK_PRIORITY_2, 1);
RG_LOGI("fMSX start");
fmsx_main(argc, (char **)argv);
RG_LOGI("fMSX ended");
rg_system_exit();
}