kolibrios-fun/programs/emulator/pokemini/PokeMini_SDL.c

528 lines
15 KiB
C
Raw Normal View History

/*
PokeMini - Pok<EFBFBD>mon-Mini Emulator
Copyright (C) 2009-2015 JustBurn
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "SDL.h"
#include "PokeMini.h"
#include "Hardware.h"
#include "ExportBMP.h"
#include "ExportWAV.h"
#include "Keyboard.h"
#include "KeybMapSDL.h"
#include "Video_x1.h"
#include "Video_x2.h"
#include "Video_x3.h"
#include "Video_x4.h"
#include "Video_x5.h"
#include "Video_x6.h"
#include "PokeMini_BG2.h"
#include "PokeMini_BG3.h"
#include "PokeMini_BG4.h"
#include "PokeMini_BG5.h"
#include "PokeMini_BG6.h"
const char *AppName = "PokeMini " PokeMini_Version " SDL";
int emurunning = 1, emulimiter = 1;
SDL_Surface *screen;
int PMWidth, PMHeight;
int PixPitch, PMOff, UIOff;
FILE *sdump;
void setup_screen();
// Sound buffer size
#define SOUNDBUFFER 512
#define PMSNDBUFFER (SOUNDBUFFER*2)
const char *clc_zoom_txt[] = {
"0x (Illegal)",
"1x ( 96x 64)",
"2x (192x128)",
"3x (288x192)",
"4x (384x256)",
"5x (480x320)",
"6x (576x384)",
};
// Custom command line (NEW IN 0.5.0)
int clc_zoom = 6, clc_bpp = 16, clc_fullscreen = 0;
char clc_dump_sound[PMTMPV] = {0};
int clc_displayfps = 0;
const TCommandLineCustom CustomArgs[] = {
{ "-dumpsound", (int *)&clc_dump_sound, COMMANDLINE_STR, PMTMPV-1 },
{ "-zoom", &clc_zoom, COMMANDLINE_INT, 1, 6 },
{ "-bpp", &clc_bpp, COMMANDLINE_INT, 16, 32 },
{ "-windowed", &clc_fullscreen, COMMANDLINE_INTSET, 0 },
{ "-fullscreen", &clc_fullscreen, COMMANDLINE_INTSET, 1 },
{ "-displayfps", &clc_displayfps, COMMANDLINE_INTSET, 1 },
{ "", NULL, COMMANDLINE_EOL }
};
const TCommandLineCustom CustomConf[] = {
{ "zoom", &clc_zoom, COMMANDLINE_INT, 1, 6 },
{ "bpp", &clc_bpp, COMMANDLINE_INT, 16, 32 },
{ "fullscreen", &clc_fullscreen, COMMANDLINE_BOOL },
{ "displayfps", &clc_displayfps, COMMANDLINE_BOOL },
{ "", NULL, COMMANDLINE_EOL }
};
// Platform menu (REQUIRED >= 0.4.4)
int UIItems_PlatformC(int index, int reason);
TUIMenu_Item UIItems_Platform[] = {
PLATFORMDEF_GOBACK,
{ 0, 1, "Zoom: %s", UIItems_PlatformC },
{ 0, 2, "Depth: %dbpp", UIItems_PlatformC },
/*{ 0, 3, "Fullscreen: %s", UIItems_PlatformC },*/
{ 0, 4, "Display FPS: %s", UIItems_PlatformC },
{ 0, 9, "Define Keyboard...", UIItems_PlatformC },
PLATFORMDEF_SAVEOPTIONS,
PLATFORMDEF_END(UIItems_PlatformC)
};
int UIItems_PlatformC(int index, int reason)
{
int zoomchanged = 0;
if (reason == UIMENU_OK) {
reason = UIMENU_RIGHT;
}
if (reason == UIMENU_CANCEL) {
UIMenu_PrevMenu();
}
if (reason == UIMENU_LEFT) {
switch (index) {
case 1: // Zoom
clc_zoom--;
if (clc_zoom < 1) clc_zoom = 6;
zoomchanged = 1;
break;
case 2: // Bits-Per-Pixel
if (clc_bpp == 32)
clc_bpp = 16;
else
clc_bpp = 32;
zoomchanged = 1;
break;
case 3: // Fullscreen
clc_fullscreen = !clc_fullscreen;
zoomchanged = 1;
break;
case 4: // Display FPS
clc_displayfps = !clc_displayfps;
break;
}
}
if (reason == UIMENU_RIGHT) {
switch (index) {
case 1: // Zoom
clc_zoom++;
if (clc_zoom > 6) clc_zoom = 1;
zoomchanged = 1;
break;
case 2: // Bits-Per-Pixel
if (clc_bpp == 16)
clc_bpp = 32;
else
clc_bpp = 16;
zoomchanged = 1;
break;
case 3: // Fullscreen
clc_fullscreen = !clc_fullscreen;
zoomchanged = 1;
break;
case 4: // Display FPS
clc_displayfps = !clc_displayfps;
break;
break;
case 9: // Define Keyboard...
KeyboardEnterMenu();
break;
}
}
UIMenu_ChangeItem(UIItems_Platform, 1, "Zoom: %s", clc_zoom_txt[clc_zoom]);
UIMenu_ChangeItem(UIItems_Platform, 2, "Depth: %dbpp", clc_bpp);
UIMenu_ChangeItem(UIItems_Platform, 3, "Fullscreen: %s", clc_fullscreen ? "Yes" : "No");
UIMenu_ChangeItem(UIItems_Platform, 4, "Display FPS: %s", clc_displayfps ? "Yes" : "No");
if (zoomchanged) {
SDL_UnlockSurface(screen);
setup_screen();
SDL_LockSurface(screen);
return 0;
}
return 1;
}
// Setup screen
void setup_screen()
{
TPokeMini_VideoSpec *videospec;
int depth, PMOffX, PMOffY, UIOffX, UIOffY;
// Calculate size based of zoom
if (clc_zoom == 1) {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video1x1;
PMWidth = 192; PMHeight = 128; PMOffX = 48; PMOffY = 32; UIOffX = 0; UIOffY = 0;
UIMenu_SetDisplay(192, 128, PokeMini_BGR16, (uint8_t *)PokeMini_BG2, (uint16_t *)PokeMini_BG2_PalBGR16, (uint32_t *)PokeMini_BG2_PalBGR32);
} else if (clc_zoom == 2) {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video2x2;
PMWidth = 208; PMHeight = 144; PMOffX = 8; PMOffY = 8; UIOffX = 8; UIOffY = 8;
UIMenu_SetDisplay(192, 128, PokeMini_BGR16, (uint8_t *)PokeMini_BG2, (uint16_t *)PokeMini_BG2_PalBGR16, (uint32_t *)PokeMini_BG2_PalBGR32);
} else if (clc_zoom == 3) {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video3x3;
PMWidth = 304; PMHeight = 208; PMOffX = 8; PMOffY = 8; UIOffX = 8; UIOffY = 8;
UIMenu_SetDisplay(288, 192, PokeMini_BGR16, (uint8_t *)PokeMini_BG3, (uint16_t *)PokeMini_BG3_PalBGR16, (uint32_t *)PokeMini_BG3_PalBGR32);
} else if (clc_zoom == 4) {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video4x4;
PMWidth = 400; PMHeight = 272; PMOffX = 8; PMOffY = 8; UIOffX = 8; UIOffY = 8;
UIMenu_SetDisplay(384, 256, PokeMini_BGR16, (uint8_t *)PokeMini_BG4, (uint16_t *)PokeMini_BG4_PalBGR16, (uint32_t *)PokeMini_BG4_PalBGR32);
} else if (clc_zoom == 5) {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video5x5;
PMWidth = 496; PMHeight = 336; PMOffX = 8; PMOffY = 8; UIOffX = 8; UIOffY = 8;
UIMenu_SetDisplay(480, 320, PokeMini_BGR16, (uint8_t *)PokeMini_BG5, (uint16_t *)PokeMini_BG5_PalBGR16, (uint32_t *)PokeMini_BG5_PalBGR32);
} else {
videospec = (TPokeMini_VideoSpec *)&PokeMini_Video6x6;
PMWidth = 592; PMHeight = 400; PMOffX = 8; PMOffY = 8; UIOffX = 8; UIOffY = 8;
UIMenu_SetDisplay(576, 384, PokeMini_BGR16, (uint8_t *)PokeMini_BG6, (uint16_t *)PokeMini_BG6_PalBGR16, (uint32_t *)PokeMini_BG6_PalBGR32);
}
// Set video spec and check if is supported
depth = PokeMini_SetVideo(videospec, clc_bpp, CommandLine.lcdfilter, CommandLine.lcdmode);
if (!depth) {
fprintf(stderr, "Couldn't set video spec from %i bpp\n", clc_bpp);
exit(1);
}
// Set video mode
screen = SDL_SetVideoMode(PMWidth, PMHeight, depth, SDL_HWSURFACE | SDL_DOUBLEBUF | (clc_fullscreen ? SDL_FULLSCREEN : 0));
if (screen == NULL) {
fprintf(stderr, "Couldn't set video mode: %s\n", SDL_GetError());
exit(1);
}
// Calculate pitch and offset
if (depth == 32) {
PixPitch = screen->pitch / 4;
PMOff = (PMOffY * screen->pitch) + (PMOffX * 4);
UIOff = (UIOffY * screen->pitch) + (UIOffX * 4);
} else {
PixPitch = screen->pitch / 2;
PMOff = (PMOffY * screen->pitch) + (PMOffX * 2);
UIOff = (UIOffY * screen->pitch) + (UIOffX * 2);
}
clc_bpp = depth;
}
// Capture screen
void capture_screen()
{
FILE *capf;
int y, capnum;
unsigned long Video[96*64];
PokeMini_VideoPreview_32((uint32_t *)Video, 96, PokeMini_LCDMode);
capf = OpenUnique_ExportBMP(&capnum, 96, 64);
if (!capf) {
fprintf(stderr, "Error while saving capture\n");
return;
}
for (y=0; y<64; y++) {
WriteArray_ExportBMP(capf, (uint32_t *)&Video[(63-y) * 96], 96);
}
printf("Capture saved at 'snap_%03d.bmp'\n", capnum);
Close_ExportBMP(capf);
}
// Handle keyboard and quit events
void handleevents(SDL_Event *event)
{
switch (event->type) {
case SDL_KEYDOWN:
if (event->key.keysym.sym == SDLK_F9) { // Capture screen
capture_screen();
} else if (event->key.keysym.sym == SDLK_F4) { // Emulator Exit
if (event->key.keysym.mod & KMOD_ALT) {
emurunning = 0;
}
} else if (event->key.keysym.sym == SDLK_F10) { // Fullscreen/Window
clc_fullscreen = !clc_fullscreen;
setup_screen();
UIItems_PlatformC(0, UIMENU_LOAD);
} else if (event->key.keysym.sym == SDLK_F11) { // Disable speed throttling
emulimiter = !emulimiter;
} else if (event->key.keysym.sym == SDLK_TAB) { // Temp disable speed throttling
emulimiter = 0;
} else {
KeyboardPressEvent(event->key.keysym.sym);
}
break;
case SDL_KEYUP:
if (event->key.keysym.sym == SDLK_TAB) { // Speed threhold
emulimiter = 1;
} else {
KeyboardReleaseEvent(event->key.keysym.sym);
}
break;
case SDL_QUIT:
emurunning = 0;
break;
};
}
// Used to fill the sound buffer
void emulatorsound(void *unused, Uint8 *stream, int len)
{
MinxAudio_GetSamplesU8(stream, len);
if (clc_dump_sound[0]) WriteU8A_ExportWAV(sdump, stream, len>>1);
}
// Enable / Disable sound
void enablesound(int sound)
{
MinxAudio_ChangeEngine(sound);
if (AudioEnabled) SDL_PauseAudio(!sound);
}
// Menu loop
void menuloop()
{
SDL_Event event;
// Update window's title and stop sound
SDL_WM_SetCaption(AppName, "PMEWindow");
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
enablesound(0);
// Update EEPROM
PokeMini_SaveFromCommandLines(0);
// Menu's loop
while (emurunning && (UI_Status == UI_STATUS_MENU)) {
// Slowdown to approx. 60fps
SDL_Delay(1);
// Process UI
UIMenu_Process();
// Screen rendering
SDL_FillRect(screen, NULL, 0);
if (SDL_LockSurface(screen) == 0) {
// Render the menu or the game screen
if (PokeMini_VideoDepth == 32)
UIMenu_Display_32((uint32_t *)((uint8_t *)screen->pixels + UIOff), PixPitch);
else
UIMenu_Display_16((uint16_t *)((uint8_t *)screen->pixels + UIOff), PixPitch);
// Unlock surface
SDL_UnlockSurface(screen);
SDL_Flip(screen);
}
// Handle events
while (SDL_PollEvent(&event)) handleevents(&event);
}
// Apply configs
PokeMini_ApplyChanges();
if (UI_Status == UI_STATUS_EXIT) emurunning = 0;
else
{
if (CommandLine.sound == MINX_AUDIO_GENERATED)
enablesound(MINX_AUDIO_GENERATED);
else
enablesound(0);
}
SDL_EnableKeyRepeat(0, 0);
}
// Main function
int main(int argc, char **argv)
{
SDL_Event event;
char title[256];
char fpstxt[16];
// Process arguments
printf("%s\n\n", AppName);
PokeMini_InitDirs(argv[0], NULL);
CommandLineInit();
CommandLineConfFile("/tmp0/1/pokemini.cfg", "/tmp0/1/pokemini_sdl.cfg", CustomConf);
if (!CommandLineArgs(argc, argv, CustomArgs)) {
PrintHelpUsage(stdout);
printf(" -dumpsound sound.wav Dump sound into a WAV file\n");
printf(" -windowed Display in window (default)\n");
printf(" -fullscreen Display in fullscreen\n");
printf(" -displayfps Display FPS counter on screen\n");
printf(" -zoom n Zoom display: 1 to 6 (def 4)\n");
printf(" -bpp n Bits-Per-Pixel: 16 or 32 (def 16)\n");
return 1;
}
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
return 1;
}
atexit(SDL_Quit); // Clean up on exit
// Initialize the display
setup_screen();
// Initialize the sound
SDL_AudioSpec audfmt;
SDL_AudioSpec obtained;
audfmt.freq = 44100;
audfmt.format = AUDIO_U8;
audfmt.channels = 1;
audfmt.samples = SOUNDBUFFER;
audfmt.callback = emulatorsound;
audfmt.userdata = NULL;
// Open the audio device
// Second setting can't be NULL on KolibriOS or else it wont play sound
if (SDL_OpenAudio(&audfmt, &obtained) < 0) {
fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
fprintf(stderr, "Audio will be disabled\n");
AudioEnabled = 0;
} else {
AudioEnabled = 1;
}
// Set the window manager title bar
SDL_WM_SetCaption(AppName, "PMEWindow");
SDL_EnableKeyRepeat(0, 0);
// Open WAV capture if was requested
if (clc_dump_sound[0]) {
sdump = Open_ExportWAV(clc_dump_sound, EXPORTWAV_44KHZ | EXPORTWAV_MONO | EXPORTWAV_16BITS);
if (!sdump) {
fprintf(stderr, "Error opening sound export file.\n");
return 1;
}
}
// Initialize the emulator
printf("Starting emulator...\n");
if (!PokeMini_Create(0, PMSNDBUFFER)) {
fprintf(stderr, "Error while initializing emulator\n");
return 1;
}
// Setup palette and LCD mode
PokeMini_VideoPalette_Init(PokeMini_BGR16, 1);
PokeMini_VideoPalette_Index(CommandLine.palette, CommandLine.custompal, CommandLine.lcdcontrast, CommandLine.lcdbright);
PokeMini_ApplyChanges();
// Load stuff
PokeMini_UseDefaultCallbacks();
if (!PokeMini_LoadFromCommandLines("Using FreeBIOS", "EEPROM data will be discarded!")) {
UI_Status = UI_STATUS_MENU;
}
// Enable sound & init UI
printf("Running emulator...\n");
UIMenu_Init();
KeyboardRemap(&KeybMapSDL);
if (CommandLine.sound == MINX_AUDIO_GENERATED)
enablesound(MINX_AUDIO_GENERATED);
else
enablesound(0);
// Emulator's loop
unsigned long time, NewTickFPS = 0, NewTickSync = 0;
int fps = 72, fpscnt = 0;
while (emurunning) {
// Emulate and syncronize
time = SDL_GetTicks();
if (RequireSoundSync) {
PokeMini_EmulateFrame();
// Sleep a little in the hope to free a few samples
if (emulimiter) while (MinxAudio_SyncWithAudio()) SDL_Delay(1);
} else {
PokeMini_EmulateFrame();
if (emulimiter) {
do {
SDL_Delay(1); // This lower CPU usage
time = SDL_GetTicks();
} while (time < NewTickSync);
NewTickSync = time + 13; // Aprox 72 times per sec
}
}
// Screen rendering
SDL_FillRect(screen, NULL, 0);
if (SDL_LockSurface(screen) == 0) {
// Render the menu or the game screen
if (PokeMini_Rumbling) {
PokeMini_VideoBlit((void *)((uint8_t *)screen->pixels + PMOff + PokeMini_GenRumbleOffset(screen->pitch)), PixPitch);
} else {
PokeMini_VideoBlit((void *)((uint8_t *)screen->pixels + PMOff), PixPitch);
}
LCDDirty = 0;
// Display FPS counter
if (clc_displayfps) {
if (PokeMini_VideoDepth == 32)
UIDraw_String_32((uint32_t *)screen->pixels, PixPitch, 4, 4, 10, fpstxt, UI_Font1_Pal32);
else
UIDraw_String_16((uint16_t *)screen->pixels, PixPitch, 4, 4, 10, fpstxt, UI_Font1_Pal16);
}
// Unlock surface
SDL_UnlockSurface(screen);
SDL_Flip(screen);
}
// Handle events
while (SDL_PollEvent(&event)) handleevents(&event);
// Menu
if (UI_Status == UI_STATUS_MENU) menuloop();
// calculate FPS
fpscnt++;
if (time >= NewTickFPS) {
fps = fpscnt;
sprintf(title, "%s - %d%%", AppName, fps * 100 / 72);
sprintf(fpstxt, "%i FPS", fps);
SDL_WM_SetCaption(title, "PMEWindow");
NewTickFPS = time + 1000;
fpscnt = 0;
}
}
// Disable sound & free UI
enablesound(0);
UIMenu_Destroy();
// Close WAV capture if there's one
if (clc_dump_sound[0]) Close_ExportWAV(sdump);
// Save Stuff
PokeMini_SaveFromCommandLines(1);
// Terminate...
printf("Shutdown emulator...\n");
PokeMini_VideoPalette_Free();
PokeMini_Destroy();
return 0;
}