picodrive/platform/common/emu.c
notaz cff531af94 clarify PicoDrive's license
- PicoDrive was originally released by fDave with simple
  "free for non-commercial use / For commercial use, separate licencing
  terms must be obtained" license and I kept it in my releases.
- in 2011, fDave re-released his code (same that I used as base
  many years ago) dual licensed with GPLv2 and MAME licenses:
    https://code.google.com/p/cyclone68000/

Based on the above I now proclaim that the whole source code is licensed
under the MAME license as more elaborate form of "for non-commercial use".
If that raises any doubt, I announce that all my modifications (which
is the vast majority of code by now) is licensed under the MAME license,
as it reads in COPYING file in this commit.

This does not affect ym2612.c/sn76496.c that were MAME licensed already
from the beginning.
2013-06-26 03:07:07 +03:00

1640 lines
40 KiB
C

/*
* PicoDrive
* (C) notaz, 2007-2010
*
* This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#ifndef NO_SYNC
#include <unistd.h>
#endif
#include "../libpicofe/posix.h"
#include "../libpicofe/input.h"
#include "../libpicofe/fonts.h"
#include "../libpicofe/lprintf.h"
#include "../libpicofe/plat.h"
#include "emu.h"
#include "input_pico.h"
#include "menu_pico.h"
#include "config.h"
#include <pico/pico_int.h>
#include <pico/patch.h>
#include <pico/cd/cue.h>
#define STATUS_MSG_TIMEOUT 2000
void *g_screen_ptr;
#if !SCREEN_SIZE_FIXED
int g_screen_width = SCREEN_WIDTH;
int g_screen_height = SCREEN_HEIGHT;
#endif
char *PicoConfigFile = "config.cfg";
currentConfig_t currentConfig, defaultConfig;
int state_slot = 0;
int config_slot = 0, config_slot_current = 0;
int pico_pen_x = 320/2, pico_pen_y = 240/2;
int pico_inp_mode = 0;
int engineState = PGS_Menu;
/* tmp buff to reduce stack usage for plats with small stack */
static char static_buff[512];
/* TODO: len checking */
char rom_fname_reload[512];
char rom_fname_loaded[512];
int rom_loaded = 0;
int reset_timing = 0;
static unsigned int notice_msg_time; /* when started showing */
static char noticeMsg[40];
unsigned char *movie_data = NULL;
static int movie_size = 0;
/* don't use tolower() for easy old glibc binary compatibility */
static void strlwr_(char *string)
{
char *p;
for (p = string; *p; p++)
if ('A' <= *p && *p <= 'Z')
*p += 'a' - 'A';
}
static int try_rfn_cut(char *fname)
{
FILE *tmp;
char *p;
p = fname + strlen(fname) - 1;
for (; p > fname; p--)
if (*p == '.') break;
*p = 0;
if((tmp = fopen(fname, "rb"))) {
fclose(tmp);
return 1;
}
return 0;
}
static void get_ext(const char *file, char *ext)
{
const char *p;
p = file + strlen(file) - 4;
if (p < file) p = file;
strncpy(ext, p, 4);
ext[4] = 0;
strlwr_(ext);
}
static void fname_ext(char *dst, int dstlen, const char *prefix, const char *ext, const char *fname)
{
int prefix_len = 0;
const char *p;
*dst = 0;
if (prefix) {
int len = plat_get_root_dir(dst, dstlen);
strcpy(dst + len, prefix);
prefix_len = len + strlen(prefix);
}
p = fname + strlen(fname) - 1;
for (; p >= fname && *p != PATH_SEP_C; p--)
;
p++;
strncpy(dst + prefix_len, p, dstlen - prefix_len - 1);
dst[dstlen - 8] = 0;
if (dst[strlen(dst) - 4] == '.')
dst[strlen(dst) - 4] = 0;
if (ext)
strcat(dst, ext);
}
static void romfname_ext(char *dst, int dstlen, const char *prefix, const char *ext)
{
fname_ext(dst, dstlen, prefix, ext, rom_fname_loaded);
}
void emu_status_msg(const char *format, ...)
{
va_list vl;
int ret;
va_start(vl, format);
ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);
va_end(vl);
/* be sure old text gets overwritten */
for (; ret < 28; ret++)
noticeMsg[ret] = ' ';
noticeMsg[ret] = 0;
notice_msg_time = plat_get_ticks_ms();
}
static const char * const biosfiles_us[] = { "us_scd1_9210", "us_scd2_9306", "SegaCDBIOS9303" };
static const char * const biosfiles_eu[] = { "eu_mcd1_9210", "eu_mcd2_9306", "eu_mcd2_9303" };
static const char * const biosfiles_jp[] = { "jp_mcd1_9112", "jp_mcd1_9111" };
static int find_bios(int region, char **bios_file)
{
int i, count;
const char * const *files;
FILE *f = NULL;
if (region == 4) { // US
files = biosfiles_us;
count = sizeof(biosfiles_us) / sizeof(char *);
} else if (region == 8) { // EU
files = biosfiles_eu;
count = sizeof(biosfiles_eu) / sizeof(char *);
} else if (region == 1 || region == 2) {
files = biosfiles_jp;
count = sizeof(biosfiles_jp) / sizeof(char *);
} else {
return 0;
}
for (i = 0; i < count; i++)
{
emu_make_path(static_buff, files[i], sizeof(static_buff) - 4);
strcat(static_buff, ".bin");
f = fopen(static_buff, "rb");
if (f) break;
static_buff[strlen(static_buff) - 4] = 0;
strcat(static_buff, ".zip");
f = fopen(static_buff, "rb");
if (f) break;
}
if (f) {
lprintf("using bios: %s\n", static_buff);
fclose(f);
if (bios_file)
*bios_file = static_buff;
return 1;
} else {
sprintf(static_buff, "no %s BIOS files found, read docs",
region != 4 ? (region == 8 ? "EU" : "JAP") : "USA");
menu_update_msg(static_buff);
return 0;
}
}
/* check if the name begins with BIOS name */
/*
static int emu_isBios(const char *name)
{
int i;
for (i = 0; i < sizeof(biosfiles_us)/sizeof(biosfiles_us[0]); i++)
if (strstr(name, biosfiles_us[i]) != NULL) return 1;
for (i = 0; i < sizeof(biosfiles_eu)/sizeof(biosfiles_eu[0]); i++)
if (strstr(name, biosfiles_eu[i]) != NULL) return 1;
for (i = 0; i < sizeof(biosfiles_jp)/sizeof(biosfiles_jp[0]); i++)
if (strstr(name, biosfiles_jp[i]) != NULL) return 1;
return 0;
}
*/
static unsigned char id_header[0x100];
/* checks if fname points to valid MegaCD image */
static int emu_cd_check(int *pregion, const char *fname_in)
{
const char *fname = fname_in;
unsigned char buf[32];
pm_file *cd_f;
int region = 4; // 1: Japan, 4: US, 8: Europe
char ext[5];
cue_track_type type = CT_UNKNOWN;
cue_data_t *cue_data = NULL;
get_ext(fname_in, ext);
if (strcasecmp(ext, ".cue") == 0) {
cue_data = cue_parse(fname_in);
if (cue_data != NULL) {
fname = cue_data->tracks[1].fname;
type = cue_data->tracks[1].type;
}
else
return -1;
}
cd_f = pm_open(fname);
if (cue_data != NULL)
cue_destroy(cue_data);
if (cd_f == NULL) return 0; // let the upper level handle this
if (pm_read(buf, 32, cd_f) != 32) {
pm_close(cd_f);
return -1;
}
if (!strncasecmp("SEGADISCSYSTEM", (char *)buf+0x00, 14)) {
if (type && type != CT_ISO)
elprintf(EL_STATUS, ".cue has wrong type: %i", type);
type = CT_ISO; // Sega CD (ISO)
}
if (!strncasecmp("SEGADISCSYSTEM", (char *)buf+0x10, 14)) {
if (type && type != CT_BIN)
elprintf(EL_STATUS, ".cue has wrong type: %i", type);
type = CT_BIN; // Sega CD (BIN)
}
if (type == CT_UNKNOWN) {
pm_close(cd_f);
return 0;
}
pm_seek(cd_f, (type == CT_ISO) ? 0x100 : 0x110, SEEK_SET);
pm_read(id_header, sizeof(id_header), cd_f);
/* it seems we have a CD image here. Try to detect region now.. */
pm_seek(cd_f, (type == CT_ISO) ? 0x100+0x10B : 0x110+0x10B, SEEK_SET);
pm_read(buf, 1, cd_f);
pm_close(cd_f);
if (buf[0] == 0x64) region = 8; // EU
if (buf[0] == 0xa1) region = 1; // JAP
lprintf("detected %s Sega/Mega CD image with %s region\n",
type == CT_BIN ? "BIN" : "ISO", region != 4 ? (region == 8 ? "EU" : "JAP") : "USA");
if (pregion != NULL) *pregion = region;
return type;
}
static int detect_media(const char *fname)
{
static const short sms_offsets[] = { 0x7ff0, 0x3ff0, 0x1ff0 };
static const char *sms_exts[] = { "sms", "gg", "sg" };
static const char *md_exts[] = { "gen", "bin", "smd" };
char buff0[32], buff[32];
unsigned short *d16;
pm_file *pmf;
char ext[5];
int i;
get_ext(fname, ext);
// detect wrong extensions
if (!strcmp(ext, ".srm") || !strcmp(ext, "s.gz") || !strcmp(ext, ".mds")) // s.gz ~ .mds.gz
return PM_BAD;
/* don't believe in extensions, except .cue */
if (strcasecmp(ext, ".cue") == 0)
return PM_CD;
pmf = pm_open(fname);
if (pmf == NULL)
return PM_BAD;
if (pm_read(buff0, 32, pmf) != 32) {
pm_close(pmf);
return PM_BAD;
}
if (strncasecmp("SEGADISCSYSTEM", buff0 + 0x00, 14) == 0 ||
strncasecmp("SEGADISCSYSTEM", buff0 + 0x10, 14) == 0) {
pm_close(pmf);
return PM_CD;
}
/* check for SMD evil */
if (pmf->size >= 0x4200 && (pmf->size & 0x3fff) == 0x200) {
if (pm_seek(pmf, sms_offsets[0] + 0x200, SEEK_SET) == sms_offsets[0] + 0x200 &&
pm_read(buff, 16, pmf) == 16 &&
strncmp("TMR SEGA", buff, 8) == 0)
goto looks_like_sms;
/* could parse further but don't bother */
goto extension_check;
}
/* MD header? Act as TMSS BIOS here */
if (pm_seek(pmf, 0x100, SEEK_SET) == 0x100 && pm_read(buff, 16, pmf) == 16) {
if (strncmp(buff, "SEGA", 4) == 0 || strncmp(buff, " SEG", 4) == 0)
goto looks_like_md;
}
for (i = 0; i < array_size(sms_offsets); i++) {
if (pm_seek(pmf, sms_offsets[i], SEEK_SET) != sms_offsets[i])
continue;
if (pm_read(buff, 16, pmf) != 16)
continue;
if (strncmp("TMR SEGA", buff, 8) == 0)
goto looks_like_sms;
}
extension_check:
/* probably some headerless thing. Maybe check the extension after all. */
for (i = 0; i < array_size(md_exts); i++)
if (strcasecmp(pmf->ext, md_exts[i]) == 0)
goto looks_like_md;
for (i = 0; i < array_size(sms_exts); i++)
if (strcasecmp(pmf->ext, sms_exts[i]) == 0)
goto looks_like_sms;
/* If everything else fails, make a guess on the reset vector */
d16 = (unsigned short *)(buff0 + 4);
if ((((d16[0] << 16) | d16[1]) & 0xffffff) >= pmf->size) {
lprintf("bad MD reset vector, assuming SMS\n");
goto looks_like_sms;
}
looks_like_md:
pm_close(pmf);
return PM_MD_CART;
looks_like_sms:
pm_close(pmf);
return PM_MARK3;
}
static int extract_text(char *dest, const unsigned char *src, int len, int swab)
{
char *p = dest;
int i;
if (swab) swab = 1;
for (i = len - 1; i >= 0; i--)
{
if (src[i^swab] != ' ') break;
}
len = i + 1;
for (i = 0; i < len; i++)
{
unsigned char s = src[i^swab];
if (s >= 0x20 && s < 0x7f && s != '#' && s != '|' &&
s != '[' && s != ']' && s != '\\')
{
*p++ = s;
}
else
{
sprintf(p, "\\%02x", s);
p += 3;
}
}
return p - dest;
}
static char *emu_make_rom_id(const char *fname)
{
static char id_string[3+0xe*3+0x3*3+0x30*3+3];
int pos, swab = 1;
if (PicoAHW & PAHW_MCD) {
strcpy(id_string, "CD|");
swab = 0;
}
else if (PicoAHW & PAHW_SMS)
strcpy(id_string, "MS|");
else strcpy(id_string, "MD|");
pos = 3;
if (!(PicoAHW & PAHW_SMS)) {
pos += extract_text(id_string + pos, id_header + 0x80, 0x0e, swab); // serial
id_string[pos] = '|'; pos++;
pos += extract_text(id_string + pos, id_header + 0xf0, 0x03, swab); // region
id_string[pos] = '|'; pos++;
pos += extract_text(id_string + pos, id_header + 0x50, 0x30, swab); // overseas name
id_string[pos] = 0;
if (pos > 5)
return id_string;
pos = 3;
}
// can't find name in ROM, use filename
fname_ext(id_string + 3, sizeof(id_string) - 3, NULL, NULL, fname);
return id_string;
}
// buffer must be at least 150 byte long
void emu_get_game_name(char *str150)
{
int ret, swab = (PicoAHW & PAHW_MCD) ? 0 : 1;
char *s, *d;
ret = extract_text(str150, id_header + 0x50, 0x30, swab); // overseas name
for (s = d = str150 + 1; s < str150+ret; s++)
{
if (*s == 0) break;
if (*s != ' ' || d[-1] != ' ')
*d++ = *s;
}
*d = 0;
}
static void shutdown_MCD(void)
{
if ((PicoAHW & PAHW_MCD) && Pico_mcd != NULL)
Stop_CD();
PicoAHW &= ~PAHW_MCD;
}
static void system_announce(void)
{
const char *sys_name, *tv_standard, *extra = "";
int fps;
if (PicoAHW & PAHW_SMS) {
sys_name = "Master System";
#ifdef NO_SMS
extra = " [no support]";
#endif
} else if (PicoAHW & PAHW_PICO) {
sys_name = "Pico";
} else if (PicoAHW & PAHW_MCD) {
sys_name = "Mega CD";
if ((Pico.m.hardware & 0xc0) == 0x80)
sys_name = "Sega CD";
} else if (PicoAHW & PAHW_32X) {
sys_name = "32X";
} else {
sys_name = "MegaDrive";
if ((Pico.m.hardware & 0xc0) == 0x80)
sys_name = "Genesis";
}
tv_standard = Pico.m.pal ? "PAL" : "NTSC";
fps = Pico.m.pal ? 50 : 60;
emu_status_msg("%s %s / %dFPS%s", tv_standard, sys_name, fps, extra);
}
// note: this function might mangle rom_fname
// XXX: portions of this code should move to pico/
int emu_reload_rom(char *rom_fname)
{
unsigned int rom_size = 0;
char *used_rom_name = rom_fname;
unsigned char *rom_data = NULL;
char ext[5];
pm_file *rom = NULL;
int cd_state = CIT_NOT_CD;
int ret, media_type, cd_region;
int cfg_loaded = 0, bad_rom = 0;
lprintf("emu_ReloadRom(%s)\n", rom_fname);
get_ext(rom_fname, ext);
// early cleanup
PicoPatchUnload();
if (movie_data) {
free(movie_data);
movie_data = 0;
}
if (!strcmp(ext, ".gmv"))
{
// check for both gmv and rom
int dummy;
FILE *movie_file = fopen(rom_fname, "rb");
if (!movie_file) {
menu_update_msg("Failed to open movie.");
return 0;
}
fseek(movie_file, 0, SEEK_END);
movie_size = ftell(movie_file);
fseek(movie_file, 0, SEEK_SET);
if (movie_size < 64+3) {
menu_update_msg("Invalid GMV file.");
fclose(movie_file);
return 0;
}
movie_data = malloc(movie_size);
if (movie_data == NULL) {
menu_update_msg("low memory.");
fclose(movie_file);
return 0;
}
dummy = fread(movie_data, 1, movie_size, movie_file);
fclose(movie_file);
if (strncmp((char *)movie_data, "Gens Movie TEST", 15) != 0) {
menu_update_msg("Invalid GMV file.");
return 0;
}
dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
if (!dummy) {
menu_update_msg("Could't find a ROM for movie.");
return 0;
}
get_ext(rom_fname, ext);
lprintf("gmv loaded for %s\n", rom_fname);
}
else if (!strcmp(ext, ".pat"))
{
int dummy;
PicoPatchLoad(rom_fname);
dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
if (!dummy) {
menu_update_msg("Could't find a ROM to patch.");
return 0;
}
get_ext(rom_fname, ext);
}
media_type = detect_media(rom_fname);
if (media_type == PM_BAD) {
menu_update_msg("Not a ROM/CD img selected.");
return 0;
}
shutdown_MCD();
PicoCartUnload();
rom_loaded = 0;
PicoAHW = 0;
if (media_type == PM_CD)
{
// check for MegaCD image
cd_state = emu_cd_check(&cd_region, rom_fname);
if (cd_state >= 0 && cd_state != CIT_NOT_CD)
{
// valid CD image, check for BIOS..
// we need to have config loaded at this point
ret = emu_read_config(rom_fname, 0);
if (!ret) emu_read_config(NULL, 0);
cfg_loaded = 1;
if (PicoRegionOverride) {
cd_region = PicoRegionOverride;
lprintf("override region to %s\n", cd_region != 4 ?
(cd_region == 8 ? "EU" : "JAP") : "USA");
}
if (!find_bios(cd_region, &used_rom_name))
return 0;
get_ext(used_rom_name, ext);
PicoAHW |= PAHW_MCD;
}
else {
menu_update_msg("Invalid CD image");
return 0;
}
}
else if (media_type == PM_MARK3) {
lprintf("detected SMS ROM\n");
PicoAHW = PAHW_SMS;
}
rom = pm_open(used_rom_name);
if (rom == NULL) {
menu_update_msg("Failed to open ROM");
return 0;
}
menu_romload_prepare(used_rom_name); // also CD load
used_rom_name = NULL; // uses static_buff
ret = PicoCartLoad(rom, &rom_data, &rom_size, (PicoAHW & PAHW_SMS) ? 1 : 0);
pm_close(rom);
if (ret != 0) {
if (ret == 2) menu_update_msg("Out of memory");
else if (ret == 3) menu_update_msg("Read failed");
else menu_update_msg("PicoCartLoad() failed.");
goto fail;
}
// detect wrong files
if (strncmp((char *)rom_data, "Pico", 4) == 0)
bad_rom = 1;
else if (!(PicoAHW & PAHW_SMS)) {
unsigned short *d = (unsigned short *)(rom_data + 4);
if ((((d[0] << 16) | d[1]) & 0xffffff) >= (int)rom_size) {
lprintf("bad reset vector\n");
bad_rom = 1;
}
}
if (bad_rom) {
menu_update_msg("Bad ROM detected.");
goto fail;
}
// load config for this ROM (do this before insert to get correct region)
if (!(PicoAHW & PAHW_MCD))
memcpy(id_header, rom_data + 0x100, sizeof(id_header));
if (!cfg_loaded) {
ret = emu_read_config(rom_fname, 0);
if (!ret) emu_read_config(NULL, 0);
}
emu_make_path(static_buff, "carthw.cfg", sizeof(static_buff));
if (PicoCartInsert(rom_data, rom_size, static_buff)) {
menu_update_msg("Failed to load ROM.");
goto fail;
}
// insert CD if it was detected
if (cd_state != CIT_NOT_CD) {
ret = Insert_CD(rom_fname, cd_state);
if (ret != 0) {
PicoCartUnload();
rom_data = NULL; // freed by unload
menu_update_msg("Insert_CD() failed, invalid CD image?");
goto fail;
}
}
menu_romload_end();
if (PicoPatches) {
PicoPatchPrepare();
PicoPatchApply();
}
// additional movie stuff
if (movie_data)
{
if (movie_data[0x14] == '6')
PicoOpt |= POPT_6BTN_PAD; // 6 button pad
else PicoOpt &= ~POPT_6BTN_PAD;
PicoOpt |= POPT_DIS_VDP_FIFO; // no VDP fifo timing
if (movie_data[0xF] >= 'A') {
if (movie_data[0x16] & 0x80) {
PicoRegionOverride = 8;
} else {
PicoRegionOverride = 4;
}
PicoReset();
// TODO: bits 6 & 5
}
movie_data[0x18+30] = 0;
emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);
}
else
{
system_announce();
PicoOpt &= ~POPT_DIS_VDP_FIFO;
}
strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);
rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;
rom_loaded = 1;
// load SRAM for this ROM
if (currentConfig.EmuOpt & EOPT_EN_SRAM)
emu_save_load_game(1, 1);
return 1;
fail:
if (rom_data)
free(rom_data);
menu_romload_end();
return 0;
}
int emu_swap_cd(const char *fname)
{
cd_img_type cd_type;
int ret = -1;
cd_type = emu_cd_check(NULL, fname);
if (cd_type != CIT_NOT_CD)
ret = Insert_CD(fname, cd_type);
if (ret != 0) {
menu_update_msg("Load failed, invalid CD image?");
return 0;
}
strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);
rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;
return 1;
}
// <base dir><end>
void emu_make_path(char *buff, const char *end, int size)
{
int pos, end_len;
end_len = strlen(end);
pos = plat_get_root_dir(buff, size);
strncpy(buff + pos, end, size - pos);
buff[size - 1] = 0;
if (pos + end_len > size - 1)
lprintf("Warning: path truncated: %s\n", buff);
}
static void make_config_cfg(char *cfg_buff_512)
{
emu_make_path(cfg_buff_512, PicoConfigFile, 512-6);
if (config_slot != 0)
{
char *p = strrchr(cfg_buff_512, '.');
if (p == NULL)
p = cfg_buff_512 + strlen(cfg_buff_512);
sprintf(p, ".%i.cfg", config_slot);
}
cfg_buff_512[511] = 0;
}
void emu_prep_defconfig(void)
{
memset(&defaultConfig, 0, sizeof(defaultConfig));
defaultConfig.EmuOpt = 0x9d | EOPT_RAM_TIMINGS|EOPT_EN_CD_LEDS;
defaultConfig.s_PicoOpt = POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80 |
POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_SVP_DRC|POPT_ACC_SPRITES |
POPT_EN_32X|POPT_EN_PWM;
defaultConfig.s_PsndRate = 44100;
defaultConfig.s_PicoRegion = 0; // auto
defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP
defaultConfig.s_PicoCDBuffers = 0;
defaultConfig.confirm_save = EOPT_CONFIRM_SAVE;
defaultConfig.Frameskip = -1; // auto
defaultConfig.volume = 50;
defaultConfig.gamma = 100;
defaultConfig.scaling = 0;
defaultConfig.turbo_rate = 15;
// platform specific overrides
pemu_prep_defconfig();
}
void emu_set_defconfig(void)
{
memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));
PicoOpt = currentConfig.s_PicoOpt;
PsndRate = currentConfig.s_PsndRate;
PicoRegionOverride = currentConfig.s_PicoRegion;
PicoAutoRgnOrder = currentConfig.s_PicoAutoRgnOrder;
PicoCDBuffers = currentConfig.s_PicoCDBuffers;
p32x_msh2_multiplier = MSH2_MULTI_DEFAULT;
p32x_ssh2_multiplier = SSH2_MULTI_DEFAULT;
}
int emu_read_config(const char *rom_fname, int no_defaults)
{
char cfg[512];
int ret;
if (!no_defaults)
emu_set_defconfig();
if (rom_fname == NULL)
{
// global config
make_config_cfg(cfg);
ret = config_readsect(cfg, NULL);
}
else
{
char *sect = emu_make_rom_id(rom_fname);
if (config_slot != 0)
sprintf(cfg, "game.%i.cfg", config_slot);
else strcpy(cfg, "game.cfg");
ret = -1;
if (config_havesect(cfg, sect))
{
// read user's config
int vol = currentConfig.volume;
emu_set_defconfig();
ret = config_readsect(cfg, sect);
currentConfig.volume = vol; // make vol global (bah)
}
else
{
// read global config, and apply game_def.cfg on top
make_config_cfg(cfg);
config_readsect(cfg, NULL);
emu_make_path(cfg, "game_def.cfg", sizeof(cfg));
ret = config_readsect(cfg, sect);
}
if (ret == 0)
{
lprintf("loaded cfg from sect \"%s\"\n", sect);
}
}
pemu_validate_config();
// some sanity checks
#ifdef PSP
/* TODO: mv to plat_validate_config() */
if (currentConfig.CPUclock < 10 || currentConfig.CPUclock > 4096) currentConfig.CPUclock = 200;
if (currentConfig.gamma < -4 || currentConfig.gamma > 16) currentConfig.gamma = 0;
if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2) currentConfig.gamma2 = 0;
#endif
if (currentConfig.volume < 0 || currentConfig.volume > 99)
currentConfig.volume = 50;
if (ret == 0)
config_slot_current = config_slot;
return (ret == 0);
}
int emu_write_config(int is_game)
{
char cfg[512], *game_sect = NULL;
int ret, write_lrom = 0;
if (!is_game)
{
make_config_cfg(cfg);
write_lrom = 1;
} else {
if (config_slot != 0)
sprintf(cfg, "game.%i.cfg", config_slot);
else strcpy(cfg, "game.cfg");
game_sect = emu_make_rom_id(rom_fname_loaded);
lprintf("emu_write_config: sect \"%s\"\n", game_sect);
}
lprintf("emu_write_config: %s ", cfg);
ret = config_writesect(cfg, game_sect);
if (write_lrom) config_writelrom(cfg);
#ifndef NO_SYNC
sync();
#endif
lprintf((ret == 0) ? "(ok)\n" : "(failed)\n");
if (ret == 0) config_slot_current = config_slot;
return ret == 0;
}
/* always using built-in font */
#define mk_text_out(name, type, val, topleft, step_x, step_y) \
void name(int x, int y, const char *text) \
{ \
int i, l, len = strlen(text); \
type *screen = (type *)(topleft) + x * step_x + y * step_y; \
\
for (i = 0; i < len; i++, screen += 8 * step_x) \
{ \
for (l = 0; l < 8; l++) \
{ \
unsigned char fd = fontdata8x8[text[i] * 8 + l];\
type *s = screen + l * step_y; \
if (fd&0x80) s[step_x * 0] = val; \
if (fd&0x40) s[step_x * 1] = val; \
if (fd&0x20) s[step_x * 2] = val; \
if (fd&0x10) s[step_x * 3] = val; \
if (fd&0x08) s[step_x * 4] = val; \
if (fd&0x04) s[step_x * 5] = val; \
if (fd&0x02) s[step_x * 6] = val; \
if (fd&0x01) s[step_x * 7] = val; \
} \
} \
}
mk_text_out(emu_text_out8, unsigned char, 0xf0, g_screen_ptr, 1, g_screen_width)
mk_text_out(emu_text_out16, unsigned short, 0xffff, g_screen_ptr, 1, g_screen_width)
mk_text_out(emu_text_out8_rot, unsigned char, 0xf0,
(char *)g_screen_ptr + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)
mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,
(short *)g_screen_ptr + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)
#undef mk_text_out
void update_movie(void)
{
int offs = Pico.m.frame_count*3 + 0x40;
if (offs+3 > movie_size) {
free(movie_data);
movie_data = 0;
emu_status_msg("END OF MOVIE.");
lprintf("END OF MOVIE.\n");
} else {
// MXYZ SACB RLDU
PicoPad[0] = ~movie_data[offs] & 0x8f; // ! SCBA RLDU
if(!(movie_data[offs] & 0x10)) PicoPad[0] |= 0x40; // C
if(!(movie_data[offs] & 0x20)) PicoPad[0] |= 0x10; // A
if(!(movie_data[offs] & 0x40)) PicoPad[0] |= 0x20; // B
PicoPad[1] = ~movie_data[offs+1] & 0x8f; // ! SCBA RLDU
if(!(movie_data[offs+1] & 0x10)) PicoPad[1] |= 0x40; // C
if(!(movie_data[offs+1] & 0x20)) PicoPad[1] |= 0x10; // A
if(!(movie_data[offs+1] & 0x40)) PicoPad[1] |= 0x20; // B
PicoPad[0] |= (~movie_data[offs+2] & 0x0A) << 8; // ! MZYX
if(!(movie_data[offs+2] & 0x01)) PicoPad[0] |= 0x0400; // X
if(!(movie_data[offs+2] & 0x04)) PicoPad[0] |= 0x0100; // Z
PicoPad[1] |= (~movie_data[offs+2] & 0xA0) << 4; // ! MZYX
if(!(movie_data[offs+2] & 0x10)) PicoPad[1] |= 0x0400; // X
if(!(movie_data[offs+2] & 0x40)) PicoPad[1] |= 0x0100; // Z
}
}
static int try_ropen_file(const char *fname)
{
FILE *f;
f = fopen(fname, "rb");
if (f) {
fclose(f);
return 1;
}
return 0;
}
char *emu_get_save_fname(int load, int is_sram, int slot)
{
char *saveFname = static_buff;
char ext[16];
if (is_sram)
{
strcpy(ext, (PicoAHW & PAHW_MCD) ? ".brm" : ".srm");
romfname_ext(saveFname, sizeof(static_buff),
(PicoAHW & PAHW_MCD) ? "brm"PATH_SEP : "srm"PATH_SEP, ext);
if (!load)
return saveFname;
if (try_ropen_file(saveFname))
return saveFname;
romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
if (try_ropen_file(saveFname))
return saveFname;
}
else
{
const char *ext_main = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds.gz" : ".mds";
const char *ext_othr = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds" : ".mds.gz";
ext[0] = 0;
if (slot > 0 && slot < 10)
sprintf(ext, ".%i", slot);
strcat(ext, ext_main);
if (!load) {
romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
return saveFname;
}
else {
romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
if (try_ropen_file(saveFname))
return saveFname;
romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
if (try_ropen_file(saveFname))
return saveFname;
// try the other ext
ext[0] = 0;
if (slot > 0 && slot < 10)
sprintf(ext, ".%i", slot);
strcat(ext, ext_othr);
romfname_ext(saveFname, sizeof(static_buff), "mds"PATH_SEP, ext);
if (try_ropen_file(saveFname))
return saveFname;
}
}
return NULL;
}
int emu_check_save_file(int slot, int *time)
{
return emu_get_save_fname(1, 0, slot) ? 1 : 0;
}
int emu_save_load_game(int load, int sram)
{
int ret = 0;
char *saveFname;
// make save filename
saveFname = emu_get_save_fname(load, sram, state_slot);
if (saveFname == NULL) {
if (!sram)
emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");
return -1;
}
lprintf("saveLoad (%i, %i): %s\n", load, sram, saveFname);
if (sram)
{
FILE *sramFile;
int sram_size;
unsigned char *sram_data;
int truncate = 1;
if (PicoAHW & PAHW_MCD)
{
if (PicoOpt & POPT_EN_MCD_RAMCART) {
sram_size = 0x12000;
sram_data = SRam.data;
if (sram_data)
memcpy32((int *)sram_data, (int *)Pico_mcd->bram, 0x2000/4);
} else {
sram_size = 0x2000;
sram_data = Pico_mcd->bram;
truncate = 0; // the .brm may contain RAM cart data after normal brm
}
} else {
sram_size = SRam.size;
sram_data = SRam.data;
}
if (sram_data == NULL)
return 0; // SRam forcefully disabled for this game
if (load)
{
sramFile = fopen(saveFname, "rb");
if (!sramFile)
return -1;
ret = fread(sram_data, 1, sram_size, sramFile);
ret = ret > 0 ? 0 : -1;
fclose(sramFile);
if ((PicoAHW & PAHW_MCD) && (PicoOpt&POPT_EN_MCD_RAMCART))
memcpy32((int *)Pico_mcd->bram, (int *)sram_data, 0x2000/4);
} else {
// sram save needs some special processing
// see if we have anything to save
for (; sram_size > 0; sram_size--)
if (sram_data[sram_size-1]) break;
if (sram_size) {
sramFile = fopen(saveFname, truncate ? "wb" : "r+b");
if (!sramFile) sramFile = fopen(saveFname, "wb"); // retry
if (!sramFile) return -1;
ret = fwrite(sram_data, 1, sram_size, sramFile);
ret = (ret != sram_size) ? -1 : 0;
fclose(sramFile);
#ifndef NO_SYNC
sync();
#endif
}
}
return ret;
}
else
{
ret = PicoState(saveFname, !load);
if (!ret) {
#ifndef NO_SYNC
if (!load) sync();
#endif
emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");
} else {
emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");
ret = -1;
}
return ret;
}
}
void emu_set_fastforward(int set_on)
{
static void *set_PsndOut = NULL;
static int set_Frameskip, set_EmuOpt, is_on = 0;
if (set_on && !is_on) {
set_PsndOut = PsndOut;
set_Frameskip = currentConfig.Frameskip;
set_EmuOpt = currentConfig.EmuOpt;
PsndOut = NULL;
currentConfig.Frameskip = 8;
currentConfig.EmuOpt &= ~4;
currentConfig.EmuOpt |= 0x40000;
is_on = 1;
emu_status_msg("FAST FORWARD");
}
else if (!set_on && is_on) {
PsndOut = set_PsndOut;
currentConfig.Frameskip = set_Frameskip;
currentConfig.EmuOpt = set_EmuOpt;
PsndRerate(1);
is_on = 0;
}
}
static void emu_tray_open(void)
{
engineState = PGS_TrayMenu;
}
static void emu_tray_close(void)
{
emu_status_msg("CD tray closed.");
}
void emu_32x_startup(void)
{
plat_video_toggle_renderer(0, 0); // HACK
system_announce();
// force mode change event
rendstatus_old = -1;
}
void emu_reset_game(void)
{
PicoReset();
reset_timing = 1;
}
void run_events_pico(unsigned int events)
{
int lim_x;
if (events & PEV_PICO_SWINP) {
pico_inp_mode++;
if (pico_inp_mode > 2)
pico_inp_mode = 0;
switch (pico_inp_mode) {
case 2: emu_status_msg("Input: Pen on Pad"); break;
case 1: emu_status_msg("Input: Pen on Storyware"); break;
case 0: emu_status_msg("Input: Joystick");
PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;
break;
}
}
if (events & PEV_PICO_PPREV) {
PicoPicohw.page--;
if (PicoPicohw.page < 0)
PicoPicohw.page = 0;
emu_status_msg("Page %i", PicoPicohw.page);
}
if (events & PEV_PICO_PNEXT) {
PicoPicohw.page++;
if (PicoPicohw.page > 6)
PicoPicohw.page = 6;
emu_status_msg("Page %i", PicoPicohw.page);
}
if (pico_inp_mode == 0)
return;
/* handle other input modes */
if (PicoPad[0] & 1) pico_pen_y--;
if (PicoPad[0] & 2) pico_pen_y++;
if (PicoPad[0] & 4) pico_pen_x--;
if (PicoPad[0] & 8) pico_pen_x++;
PicoPad[0] &= ~0x0f; // release UDLR
lim_x = (Pico.video.reg[12]&1) ? 319 : 255;
if (pico_pen_y < 8)
pico_pen_y = 8;
if (pico_pen_y > 224 - PICO_PEN_ADJUST_Y)
pico_pen_y = 224 - PICO_PEN_ADJUST_Y;
if (pico_pen_x < 0)
pico_pen_x = 0;
if (pico_pen_x > lim_x - PICO_PEN_ADJUST_X)
pico_pen_x = lim_x - PICO_PEN_ADJUST_X;
PicoPicohw.pen_pos[0] = pico_pen_x;
if (!(Pico.video.reg[12] & 1))
PicoPicohw.pen_pos[0] += pico_pen_x / 4;
PicoPicohw.pen_pos[0] += 0x3c;
PicoPicohw.pen_pos[1] = pico_inp_mode == 1 ? (0x2f8 + pico_pen_y) : (0x1fc + pico_pen_y);
}
static void do_turbo(int *pad, int acts)
{
static int turbo_pad = 0;
static unsigned char turbo_cnt[3] = { 0, 0, 0 };
int inc = currentConfig.turbo_rate * 2;
if (acts & 0x1000) {
turbo_cnt[0] += inc;
if (turbo_cnt[0] >= 60)
turbo_pad ^= 0x10, turbo_cnt[0] = 0;
}
if (acts & 0x2000) {
turbo_cnt[1] += inc;
if (turbo_cnt[1] >= 60)
turbo_pad ^= 0x20, turbo_cnt[1] = 0;
}
if (acts & 0x4000) {
turbo_cnt[2] += inc;
if (turbo_cnt[2] >= 60)
turbo_pad ^= 0x40, turbo_cnt[2] = 0;
}
*pad |= turbo_pad & (acts >> 8);
}
static void run_events_ui(unsigned int which)
{
if (which & (PEV_STATE_LOAD|PEV_STATE_SAVE))
{
int do_it = 1;
if ( emu_check_save_file(state_slot, NULL) &&
(((which & PEV_STATE_LOAD) && (currentConfig.confirm_save & EOPT_CONFIRM_LOAD)) ||
((which & PEV_STATE_SAVE) && (currentConfig.confirm_save & EOPT_CONFIRM_SAVE))) )
{
const char *nm;
char tmp[64];
int keys, len;
strcpy(tmp, (which & PEV_STATE_LOAD) ? "LOAD STATE?" : "OVERWRITE SAVE?");
len = strlen(tmp);
nm = in_get_key_name(-1, -PBTN_MA3);
snprintf(tmp + len, sizeof(tmp) - len, "(%s=yes, ", nm);
len = strlen(tmp);
nm = in_get_key_name(-1, -PBTN_MBACK);
snprintf(tmp + len, sizeof(tmp) - len, "%s=no)", nm);
plat_status_msg_busy_first(tmp);
in_set_config_int(0, IN_CFG_BLOCKING, 1);
while (in_menu_wait_any(NULL, 50) & (PBTN_MA3|PBTN_MBACK))
;
while ( !((keys = in_menu_wait_any(NULL, 50)) & (PBTN_MA3|PBTN_MBACK)) )
;
if (keys & PBTN_MBACK)
do_it = 0;
while (in_menu_wait_any(NULL, 50) & (PBTN_MA3|PBTN_MBACK))
;
in_set_config_int(0, IN_CFG_BLOCKING, 0);
}
if (do_it) {
plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");
PicoStateProgressCB = plat_status_msg_busy_next;
emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);
PicoStateProgressCB = NULL;
}
}
if (which & PEV_SWITCH_RND)
{
plat_video_toggle_renderer(1, 0);
}
if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))
{
if (which & PEV_SSLOT_PREV) {
state_slot -= 1;
if (state_slot < 0)
state_slot = 9;
} else {
state_slot += 1;
if (state_slot > 9)
state_slot = 0;
}
emu_status_msg("SAVE SLOT %i [%s]", state_slot,
emu_check_save_file(state_slot, NULL) ? "USED" : "FREE");
}
if (which & PEV_MENU)
engineState = PGS_Menu;
}
void emu_update_input(void)
{
static int prev_events = 0;
int actions[IN_BINDTYPE_COUNT] = { 0, };
int pl_actions[2];
int events;
in_update(actions);
pl_actions[0] = actions[IN_BINDTYPE_PLAYER12];
pl_actions[1] = actions[IN_BINDTYPE_PLAYER12] >> 16;
PicoPad[0] = pl_actions[0] & 0xfff;
PicoPad[1] = pl_actions[1] & 0xfff;
if (pl_actions[0] & 0x7000)
do_turbo(&PicoPad[0], pl_actions[0]);
if (pl_actions[1] & 0x7000)
do_turbo(&PicoPad[1], pl_actions[1]);
events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
// volume is treated in special way and triggered every frame
if (events & (PEV_VOL_DOWN|PEV_VOL_UP))
plat_update_volume(1, events & PEV_VOL_UP);
if ((events ^ prev_events) & PEV_FF) {
emu_set_fastforward(events & PEV_FF);
plat_update_volume(0, 0);
reset_timing = 1;
}
events &= ~prev_events;
if (PicoAHW == PAHW_PICO)
run_events_pico(events);
if (events)
run_events_ui(events);
if (movie_data)
update_movie();
prev_events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
}
static void mkdir_path(char *path_with_reserve, int pos, const char *name)
{
strcpy(path_with_reserve + pos, name);
if (plat_is_dir(path_with_reserve))
return;
if (mkdir(path_with_reserve, 0777) < 0)
lprintf("failed to create: %s\n", path_with_reserve);
}
void emu_cmn_forced_frame(int no_scale, int do_emu)
{
int po_old = PicoOpt;
memset32(g_screen_ptr, 0, g_screen_width * g_screen_height * 2 / 4);
PicoOpt &= ~POPT_ALT_RENDERER;
PicoOpt |= POPT_ACC_SPRITES;
if (!no_scale)
PicoOpt |= POPT_EN_SOFTSCALE;
PicoDrawSetOutFormat(PDF_RGB555, 1);
Pico.m.dirtyPal = 1;
if (do_emu)
PicoFrame();
else
PicoFrameDrawOnly();
PicoOpt = po_old;
}
void emu_init(void)
{
char path[512];
int pos;
#if 0
// FIXME: handle through menu, etc
FILE *f;
f = fopen("32X_M_BIOS.BIN", "rb");
p32x_bios_m = malloc(2048);
fread(p32x_bios_m, 1, 2048, f);
fclose(f);
f = fopen("32X_S_BIOS.BIN", "rb");
p32x_bios_s = malloc(1024);
fread(p32x_bios_s, 1, 1024, f);
fclose(f);
#endif
/* make dirs for saves */
pos = plat_get_root_dir(path, sizeof(path) - 4);
mkdir_path(path, pos, "mds");
mkdir_path(path, pos, "srm");
mkdir_path(path, pos, "brm");
pprof_init();
make_config_cfg(path);
config_readlrom(path);
PicoInit();
PicoMessage = plat_status_msg_busy_next;
PicoMCDopenTray = emu_tray_open;
PicoMCDcloseTray = emu_tray_close;
}
void emu_finish(void)
{
// save SRAM
if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {
emu_save_load_game(0, 1);
SRam.changed = 0;
}
if (!(currentConfig.EmuOpt & EOPT_NO_AUTOSVCFG)) {
char cfg[512];
make_config_cfg(cfg);
config_writelrom(cfg);
#ifndef NO_SYNC
sync();
#endif
}
pprof_finish();
PicoExit();
}
static void skip_frame(int do_audio)
{
PicoSkipFrame = do_audio ? 1 : 2;
PicoFrame();
PicoSkipFrame = 0;
}
/* our tick here is 1 us right now */
#define ms_to_ticks(x) (unsigned int)(x * 1000)
#define get_ticks() plat_get_ticks_us()
void emu_loop(void)
{
int pframes_done; /* "period" frames, used for sync */
int frames_done, frames_shown; /* actual frames for fps counter */
int target_fps, target_frametime;
unsigned int timestamp_base = 0, timestamp_fps;
char *notice_msg = NULL;
char fpsbuff[24];
int i;
fpsbuff[0] = 0;
/* make sure we are in correct mode */
Pico.m.dirtyPal = 1;
rendstatus_old = -1;
PicoLoopPrepare();
// prepare CD buffer
if (PicoAHW & PAHW_MCD)
PicoCDBufferInit();
pemu_loop_prep();
/* number of ticks per frame */
if (Pico.m.pal) {
target_fps = 50;
target_frametime = ms_to_ticks(1000) / 50;
} else {
target_fps = 60;
target_frametime = ms_to_ticks(1000) / 60 + 1;
}
timestamp_fps = get_ticks();
reset_timing = 1;
frames_done = frames_shown = pframes_done = 0;
plat_video_wait_vsync();
/* loop with resync every 1 sec. */
while (engineState == PGS_Running)
{
unsigned int timestamp;
int diff, diff_lim;
pprof_start(main);
timestamp = get_ticks();
if (reset_timing) {
reset_timing = 0;
timestamp_base = timestamp;
pframes_done = 0;
}
// show notice_msg message?
if (notice_msg_time != 0)
{
static int noticeMsgSum;
if (timestamp - ms_to_ticks(notice_msg_time) > ms_to_ticks(STATUS_MSG_TIMEOUT)) {
notice_msg_time = 0;
plat_status_msg_clear();
notice_msg = NULL;
} else {
int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];
if (sum != noticeMsgSum) {
plat_status_msg_clear();
noticeMsgSum = sum;
}
notice_msg = noticeMsg;
}
}
// second changed?
if (timestamp - timestamp_fps >= ms_to_ticks(1000))
{
#ifdef BENCHMARK
static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];
if (++bench == 10) {
bench = 0;
bench_fps_s = bench_fps;
bf[bfp++ & 3] = bench_fps;
bench_fps = 0;
}
bench_fps += frames_shown;
sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);
printf("%s\n", fpsbuff);
#else
if (currentConfig.EmuOpt & EOPT_SHOW_FPS) {
sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);
if (fpsbuff[5] == 0) { fpsbuff[5] = fpsbuff[6] = ' '; fpsbuff[7] = 0; }
}
#endif
frames_shown = frames_done = 0;
timestamp_fps += ms_to_ticks(1000);
}
#ifdef PFRAMES
sprintf(fpsbuff, "%i", Pico.m.frame_count);
#endif
if (timestamp - timestamp_base >= ms_to_ticks(1000))
{
if ((currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) && currentConfig.Frameskip >= 0)
pframes_done = 0;
else
pframes_done -= target_fps;
if (pframes_done < -2) {
/* don't drag more than 2 frames behind */
pframes_done = -2;
timestamp_base = timestamp - 2 * target_frametime;
}
else
timestamp_base += ms_to_ticks(1000);
}
diff = timestamp - timestamp_base;
diff_lim = (pframes_done + 1) * target_frametime;
if (currentConfig.Frameskip >= 0) // frameskip enabled
{
for (i = 0; i < currentConfig.Frameskip; i++) {
emu_update_input();
skip_frame(1);
pframes_done++; frames_done++;
diff_lim += target_frametime;
if (!(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT))) {
timestamp = get_ticks();
diff = timestamp - timestamp_base;
if (!reset_timing && diff < diff_lim) // we are too fast
plat_wait_till_us(timestamp_base + diff_lim);
}
}
}
else if (diff > diff_lim)
{
/* no time left for this frame - skip */
/* limit auto frameskip to 8 */
if (frames_done / 8 <= frames_shown) {
emu_update_input();
skip_frame(diff < diff_lim + target_frametime * 16);
pframes_done++; frames_done++;
continue;
}
}
emu_update_input();
PicoFrame();
pemu_finalize_frame(fpsbuff, notice_msg);
// plat_video_flip();
/* frame limiter */
if (!reset_timing && !(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT)))
{
timestamp = get_ticks();
diff = timestamp - timestamp_base;
// sleep or vsync if we are still too fast
if (diff < diff_lim)
{
// we are too fast
plat_wait_till_us(timestamp_base + diff_lim - target_frametime / 4);
if (currentConfig.EmuOpt & EOPT_VSYNC)
plat_video_wait_vsync();
}
}
// XXX: for some plats it might be better to flip before vsync
// (due to shadow registers in display hw)
plat_video_flip();
pframes_done++; frames_done++; frames_shown++;
pprof_end(main);
}
emu_set_fastforward(0);
// save SRAM
if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {
plat_status_msg_busy_first("Writing SRAM/BRAM...");
emu_save_load_game(0, 1);
SRam.changed = 0;
}
pemu_loop_end();
// pemu_loop_end() might want to do 1 frame for bg image,
// so free CD buffer here
if (PicoAHW & PAHW_MCD)
PicoCDBufferFree();
}