mirror of
https://github.com/libretro/beetle-gba-libretro.git
synced 2024-11-24 00:39:50 +00:00
4285 lines
96 KiB
C++
4285 lines
96 KiB
C++
#include "mednafen/mednafen.h"
|
|
#include "mednafen/mempatcher.h"
|
|
#include "mednafen/git.h"
|
|
#include "mednafen/general.h"
|
|
#include "mednafen/md5.h"
|
|
#ifdef NEED_DEINTERLACER
|
|
#include "mednafen/video/Deinterlacer.h"
|
|
#endif
|
|
#include "libretro.h"
|
|
|
|
static MDFNGI *game;
|
|
|
|
struct retro_perf_callback perf_cb;
|
|
retro_get_cpu_features_t perf_get_cpu_features_cb = NULL;
|
|
retro_log_printf_t log_cb;
|
|
static retro_video_refresh_t video_cb;
|
|
static retro_audio_sample_t audio_cb;
|
|
static retro_audio_sample_batch_t audio_batch_cb;
|
|
static retro_environment_t environ_cb;
|
|
static retro_input_poll_t input_poll_cb;
|
|
static retro_input_state_t input_state_cb;
|
|
|
|
static retro_rumble_interface rumble;
|
|
|
|
static bool overscan;
|
|
static double last_sound_rate;
|
|
static MDFN_PixelFormat last_pixel_format;
|
|
|
|
static MDFN_Surface *surf;
|
|
|
|
static bool failed_init;
|
|
|
|
static void hookup_ports(bool force);
|
|
|
|
static bool initial_ports_hookup = false;
|
|
|
|
std::string retro_base_directory;
|
|
std::string retro_base_name;
|
|
std::string retro_save_directory;
|
|
|
|
// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
|
|
// Copyright (C) 1999-2003 Forgotten
|
|
// Copyright (C) 2005 Forgotten and the VBA development team
|
|
|
|
// 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 2, 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, write to the Free Software Foundation,
|
|
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "mednafen/gba/GBA.h"
|
|
#include "mednafen/general.h"
|
|
#include "mednafen/file.h"
|
|
#include "mednafen/state.h"
|
|
#include "mednafen/mempatcher.h"
|
|
#include "mednafen/md5.h"
|
|
#include "mednafen/gba/GBAinline.h"
|
|
#include "mednafen/gba/Globals.h"
|
|
#include "mednafen/gba/Gfx.h"
|
|
#include "mednafen/gba/eeprom.h"
|
|
#include "mednafen/gba/flash.h"
|
|
#include "mednafen/gba/Sound.h"
|
|
#include "mednafen/gba/sram.h"
|
|
#include "mednafen/gba/bios.h"
|
|
#include "mednafen/gba/Port.h"
|
|
|
|
#include "mednafen/gba/arm.h"
|
|
#include "mednafen/gba/thumb.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "mednafen/FileStream.h"
|
|
|
|
#include "scrc32.h"
|
|
|
|
static bool CPUInit(const std::string bios_fn) MDFN_COLD;
|
|
static void CPUReset(void) MDFN_COLD;
|
|
static void CPUUpdateRender(void);
|
|
|
|
#define UPDATE_REG(address, value)\
|
|
{\
|
|
WRITE16LE(((uint16 *)&ioMem[address]),value);\
|
|
}\
|
|
|
|
#ifdef __GNUC__
|
|
#define _stricmp strcasecmp
|
|
#endif
|
|
|
|
RTC *GBA_RTC = NULL;
|
|
|
|
int SWITicks = 0;
|
|
int IRQTicks = 0;
|
|
|
|
int layerEnableDelay = 0;
|
|
bool busPrefetch = false;
|
|
bool busPrefetchEnable = false;
|
|
uint32 busPrefetchCount = 0;
|
|
int cpuDmaTicksToUpdate = 0;
|
|
int cpuDmaCount = 0;
|
|
bool cpuDmaHack = false;
|
|
uint32 cpuDmaLast = 0;
|
|
int dummyAddress = 0;
|
|
|
|
bool cpuBreakLoop = false;
|
|
int cpuNextEvent = 0;
|
|
|
|
static bool intState = false;
|
|
static bool stopState = false;
|
|
static bool holdState = false;
|
|
static int holdType = 0;
|
|
|
|
static bool FlashSizeSet; // Set to TRUE if explicitly set by the user
|
|
bool cpuSramEnabled;
|
|
bool cpuFlashEnabled;
|
|
bool cpuEEPROMEnabled;
|
|
bool cpuEEPROMSensorEnabled;
|
|
|
|
uint32 cpuPrefetch[2];
|
|
|
|
int cpuTotalTicks = 0;
|
|
static int lcdTicks;
|
|
uint8 timerOnOffDelay = 0;
|
|
|
|
GBATimer timers[4];
|
|
|
|
uint32 dmaSource[4] = {0};
|
|
uint32 dmaDest[4] = {0};
|
|
void (*renderLine)() = mode0RenderLine;
|
|
bool fxOn = false;
|
|
bool windowOn = false;
|
|
|
|
static const int TIMER_TICKS[4] =
|
|
{
|
|
0,
|
|
6,
|
|
8,
|
|
10
|
|
};
|
|
|
|
const uint32 objTilesAddress [3] = {0x010000, 0x014000, 0x014000};
|
|
const uint8 gamepakRamWaitState[4] = { 4, 3, 2, 8 };
|
|
const uint8 gamepakWaitState[4] = { 4, 3, 2, 8 };
|
|
const uint8 gamepakWaitState0[2] = { 2, 1 };
|
|
const uint8 gamepakWaitState1[2] = { 4, 1 };
|
|
const uint8 gamepakWaitState2[2] = { 8, 1 };
|
|
const bool isInRom [16]=
|
|
{ false, false, false, false, false, false, false, false,
|
|
true, true, true, true, true, true, false, false };
|
|
|
|
uint8 memoryWait[16] =
|
|
{ 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0 };
|
|
uint8 memoryWait32[16] =
|
|
{ 0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 4, 0 };
|
|
uint8 memoryWaitSeq[16] =
|
|
{ 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4, 0 };
|
|
uint8 memoryWaitSeq32[16] =
|
|
{ 0, 0, 5, 0, 0, 1, 1, 0, 5, 5, 9, 9, 17, 17, 4, 0 };
|
|
|
|
// The videoMemoryWait constants are used to add some waitstates
|
|
// if the opcode access video memory data outside of vblank/hblank
|
|
// It seems to happen on only one ticks for each pixel.
|
|
// Not used for now (too problematic with current code).
|
|
//const uint8 videoMemoryWait[16] =
|
|
// {0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
|
|
uint8 biosProtected[4];
|
|
|
|
static const uint32 myROM[] =
|
|
{
|
|
#include "mednafen/gba/myrom.h"
|
|
};
|
|
|
|
union SysCM
|
|
{
|
|
uint32 v32[65536];
|
|
uint16 v16[65536];
|
|
};
|
|
static SysCM* systemColorMap = NULL;
|
|
static uint8 *CustomColorMap = NULL; // 32768 * 3
|
|
static int romSize = 0x2000000;
|
|
|
|
static INLINE int CPUUpdateTicks()
|
|
{
|
|
int cpuLoopTicks = lcdTicks;
|
|
|
|
if(timers[0].On && (timers[0].Ticks < cpuLoopTicks)) {
|
|
cpuLoopTicks = timers[0].Ticks;
|
|
}
|
|
if(timers[1].On && !(timers[1].CNT & 4) && (timers[1].Ticks < cpuLoopTicks)) {
|
|
cpuLoopTicks = timers[1].Ticks;
|
|
}
|
|
if(timers[2].On && !(timers[2].CNT & 4) && (timers[2].Ticks < cpuLoopTicks)) {
|
|
cpuLoopTicks = timers[2].Ticks;
|
|
}
|
|
if(timers[3].On && !(timers[3].CNT & 4) && (timers[3].Ticks < cpuLoopTicks)) {
|
|
cpuLoopTicks = timers[3].Ticks;
|
|
}
|
|
|
|
if (SWITicks) {
|
|
if (SWITicks < cpuLoopTicks)
|
|
cpuLoopTicks = SWITicks;
|
|
}
|
|
|
|
if (IRQTicks) {
|
|
if (IRQTicks < cpuLoopTicks)
|
|
cpuLoopTicks = IRQTicks;
|
|
}
|
|
return cpuLoopTicks;
|
|
}
|
|
|
|
void CPUUpdateWindow0()
|
|
{
|
|
int x00 = WIN0H>>8;
|
|
int x01 = WIN0H & 255;
|
|
|
|
if(x00 <= x01) {
|
|
for(int i = 0; i < 240; i++) {
|
|
gfxInWin0[i] = (i >= x00 && i < x01);
|
|
}
|
|
} else {
|
|
for(int i = 0; i < 240; i++) {
|
|
gfxInWin0[i] = (i >= x00 || i < x01);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPUUpdateWindow1()
|
|
{
|
|
int x00 = WIN1H>>8;
|
|
int x01 = WIN1H & 255;
|
|
|
|
if(x00 <= x01) {
|
|
for(int i = 0; i < 240; i++) {
|
|
gfxInWin1[i] = (i >= x00 && i < x01);
|
|
}
|
|
} else {
|
|
for(int i = 0; i < 240; i++) {
|
|
gfxInWin1[i] = (i >= x00 || i < x01);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CLEAR_ARRAY(a) \
|
|
{\
|
|
uint32 *array = (a);\
|
|
for(int i = 0; i < 240; i++) {\
|
|
*array++ = 0x80000000;\
|
|
}\
|
|
}\
|
|
|
|
void CPUUpdateRenderBuffers(bool force)
|
|
{
|
|
if(!(layerEnable & 0x0100) || force) {
|
|
CLEAR_ARRAY(line0);
|
|
}
|
|
if(!(layerEnable & 0x0200) || force) {
|
|
CLEAR_ARRAY(line1);
|
|
}
|
|
if(!(layerEnable & 0x0400) || force) {
|
|
CLEAR_ARRAY(line2);
|
|
}
|
|
if(!(layerEnable & 0x0800) || force) {
|
|
CLEAR_ARRAY(line3);
|
|
}
|
|
}
|
|
|
|
static uint16 padbufblah;
|
|
static SFORMAT Joy_StateRegs[] =
|
|
{
|
|
SFVAR( padbufblah),
|
|
SFEND
|
|
};
|
|
|
|
int StateAction(StateMem *sm, int load, int data_only)
|
|
{
|
|
int ret = 1;
|
|
|
|
SFORMAT StateRegs[] =
|
|
{
|
|
// Type-cast to uint32* so the macro will work(they really are 32-bit elements, just wrapped up in a union)
|
|
SFARRAY32N((uint32 *)reg, sizeof(reg) / sizeof(reg_pair), "reg"),
|
|
|
|
SFVAR(busPrefetch),
|
|
SFVAR(busPrefetchEnable),
|
|
SFVAR(busPrefetchCount),
|
|
SFVAR(cpuDmaHack),
|
|
SFVAR(cpuDmaLast),
|
|
SFVAR(cpuDmaTicksToUpdate),
|
|
SFVAR(cpuDmaCount),
|
|
SFVAR(stopState),
|
|
SFVAR(intState),
|
|
SFVAR(DISPCNT),
|
|
SFVAR(DISPSTAT),
|
|
SFVAR(VCOUNT),
|
|
SFVAR(BG0CNT),
|
|
SFVAR(BG1CNT),
|
|
SFVAR(BG2CNT),
|
|
SFVAR(BG3CNT),
|
|
|
|
|
|
SFARRAY16(BGHOFS, 4),
|
|
SFARRAY16(BGVOFS, 4),
|
|
|
|
SFVAR(BG2PA),
|
|
SFVAR(BG2PB),
|
|
SFVAR(BG2PC),
|
|
SFVAR(BG2PD),
|
|
SFVAR(BG2X_L),
|
|
SFVAR(BG2X_H),
|
|
SFVAR(BG2Y_L),
|
|
SFVAR(BG2Y_H),
|
|
|
|
SFVAR(BG3PA),
|
|
SFVAR(BG3PB),
|
|
SFVAR(BG3PC),
|
|
SFVAR(BG3PD),
|
|
SFVAR(BG3X_L),
|
|
SFVAR(BG3X_H),
|
|
SFVAR(BG3Y_L),
|
|
SFVAR(BG3Y_H),
|
|
SFVAR(WIN0H),
|
|
SFVAR(WIN1H),
|
|
SFVAR(WIN0V),
|
|
SFVAR(WIN1V),
|
|
SFVAR(WININ),
|
|
SFVAR(WINOUT),
|
|
SFVAR(MOSAIC),
|
|
SFVAR(BLDMOD),
|
|
SFVAR(COLEV),
|
|
SFVAR(COLY),
|
|
|
|
SFARRAY16(DMSAD_L, 4),
|
|
SFARRAY16(DMSAD_H, 4),
|
|
SFARRAY16(DMDAD_L, 4),
|
|
SFARRAY16(DMDAD_H, 4),
|
|
SFARRAY16(DMCNT_L, 4),
|
|
SFARRAY16(DMCNT_H, 4),
|
|
|
|
SFVAR(timers[0].D),
|
|
SFVAR(timers[0].CNT),
|
|
SFVAR(timers[1].D),
|
|
SFVAR(timers[1].CNT),
|
|
SFVAR(timers[2].D),
|
|
SFVAR(timers[2].CNT),
|
|
SFVAR(timers[3].D),
|
|
SFVAR(timers[3].CNT),
|
|
|
|
SFVAR(P1),
|
|
SFVAR(IE),
|
|
SFVAR(IF),
|
|
SFVAR(IME),
|
|
|
|
SFVAR(holdState),
|
|
SFVAR(holdType),
|
|
SFVAR(lcdTicks),
|
|
|
|
SFVAR(timers[0].On),
|
|
SFVAR(timers[0].Ticks),
|
|
SFVAR(timers[0].Reload),
|
|
SFVAR(timers[0].ClockReload),
|
|
|
|
SFVAR(timers[1].On),
|
|
SFVAR(timers[1].Ticks),
|
|
SFVAR(timers[1].Reload),
|
|
SFVAR(timers[1].ClockReload),
|
|
|
|
SFVAR(timers[2].On),
|
|
SFVAR(timers[2].Ticks),
|
|
SFVAR(timers[2].Reload),
|
|
SFVAR(timers[2].ClockReload),
|
|
|
|
SFVAR(timers[3].On),
|
|
SFVAR(timers[3].Ticks),
|
|
SFVAR(timers[3].Reload),
|
|
SFVAR(timers[3].ClockReload),
|
|
|
|
SFARRAY32(dmaSource, 4),
|
|
SFARRAY32(dmaDest, 4),
|
|
|
|
SFVAR(fxOn),
|
|
SFVAR(windowOn),
|
|
|
|
SFVAR(N_FLAG),
|
|
SFVAR(C_FLAG),
|
|
SFVAR(Z_FLAG),
|
|
SFVAR(V_FLAG),
|
|
SFVAR(armState),
|
|
SFVAR(armIrqEnable),
|
|
SFVAR(armNextPC),
|
|
SFVAR(armMode),
|
|
|
|
SFVAR(cpuSramEnabled),
|
|
SFVAR(cpuFlashEnabled),
|
|
SFVAR(cpuEEPROMEnabled),
|
|
SFVAR(cpuEEPROMSensorEnabled),
|
|
|
|
SFEND
|
|
};
|
|
|
|
ret &= MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");
|
|
|
|
SFORMAT RAMState[] =
|
|
{
|
|
SFARRAY(internalRAM, 0x8000),
|
|
SFARRAY(paletteRAM, 0x400),
|
|
SFARRAY(workRAM, 0x40000),
|
|
SFARRAY(vram, 0x20000),
|
|
SFARRAY(oam, 0x400),
|
|
SFARRAY(ioMem, 0x400),
|
|
SFEND
|
|
};
|
|
|
|
ret &= MDFNSS_StateAction(sm, load, data_only, RAMState, "RAM");
|
|
|
|
if(cpuEEPROMEnabled)
|
|
ret &= EEPROM_StateAction(sm, load, data_only);
|
|
|
|
ret &= GBA_Flash_StateAction(sm, load, data_only);
|
|
|
|
if(GBA_RTC)
|
|
ret &= GBA_RTC->StateAction(sm, load, data_only);
|
|
|
|
ret &= MDFNSS_StateAction(sm, load, data_only, Joy_StateRegs, "JOY");
|
|
ret &= MDFNGBASOUND_StateAction(sm, load, data_only);
|
|
|
|
if(load)
|
|
{
|
|
// set pointers!
|
|
layerEnable = layerSettings & DISPCNT;
|
|
|
|
CPUUpdateRender();
|
|
CPUUpdateRenderBuffers(true);
|
|
CPUUpdateWindow0();
|
|
CPUUpdateWindow1();
|
|
|
|
if(armState) {
|
|
ARM_PREFETCH;
|
|
} else {
|
|
THUMB_PREFETCH;
|
|
}
|
|
CPUUpdateRegister(0x204, CPUReadHalfWordQuick(0x4000204));
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
static bool CPUWriteBatteryFile(const char *filename) MDFN_COLD;
|
|
static bool CPUWriteBatteryFile(const char *filename)
|
|
{
|
|
if(cpuSramEnabled || cpuFlashEnabled)
|
|
{
|
|
if(cpuSramEnabled)
|
|
{
|
|
if(!MDFN_DumpToFile(filename, 6, flashSaveMemory, 0x10000))
|
|
return(0);
|
|
}
|
|
else if(cpuFlashEnabled)
|
|
{
|
|
if(!MDFN_DumpToFile(filename, 6, flashSaveMemory, flashSize))
|
|
return(0);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
static bool CPUReadBatteryFile(const char *filename) MDFN_COLD;
|
|
static bool CPUReadBatteryFile(const char *filename)
|
|
{
|
|
FILE *gp = gzopen(filename, "rb");
|
|
long size;
|
|
|
|
if(!gp)
|
|
return(FALSE);
|
|
|
|
size = 0;
|
|
while(gzgetc(gp) != EOF)
|
|
size++;
|
|
|
|
if(size == 512 || size == 0x2000) // Load the file as EEPROM for Mednafen < 0.8.2
|
|
{
|
|
gzclose(gp);
|
|
|
|
if(cpuEEPROMEnabled)
|
|
{
|
|
puts("note: Loading sav file as eeprom data");
|
|
GBA_EEPROM_LoadFile(filename);
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
gzseek(gp, 0, SEEK_SET);
|
|
|
|
if(size == 0x20000)
|
|
{
|
|
if(gzread(gp, flashSaveMemory, 0x20000) != 0x20000)
|
|
{
|
|
gzclose(gp);
|
|
return(FALSE);
|
|
}
|
|
if(!FlashSizeSet)
|
|
{
|
|
flashSetSize(0x20000);
|
|
FlashSizeSet = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(gzread(gp, flashSaveMemory, 0x10000) != 0x10000)
|
|
{
|
|
gzclose(gp);
|
|
return(FALSE);
|
|
}
|
|
if(!FlashSizeSet)
|
|
{
|
|
flashSetSize(0x10000);
|
|
FlashSizeSet = TRUE;
|
|
}
|
|
}
|
|
gzclose(gp);
|
|
return(TRUE);
|
|
}
|
|
|
|
static void CPUCleanUp(void) MDFN_COLD;
|
|
static void CPUCleanUp(void)
|
|
{
|
|
if(rom)
|
|
{
|
|
free(rom);
|
|
rom = NULL;
|
|
}
|
|
|
|
if(vram)
|
|
{
|
|
free(vram);
|
|
vram = NULL;
|
|
}
|
|
|
|
if(paletteRAM)
|
|
{
|
|
free(paletteRAM);
|
|
paletteRAM = NULL;
|
|
}
|
|
|
|
if(internalRAM)
|
|
{
|
|
free(internalRAM);
|
|
internalRAM = NULL;
|
|
}
|
|
|
|
if(workRAM)
|
|
{
|
|
free(workRAM);
|
|
workRAM = NULL;
|
|
}
|
|
|
|
if(bios)
|
|
{
|
|
free(bios);
|
|
bios = NULL;
|
|
}
|
|
|
|
if(pix)
|
|
{
|
|
free(pix);
|
|
pix = NULL;
|
|
}
|
|
|
|
if(oam)
|
|
{
|
|
free(oam);
|
|
oam = NULL;
|
|
}
|
|
|
|
if(ioMem)
|
|
{
|
|
free(ioMem);
|
|
ioMem = NULL;
|
|
}
|
|
|
|
if(systemColorMap)
|
|
{
|
|
free(systemColorMap);
|
|
systemColorMap = NULL;
|
|
}
|
|
|
|
MDFNGBASOUND_Kill();
|
|
|
|
GBA_Flash_Kill();
|
|
|
|
if(GBA_RTC)
|
|
{
|
|
delete GBA_RTC;
|
|
GBA_RTC = NULL;
|
|
}
|
|
}
|
|
|
|
static void CloseGame(void)
|
|
{
|
|
GBA_EEPROM_SaveFile(MDFN_MakeFName(MDFNMKF_SAV, 0, "eep").c_str());
|
|
CPUWriteBatteryFile(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str());
|
|
|
|
// Call CPUCleanUp() to deallocate memory AFTER the backup memory is saved.
|
|
CPUCleanUp();
|
|
}
|
|
|
|
static bool LoadCPalette(const char *syspalname, uint8 **ptr, uint32 num_entries) MDFN_COLD;
|
|
static bool LoadCPalette(const char *syspalname, uint8 **ptr, uint32 num_entries)
|
|
{
|
|
std::string colormap_fn = MDFN_MakeFName(MDFNMKF_PALETTE, 0, syspalname).c_str();
|
|
|
|
MDFN_printf(_("Loading custom palette from \"%s\"...\n"), colormap_fn.c_str());
|
|
MDFN_indent(1);
|
|
|
|
*ptr = NULL;
|
|
try
|
|
{
|
|
FileStream fp(colormap_fn.c_str(), FileStream::MODE_READ);
|
|
|
|
if(!(*ptr = (uint8 *)malloc(num_entries * 3)))
|
|
{
|
|
MDFN_indent(-1);
|
|
return(false);
|
|
}
|
|
|
|
fp.read(*ptr, num_entries * 3);
|
|
}
|
|
catch(MDFN_Error &e)
|
|
{
|
|
if(*ptr)
|
|
{
|
|
free(*ptr);
|
|
*ptr = NULL;
|
|
}
|
|
|
|
MDFN_printf(_("Error: %s\n"), e.what());
|
|
MDFN_indent(-1);
|
|
return(e.GetErrno() == ENOENT); // Return fatal error if it's an error other than the file not being found.
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
if(*ptr)
|
|
{
|
|
free(*ptr);
|
|
*ptr = NULL;
|
|
}
|
|
|
|
MDFN_printf(_("Error: %s\n"), e.what());
|
|
MDFN_indent(-1);
|
|
return(false);
|
|
}
|
|
|
|
MDFN_indent(-1);
|
|
|
|
return(true);
|
|
}
|
|
|
|
static void RedoColorMap(const MDFN_PixelFormat &format) MDFN_COLD;
|
|
static void RedoColorMap(const MDFN_PixelFormat &format)
|
|
{
|
|
for(int x = 0; x < 65536; x++)
|
|
{
|
|
int r, g, b;
|
|
|
|
if(CustomColorMap)
|
|
{
|
|
r = CustomColorMap[(x & 0x7FFF) * 3 + 0];
|
|
g = CustomColorMap[(x & 0x7FFF) * 3 + 1];
|
|
b = CustomColorMap[(x & 0x7FFF) * 3 + 2];
|
|
}
|
|
else
|
|
{
|
|
r = (x & 0x1F);
|
|
g = ((x & 0x3E0) >> 5);
|
|
b = ((x & 0x7C00) >> 10);
|
|
|
|
if(format.bpp == 16)
|
|
{
|
|
r = (r * 255 + 15) / 31;
|
|
g = (g * 255 + 15) / 31;
|
|
b = (b * 255 + 15) / 31;
|
|
}
|
|
else
|
|
{
|
|
r = (r << 3); // | (r >> 2);
|
|
g = (g << 3); // | (g >> 2);
|
|
b = (b << 3); // | (b >> 2);
|
|
}
|
|
}
|
|
|
|
if(format.bpp == 32)
|
|
systemColorMap->v32[x] = format.MakeColor(r, g, b);
|
|
else
|
|
systemColorMap->v16[x] = format.MakeColor(r, g, b);
|
|
}
|
|
|
|
#if 0
|
|
for(unsigned i = 0; i < 32; i++)
|
|
{
|
|
printf("%u %u\n", i, format.MakeColor(0, 0, (i * 255 + 15) / 31));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static bool TestMagic(const char *name, MDFNFILE *fp) MDFN_COLD;
|
|
static bool TestMagic(const char *name, MDFNFILE *fp)
|
|
{
|
|
if(!memcmp(GET_FDATA_PTR(fp), "PSF\x22", 4))
|
|
return(TRUE);
|
|
|
|
if(!strcasecmp(GET_FEXTS_PTR(fp), "gba") || !strcasecmp(GET_FEXTS_PTR(fp), "agb"))
|
|
return(TRUE);
|
|
|
|
if(GET_FSIZE_PTR(fp) >= 192 && !strcasecmp(GET_FEXTS_PTR(fp), "bin"))
|
|
{
|
|
if((GET_FDATA_PTR(fp)[0xb2] == 0x96 && GET_FDATA_PTR(fp)[0xb3] == 0x00) ||
|
|
(GET_FDATA_PTR(fp)[0] == 0x2E && GET_FDATA_PTR(fp)[3] == 0xEA))
|
|
return(TRUE);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
static int Load(const char *name, MDFNFILE *fp) MDFN_COLD;
|
|
static int Load(const char *name, MDFNFILE *fp)
|
|
{
|
|
layerSettings = 0xFF00;
|
|
|
|
if(!(rom = (uint8 *)malloc(0x2000000)))
|
|
return(0);
|
|
|
|
memset(rom, 0xFF, 0x2000000);
|
|
|
|
if(!(workRAM = (uint8 *)calloc(1, 0x40000)))
|
|
{
|
|
free(rom);
|
|
return(0);
|
|
}
|
|
|
|
|
|
{
|
|
uint32 size = GET_FSIZE_PTR(fp);
|
|
uint8 *whereToLoad;
|
|
|
|
if(cpuIsMultiBoot)
|
|
{
|
|
whereToLoad = workRAM;
|
|
if(size > 0x40000)
|
|
size = 0x40000;
|
|
}
|
|
else
|
|
{
|
|
whereToLoad = rom;
|
|
if(size > 0x2000000)
|
|
size = 0x2000000;
|
|
}
|
|
|
|
memcpy(whereToLoad, GET_FDATA_PTR(fp), size);
|
|
|
|
md5_context md5;
|
|
md5.starts();
|
|
md5.update(GET_FDATA_PTR(fp), size);
|
|
md5.finish(MDFNGameInfo->MD5);
|
|
|
|
MDFN_printf(_("ROM: %dKiB\n"), (size + 1023) / 1024);
|
|
MDFN_printf(_("ROM CRC32: 0x%08x\n"), (unsigned int)crc32(0, GET_FDATA_PTR(fp), size));
|
|
MDFN_printf(_("ROM MD5: 0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());
|
|
|
|
uint16 *temp = (uint16 *)(rom+((size+1)&~1));
|
|
int i;
|
|
|
|
for(i = (size+1)&~1; i < 0x2000000; i+=2)
|
|
{
|
|
WRITE16LE(temp, (i >> 1) & 0xFFFF);
|
|
temp++;
|
|
}
|
|
}
|
|
|
|
if(!(bios = (uint8 *)calloc(1, 0x4000)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(internalRAM = (uint8 *)calloc(1, 0x8000)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(paletteRAM = (uint8 *)calloc(1, 0x400)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(vram = (uint8 *)calloc(1, 0x20000)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(oam = (uint8 *)calloc(1, 0x400)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(ioMem = (uint8 *)calloc(1, 0x400)))
|
|
{
|
|
CPUCleanUp();
|
|
return 0;
|
|
}
|
|
|
|
if(!(systemColorMap = (SysCM*)malloc(sizeof(SysCM))))
|
|
{
|
|
CPUCleanUp();
|
|
return(0);
|
|
}
|
|
|
|
CPUUpdateRenderBuffers(true);
|
|
|
|
MDFNGameInfo->GameSetMD5Valid = FALSE;
|
|
|
|
MDFNGBASOUND_Init();
|
|
|
|
{
|
|
MDFNMP_Init(0x8000, (1 << 28) / 0x8000);
|
|
|
|
MDFNMP_AddRAM(0x40000, 0x2 << 24, workRAM);
|
|
MDFNMP_AddRAM(0x08000, 0x3 << 24, internalRAM);
|
|
}
|
|
|
|
if(!CPUInit(MDFN_GetSettingS("gba.bios")))
|
|
{
|
|
CPUCleanUp();
|
|
return(0);
|
|
}
|
|
CPUReset();
|
|
|
|
GBA_Flash_Init();
|
|
eepromInit();
|
|
|
|
{
|
|
// EEPROM might be loaded from within CPUReadBatteryFile for support for Mednafen < 0.8.2, so call before GBA_EEPROM_LoadFile(), which
|
|
// is more specific...kinda.
|
|
if(cpuSramEnabled || cpuFlashEnabled)
|
|
CPUReadBatteryFile(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str());
|
|
|
|
if(cpuEEPROMEnabled)
|
|
GBA_EEPROM_LoadFile(MDFN_MakeFName(MDFNMKF_SAV, 0, "eep").c_str());
|
|
}
|
|
|
|
//if(!LoadCPalette(NULL, &CustomColorMap, 32768))
|
|
//{
|
|
// CPUCleanUp();
|
|
// return(0);
|
|
//}
|
|
|
|
return(1);
|
|
}
|
|
|
|
void doMirroring (bool b)
|
|
{
|
|
uint32 mirroredRomSize = (((romSize)>>20) & 0x3F)<<20;
|
|
uint32 mirroredRomAddress = romSize;
|
|
if ((mirroredRomSize <=0x800000) && (b))
|
|
{
|
|
mirroredRomAddress = mirroredRomSize;
|
|
if (mirroredRomSize==0)
|
|
mirroredRomSize=0x100000;
|
|
while (mirroredRomAddress<0x01000000)
|
|
{
|
|
memcpy ((uint16 *)(rom+mirroredRomAddress), (uint16 *)(rom), mirroredRomSize);
|
|
mirroredRomAddress+=mirroredRomSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CPUUpdateRender(void)
|
|
{
|
|
switch(DISPCNT & 7) {
|
|
case 0:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode0RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode0RenderLineNoWindow;
|
|
else
|
|
renderLine = mode0RenderLineAll;
|
|
break;
|
|
case 1:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode1RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode1RenderLineNoWindow;
|
|
else
|
|
renderLine = mode1RenderLineAll;
|
|
break;
|
|
case 2:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode2RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode2RenderLineNoWindow;
|
|
else
|
|
renderLine = mode2RenderLineAll;
|
|
break;
|
|
case 3:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode3RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode3RenderLineNoWindow;
|
|
else
|
|
renderLine = mode3RenderLineAll;
|
|
break;
|
|
case 4:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode4RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode4RenderLineNoWindow;
|
|
else
|
|
renderLine = mode4RenderLineAll;
|
|
break;
|
|
case 5:
|
|
if((!fxOn && !windowOn && !(layerEnable & 0x8000)) ||
|
|
cpuDisableSfx)
|
|
renderLine = mode5RenderLine;
|
|
else if(fxOn && !windowOn && !(layerEnable & 0x8000))
|
|
renderLine = mode5RenderLineNoWindow;
|
|
else
|
|
renderLine = mode5RenderLineAll;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPUUpdateCPSR()
|
|
{
|
|
uint32 CPSR = reg[16].I & 0x40;
|
|
if(N_FLAG)
|
|
CPSR |= 0x80000000;
|
|
if(Z_FLAG)
|
|
CPSR |= 0x40000000;
|
|
if(C_FLAG)
|
|
CPSR |= 0x20000000;
|
|
if(V_FLAG)
|
|
CPSR |= 0x10000000;
|
|
if(!armState)
|
|
CPSR |= 0x00000020;
|
|
if(!armIrqEnable)
|
|
CPSR |= 0x80;
|
|
CPSR |= (armMode & 0x1F);
|
|
reg[16].I = CPSR;
|
|
}
|
|
|
|
void CPUUpdateFlags(bool breakLoop)
|
|
{
|
|
uint32 CPSR = reg[16].I;
|
|
|
|
N_FLAG = (CPSR & 0x80000000) ? true: false;
|
|
Z_FLAG = (CPSR & 0x40000000) ? true: false;
|
|
C_FLAG = (CPSR & 0x20000000) ? true: false;
|
|
V_FLAG = (CPSR & 0x10000000) ? true: false;
|
|
armState = (CPSR & 0x20) ? false : true;
|
|
armIrqEnable = (CPSR & 0x80) ? false : true;
|
|
if(breakLoop) {
|
|
if (armIrqEnable && (IF & IE) && (IME & 1))
|
|
cpuNextEvent = cpuTotalTicks;
|
|
}
|
|
}
|
|
|
|
void CPUUpdateFlags()
|
|
{
|
|
CPUUpdateFlags(true);
|
|
}
|
|
|
|
#ifdef MSB_FIRST
|
|
static void CPUSwap(volatile uint32 *a, volatile uint32 *b)
|
|
{
|
|
volatile uint32 c = *b;
|
|
*b = *a;
|
|
*a = c;
|
|
}
|
|
#else
|
|
static void CPUSwap(uint32 *a, uint32 *b)
|
|
{
|
|
uint32 c = *b;
|
|
*b = *a;
|
|
*a = c;
|
|
}
|
|
#endif
|
|
|
|
void CPUSwitchMode(int mode, bool saveState, bool breakLoop)
|
|
{
|
|
// if(armMode == mode)
|
|
// return;
|
|
|
|
CPUUpdateCPSR();
|
|
|
|
switch(armMode) {
|
|
case 0x10:
|
|
case 0x1F:
|
|
reg[R13_USR].I = reg[13].I;
|
|
reg[R14_USR].I = reg[14].I;
|
|
reg[17].I = reg[16].I;
|
|
break;
|
|
case 0x11:
|
|
CPUSwap(®[R8_FIQ].I, ®[8].I);
|
|
CPUSwap(®[R9_FIQ].I, ®[9].I);
|
|
CPUSwap(®[R10_FIQ].I, ®[10].I);
|
|
CPUSwap(®[R11_FIQ].I, ®[11].I);
|
|
CPUSwap(®[R12_FIQ].I, ®[12].I);
|
|
reg[R13_FIQ].I = reg[13].I;
|
|
reg[R14_FIQ].I = reg[14].I;
|
|
reg[SPSR_FIQ].I = reg[17].I;
|
|
break;
|
|
case 0x12:
|
|
reg[R13_IRQ].I = reg[13].I;
|
|
reg[R14_IRQ].I = reg[14].I;
|
|
reg[SPSR_IRQ].I = reg[17].I;
|
|
break;
|
|
case 0x13:
|
|
reg[R13_SVC].I = reg[13].I;
|
|
reg[R14_SVC].I = reg[14].I;
|
|
reg[SPSR_SVC].I = reg[17].I;
|
|
break;
|
|
case 0x17:
|
|
reg[R13_ABT].I = reg[13].I;
|
|
reg[R14_ABT].I = reg[14].I;
|
|
reg[SPSR_ABT].I = reg[17].I;
|
|
break;
|
|
case 0x1b:
|
|
reg[R13_UND].I = reg[13].I;
|
|
reg[R14_UND].I = reg[14].I;
|
|
reg[SPSR_UND].I = reg[17].I;
|
|
break;
|
|
}
|
|
|
|
uint32 CPSR = reg[16].I;
|
|
uint32 SPSR = reg[17].I;
|
|
|
|
switch(mode) {
|
|
case 0x10:
|
|
case 0x1F:
|
|
reg[13].I = reg[R13_USR].I;
|
|
reg[14].I = reg[R14_USR].I;
|
|
reg[16].I = SPSR;
|
|
break;
|
|
case 0x11:
|
|
CPUSwap(®[8].I, ®[R8_FIQ].I);
|
|
CPUSwap(®[9].I, ®[R9_FIQ].I);
|
|
CPUSwap(®[10].I, ®[R10_FIQ].I);
|
|
CPUSwap(®[11].I, ®[R11_FIQ].I);
|
|
CPUSwap(®[12].I, ®[R12_FIQ].I);
|
|
reg[13].I = reg[R13_FIQ].I;
|
|
reg[14].I = reg[R14_FIQ].I;
|
|
if(saveState)
|
|
reg[17].I = CPSR;
|
|
else
|
|
reg[17].I = reg[SPSR_FIQ].I;
|
|
break;
|
|
case 0x12:
|
|
reg[13].I = reg[R13_IRQ].I;
|
|
reg[14].I = reg[R14_IRQ].I;
|
|
reg[16].I = SPSR;
|
|
if(saveState)
|
|
reg[17].I = CPSR;
|
|
else
|
|
reg[17].I = reg[SPSR_IRQ].I;
|
|
break;
|
|
case 0x13:
|
|
reg[13].I = reg[R13_SVC].I;
|
|
reg[14].I = reg[R14_SVC].I;
|
|
reg[16].I = SPSR;
|
|
if(saveState)
|
|
reg[17].I = CPSR;
|
|
else
|
|
reg[17].I = reg[SPSR_SVC].I;
|
|
break;
|
|
case 0x17:
|
|
reg[13].I = reg[R13_ABT].I;
|
|
reg[14].I = reg[R14_ABT].I;
|
|
reg[16].I = SPSR;
|
|
if(saveState)
|
|
reg[17].I = CPSR;
|
|
else
|
|
reg[17].I = reg[SPSR_ABT].I;
|
|
break;
|
|
case 0x1b:
|
|
reg[13].I = reg[R13_UND].I;
|
|
reg[14].I = reg[R14_UND].I;
|
|
reg[16].I = SPSR;
|
|
if(saveState)
|
|
reg[17].I = CPSR;
|
|
else
|
|
reg[17].I = reg[SPSR_UND].I;
|
|
break;
|
|
default:
|
|
//systemMessage(MSG_UNSUPPORTED_ARM_MODE, N_("Unsupported ARM mode %02x"), mode);
|
|
break;
|
|
}
|
|
armMode = mode;
|
|
CPUUpdateFlags(breakLoop);
|
|
CPUUpdateCPSR();
|
|
}
|
|
|
|
void CPUSwitchMode(int mode, bool saveState)
|
|
{
|
|
CPUSwitchMode(mode, saveState, true);
|
|
}
|
|
|
|
void CPUUndefinedException()
|
|
{
|
|
uint32 PC = reg[15].I;
|
|
bool savedArmState = armState;
|
|
CPUSwitchMode(0x1b, true, false);
|
|
reg[14].I = PC - (savedArmState ? 4 : 2);
|
|
reg[15].I = 0x04;
|
|
armState = true;
|
|
armIrqEnable = false;
|
|
armNextPC = 0x04;
|
|
ARM_PREFETCH;
|
|
reg[15].I += 4;
|
|
}
|
|
|
|
void CPUSoftwareInterrupt()
|
|
{
|
|
uint32 PC = reg[15].I;
|
|
bool savedArmState = armState;
|
|
CPUSwitchMode(0x13, true, false);
|
|
reg[14].I = PC - (savedArmState ? 4 : 2);
|
|
reg[15].I = 0x08;
|
|
armState = true;
|
|
armIrqEnable = false;
|
|
armNextPC = 0x08;
|
|
ARM_PREFETCH;
|
|
reg[15].I += 4;
|
|
}
|
|
|
|
void CPUSoftwareInterrupt(int comment)
|
|
{
|
|
static bool disableMessage = false;
|
|
if(armState) comment >>= 16;
|
|
if(comment == 0xfa) {
|
|
return;
|
|
}
|
|
if(useBios) {
|
|
CPUSoftwareInterrupt();
|
|
return;
|
|
}
|
|
// This would be correct, but it causes problems if uncommented
|
|
// else {
|
|
// biosProtected = 0xe3a02004;
|
|
// }
|
|
|
|
switch(comment) {
|
|
case 0x00:
|
|
BIOS_SoftReset();
|
|
ARM_PREFETCH;
|
|
break;
|
|
case 0x01:
|
|
BIOS_RegisterRamReset();
|
|
break;
|
|
case 0x02:
|
|
holdState = true;
|
|
holdType = -1;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x03:
|
|
holdState = true;
|
|
holdType = -1;
|
|
stopState = true;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x04:
|
|
CPUSoftwareInterrupt();
|
|
break;
|
|
case 0x05:
|
|
CPUSoftwareInterrupt();
|
|
break;
|
|
case 0x06:
|
|
CPUSoftwareInterrupt();
|
|
break;
|
|
case 0x07:
|
|
CPUSoftwareInterrupt();
|
|
break;
|
|
case 0x08:
|
|
BIOS_Sqrt();
|
|
break;
|
|
case 0x09:
|
|
BIOS_ArcTan();
|
|
break;
|
|
case 0x0A:
|
|
BIOS_ArcTan2();
|
|
break;
|
|
case 0x0B:
|
|
{
|
|
int len = (reg[2].I & 0x1FFFFF) >>1;
|
|
if (!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + len) & 0xe000000) == 0))
|
|
{
|
|
if ((reg[2].I >> 24) & 1)
|
|
{
|
|
if ((reg[2].I >> 26) & 1)
|
|
SWITicks = (7 + memoryWait32[(reg[1].I>>24) & 0xF]) * (len>>1);
|
|
else
|
|
SWITicks = (8 + memoryWait[(reg[1].I>>24) & 0xF]) * (len);
|
|
}
|
|
else
|
|
{
|
|
if ((reg[2].I >> 26) & 1)
|
|
SWITicks = (10 + memoryWait32[(reg[0].I>>24) & 0xF] +
|
|
memoryWait32[(reg[1].I>>24) & 0xF]) * (len>>1);
|
|
else
|
|
SWITicks = (11 + memoryWait[(reg[0].I>>24) & 0xF] +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
}
|
|
}
|
|
BIOS_CpuSet();
|
|
break;
|
|
case 0x0C:
|
|
{
|
|
int len = (reg[2].I & 0x1FFFFF) >>5;
|
|
if (!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + len) & 0xe000000) == 0))
|
|
{
|
|
if ((reg[2].I >> 24) & 1)
|
|
SWITicks = (6 + memoryWait32[(reg[1].I>>24) & 0xF] +
|
|
7 * (memoryWaitSeq32[(reg[1].I>>24) & 0xF] + 1)) * len;
|
|
else
|
|
SWITicks = (9 + memoryWait32[(reg[0].I>>24) & 0xF] +
|
|
memoryWait32[(reg[1].I>>24) & 0xF] +
|
|
7 * (memoryWaitSeq32[(reg[0].I>>24) & 0xF] +
|
|
memoryWaitSeq32[(reg[1].I>>24) & 0xF] + 2)) * len;
|
|
}
|
|
}
|
|
BIOS_CpuFastSet();
|
|
break;
|
|
case 0x0E:
|
|
BIOS_BgAffineSet();
|
|
break;
|
|
case 0x0F:
|
|
BIOS_ObjAffineSet();
|
|
break;
|
|
case 0x10:
|
|
{
|
|
int len = CPUReadHalfWord(reg[2].I);
|
|
if (!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + len) & 0xe000000) == 0))
|
|
SWITicks = (32 + memoryWait[(reg[0].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_BitUnPack();
|
|
break;
|
|
case 0x11:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 8;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (9 + memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_LZ77UnCompWram();
|
|
break;
|
|
case 0x12:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 8;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (19 + memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_LZ77UnCompVram();
|
|
break;
|
|
case 0x13:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 8;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (29 + (memoryWait[(reg[0].I>>24) & 0xF]<<1)) * len;
|
|
}
|
|
BIOS_HuffUnComp();
|
|
break;
|
|
case 0x14:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 8;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (11 + memoryWait[(reg[0].I>>24) & 0xF] +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_RLUnCompWram();
|
|
break;
|
|
case 0x15:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 9;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (34 + (memoryWait[(reg[0].I>>24) & 0xF] << 1) +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_RLUnCompVram();
|
|
break;
|
|
case 0x16:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 8;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (13 + memoryWait[(reg[0].I>>24) & 0xF] +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_Diff8bitUnFilterWram();
|
|
break;
|
|
case 0x17:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 9;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (39 + (memoryWait[(reg[0].I>>24) & 0xF]<<1) +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_Diff8bitUnFilterVram();
|
|
break;
|
|
case 0x18:
|
|
{
|
|
uint32 len = CPUReadMemory(reg[0].I) >> 9;
|
|
if(!(((reg[0].I & 0xe000000) == 0) ||
|
|
((reg[0].I + (len & 0x1fffff)) & 0xe000000) == 0))
|
|
SWITicks = (13 + memoryWait[(reg[0].I>>24) & 0xF] +
|
|
memoryWait[(reg[1].I>>24) & 0xF]) * len;
|
|
}
|
|
BIOS_Diff16bitUnFilter();
|
|
break;
|
|
case 0x19:
|
|
//if(reg[0].I)
|
|
// systemSoundPause();
|
|
//else
|
|
// systemSoundResume();
|
|
break;
|
|
case 0x1F:
|
|
BIOS_MidiKey2Freq();
|
|
break;
|
|
case 0x2A:
|
|
BIOS_SndDriverJmpTableCopy();
|
|
// let it go, because we don't really emulate this function
|
|
default:
|
|
if(!disableMessage) {
|
|
MDFN_PrintError(_("Unsupported BIOS function %02x called from %08x. A BIOS file is needed in order to get correct behaviour."),
|
|
comment,
|
|
armMode ? armNextPC - 4: armNextPC - 2);
|
|
disableMessage = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void CPUCompareVCOUNT()
|
|
{
|
|
if(VCOUNT == (DISPSTAT >> 8)) {
|
|
DISPSTAT |= 4;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
|
|
if(DISPSTAT & 0x20) {
|
|
IF |= 4;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
} else {
|
|
DISPSTAT &= 0xFFFB;
|
|
UPDATE_REG(0x4, DISPSTAT);
|
|
}
|
|
if (layerEnableDelay>0)
|
|
{
|
|
layerEnableDelay--;
|
|
if (layerEnableDelay==1)
|
|
layerEnable = layerSettings & DISPCNT;
|
|
}
|
|
|
|
}
|
|
|
|
#define doDMA(s, d, _si, _di, _c, _transfer32) \
|
|
{ \
|
|
uint32 si = _si; \
|
|
uint32 di = _di; \
|
|
uint32 c = _c; \
|
|
int sm = s >> 24; \
|
|
int dm = d >> 24; \
|
|
int sw = 0; \
|
|
int dw = 0; \
|
|
int sc = c; \
|
|
cpuDmaCount = c; \
|
|
if (sm>15) \
|
|
sm=15; \
|
|
if (dm>15) \
|
|
dm=15; \
|
|
if(_transfer32) { \
|
|
s &= 0xFFFFFFFC; \
|
|
if(s < 0x02000000 && (reg[15].I >> 24)) { \
|
|
while(c != 0) { \
|
|
CPUWriteMemory(d, 0); \
|
|
d += di; \
|
|
c--; \
|
|
} \
|
|
} else { \
|
|
while(c != 0) { \
|
|
cpuDmaLast = CPUReadMemory(s); \
|
|
CPUWriteMemory(d, cpuDmaLast); \
|
|
d += di; \
|
|
s += si; \
|
|
c--; \
|
|
} \
|
|
} \
|
|
} else { \
|
|
s &= 0xFFFFFFFE; \
|
|
si = (int)si >> 1; \
|
|
di = (int)di >> 1; \
|
|
if(s < 0x02000000 && (reg[15].I >> 24)) { \
|
|
while(c != 0) { \
|
|
CPUWriteHalfWord(d, 0); \
|
|
d += di; \
|
|
c--; \
|
|
} \
|
|
} else { \
|
|
while(c != 0) { \
|
|
cpuDmaLast = CPUReadHalfWord(s); \
|
|
CPUWriteHalfWord(d, cpuDmaLast); \
|
|
cpuDmaLast |= (cpuDmaLast<<16); \
|
|
d += di; \
|
|
s += si; \
|
|
c--; \
|
|
} \
|
|
} \
|
|
} \
|
|
cpuDmaCount = 0; \
|
|
int totalTicks = 0; \
|
|
if(_transfer32) { \
|
|
sw =1+memoryWaitSeq32[sm & 15]; \
|
|
dw =1+memoryWaitSeq32[dm & 15]; \
|
|
totalTicks = (sw+dw)*(sc-1) + 6 + memoryWait32[sm & 15] + \
|
|
memoryWaitSeq32[dm & 15]; \
|
|
} \
|
|
else \
|
|
{ \
|
|
sw = 1+memoryWaitSeq[sm & 15]; \
|
|
dw = 1+memoryWaitSeq[dm & 15]; \
|
|
totalTicks = (sw+dw)*(sc-1) + 6 + memoryWait[sm & 15] + \
|
|
memoryWaitSeq[dm & 15]; \
|
|
} \
|
|
cpuDmaTicksToUpdate += totalTicks; \
|
|
}
|
|
|
|
void CPUCheckDMA(int reason, int dmamask)
|
|
{
|
|
// DMA 0
|
|
if((DMCNT_H[0] & 0x8000) && (dmamask & 1)) {
|
|
if(((DMCNT_H[0] >> 12) & 3) == reason) {
|
|
uint32 sourceIncrement = 4;
|
|
uint32 destIncrement = 4;
|
|
switch((DMCNT_H[0] >> 7) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
sourceIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
sourceIncrement = 0;
|
|
break;
|
|
}
|
|
switch((DMCNT_H[0] >> 5) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
destIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
destIncrement = 0;
|
|
break;
|
|
}
|
|
doDMA(dmaSource[0], dmaDest[0], sourceIncrement, destIncrement,
|
|
DMCNT_L[0] ? DMCNT_L[0] : 0x4000,
|
|
DMCNT_H[0] & 0x0400);
|
|
cpuDmaHack = true;
|
|
|
|
if(DMCNT_H[0] & 0x4000) {
|
|
IF |= 0x0100;
|
|
UPDATE_REG(0x202, IF);
|
|
cpuNextEvent = cpuTotalTicks;
|
|
}
|
|
|
|
if(((DMCNT_H[0] >> 5) & 3) == 3) {
|
|
dmaDest[0] = DMDAD_L[0] | (DMDAD_H[0] << 16);
|
|
}
|
|
|
|
if(!(DMCNT_H[0] & 0x0200) || (reason == 0)) {
|
|
DMCNT_H[0] &= 0x7FFF;
|
|
UPDATE_REG(0xBA, DMCNT_H[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// DMA 1
|
|
if((DMCNT_H[1] & 0x8000) && (dmamask & 2)) {
|
|
if(((DMCNT_H[1] >> 12) & 3) == reason) {
|
|
uint32 sourceIncrement = 4;
|
|
uint32 destIncrement = 4;
|
|
switch((DMCNT_H[1] >> 7) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
sourceIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
sourceIncrement = 0;
|
|
break;
|
|
}
|
|
switch((DMCNT_H[1] >> 5) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
destIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
destIncrement = 0;
|
|
break;
|
|
}
|
|
if(reason == 3) {
|
|
doDMA(dmaSource[1], dmaDest[1], sourceIncrement, 0, 4,
|
|
0x0400);
|
|
} else {
|
|
doDMA(dmaSource[1], dmaDest[1], sourceIncrement, destIncrement,
|
|
DMCNT_L[1] ? DMCNT_L[1] : 0x4000,
|
|
DMCNT_H[1] & 0x0400);
|
|
}
|
|
cpuDmaHack = true;
|
|
|
|
if(DMCNT_H[1] & 0x4000) {
|
|
IF |= 0x0200;
|
|
UPDATE_REG(0x202, IF);
|
|
cpuNextEvent = cpuTotalTicks;
|
|
}
|
|
|
|
if(((DMCNT_H[1] >> 5) & 3) == 3) {
|
|
dmaDest[1] = DMDAD_L[1] | (DMDAD_H[1] << 16);
|
|
}
|
|
|
|
if(!(DMCNT_H[1] & 0x0200) || (reason == 0)) {
|
|
DMCNT_H[1] &= 0x7FFF;
|
|
UPDATE_REG(0xC6, DMCNT_H[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// DMA 2
|
|
if((DMCNT_H[2] & 0x8000) && (dmamask & 4)) {
|
|
if(((DMCNT_H[2] >> 12) & 3) == reason) {
|
|
uint32 sourceIncrement = 4;
|
|
uint32 destIncrement = 4;
|
|
switch((DMCNT_H[2] >> 7) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
sourceIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
sourceIncrement = 0;
|
|
break;
|
|
}
|
|
switch((DMCNT_H[2] >> 5) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
destIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
destIncrement = 0;
|
|
break;
|
|
}
|
|
if(reason == 3) {
|
|
doDMA(dmaSource[2], dmaDest[2], sourceIncrement, 0, 4,
|
|
0x0400);
|
|
} else {
|
|
doDMA(dmaSource[2], dmaDest[2], sourceIncrement, destIncrement,
|
|
DMCNT_L[2] ? DMCNT_L[2] : 0x4000,
|
|
DMCNT_H[2] & 0x0400);
|
|
}
|
|
cpuDmaHack = true;
|
|
|
|
if(DMCNT_H[2] & 0x4000) {
|
|
IF |= 0x0400;
|
|
UPDATE_REG(0x202, IF);
|
|
cpuNextEvent = cpuTotalTicks;
|
|
}
|
|
|
|
if(((DMCNT_H[2] >> 5) & 3) == 3) {
|
|
dmaDest[2] = DMDAD_L[2] | (DMDAD_H[2] << 16);
|
|
}
|
|
|
|
if(!(DMCNT_H[2] & 0x0200) || (reason == 0)) {
|
|
DMCNT_H[2] &= 0x7FFF;
|
|
UPDATE_REG(0xD2, DMCNT_H[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// DMA 3
|
|
if((DMCNT_H[3] & 0x8000) && (dmamask & 8)) {
|
|
if(((DMCNT_H[3] >> 12) & 3) == reason) {
|
|
uint32 sourceIncrement = 4;
|
|
uint32 destIncrement = 4;
|
|
switch((DMCNT_H[3] >> 7) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
sourceIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
sourceIncrement = 0;
|
|
break;
|
|
}
|
|
switch((DMCNT_H[3] >> 5) & 3) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
destIncrement = (uint32)-4;
|
|
break;
|
|
case 2:
|
|
destIncrement = 0;
|
|
break;
|
|
}
|
|
doDMA(dmaSource[3], dmaDest[3], sourceIncrement, destIncrement,
|
|
DMCNT_L[3] ? DMCNT_L[3] : 0x10000,
|
|
DMCNT_H[3] & 0x0400);
|
|
if(DMCNT_H[3] & 0x4000) {
|
|
IF |= 0x0800;
|
|
UPDATE_REG(0x202, IF);
|
|
cpuNextEvent = cpuTotalTicks;
|
|
}
|
|
|
|
if(((DMCNT_H[3] >> 5) & 3) == 3) {
|
|
dmaDest[3] = DMDAD_L[3] | (DMDAD_H[3] << 16);
|
|
}
|
|
|
|
if(!(DMCNT_H[3] & 0x0200) || (reason == 0)) {
|
|
DMCNT_H[3] &= 0x7FFF;
|
|
UPDATE_REG(0xDE, DMCNT_H[3]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPUUpdateRegister(uint32 address, uint16 value)
|
|
{
|
|
switch(address)
|
|
{
|
|
case 0x00:
|
|
{
|
|
if ((value & 7) >5)
|
|
DISPCNT = (value &7);
|
|
bool change = ((DISPCNT ^ value) & 0x80) ? true : false;
|
|
bool changeBG = ((DISPCNT ^ value) & 0x0F00) ? true : false;
|
|
uint16 changeBGon = (((~DISPCNT) & value) & 0x0F00);
|
|
DISPCNT = (value & 0xFFF7);
|
|
UPDATE_REG(0x00, DISPCNT);
|
|
|
|
if (changeBGon)
|
|
{
|
|
layerEnableDelay=4;
|
|
layerEnable = layerSettings & value & (~changeBGon);
|
|
}
|
|
else
|
|
layerEnable = layerSettings & value;
|
|
// CPUUpdateTicks();
|
|
|
|
windowOn = (layerEnable & 0x6000) ? true : false;
|
|
if(change && !((value & 0x80))) {
|
|
if(!(DISPSTAT & 1)) {
|
|
lcdTicks = 1008;
|
|
// VCOUNT = 0;
|
|
// UPDATE_REG(0x06, VCOUNT);
|
|
DISPSTAT &= 0xFFFC;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
CPUCompareVCOUNT();
|
|
}
|
|
// (*renderLine)();
|
|
}
|
|
CPUUpdateRender();
|
|
// we only care about changes in BG0-BG3
|
|
if(changeBG)
|
|
CPUUpdateRenderBuffers(false);
|
|
}
|
|
break;
|
|
case 0x04:
|
|
DISPSTAT = (value & 0xFF38) | (DISPSTAT & 7);
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
break;
|
|
case 0x06:
|
|
// not writable
|
|
break;
|
|
case 0x08:
|
|
BG0CNT = (value & 0xDFCF);
|
|
UPDATE_REG(0x08, BG0CNT);
|
|
break;
|
|
case 0x0A:
|
|
BG1CNT = (value & 0xDFCF);
|
|
UPDATE_REG(0x0A, BG1CNT);
|
|
break;
|
|
case 0x0C:
|
|
BG2CNT = (value & 0xFFCF);
|
|
UPDATE_REG(0x0C, BG2CNT);
|
|
break;
|
|
case 0x0E:
|
|
BG3CNT = (value & 0xFFCF);
|
|
UPDATE_REG(0x0E, BG3CNT);
|
|
break;
|
|
case 0x10:
|
|
BGHOFS[0] = value & 511;
|
|
UPDATE_REG(0x10, BGHOFS[0]);
|
|
break;
|
|
case 0x12:
|
|
BGVOFS[0] = value & 511;
|
|
UPDATE_REG(0x12, BGVOFS[0]);
|
|
break;
|
|
case 0x14:
|
|
BGHOFS[1] = value & 511;
|
|
UPDATE_REG(0x14, BGHOFS[1]);
|
|
break;
|
|
case 0x16:
|
|
BGVOFS[1] = value & 511;
|
|
UPDATE_REG(0x16, BGVOFS[1]);
|
|
break;
|
|
case 0x18:
|
|
BGHOFS[2] = value & 511;
|
|
UPDATE_REG(0x18, BGHOFS[2]);
|
|
break;
|
|
case 0x1A:
|
|
BGVOFS[2] = value & 511;
|
|
UPDATE_REG(0x1A, BGVOFS[2]);
|
|
break;
|
|
case 0x1C:
|
|
BGHOFS[3] = value & 511;
|
|
UPDATE_REG(0x1C, BGHOFS[3]);
|
|
break;
|
|
case 0x1E:
|
|
BGVOFS[3] = value & 511;
|
|
UPDATE_REG(0x1E, BGVOFS[3]);
|
|
break;
|
|
case 0x20:
|
|
BG2PA = value;
|
|
UPDATE_REG(0x20, BG2PA);
|
|
break;
|
|
case 0x22:
|
|
BG2PB = value;
|
|
UPDATE_REG(0x22, BG2PB);
|
|
break;
|
|
case 0x24:
|
|
BG2PC = value;
|
|
UPDATE_REG(0x24, BG2PC);
|
|
break;
|
|
case 0x26:
|
|
BG2PD = value;
|
|
UPDATE_REG(0x26, BG2PD);
|
|
break;
|
|
case 0x28:
|
|
BG2X_L = value;
|
|
UPDATE_REG(0x28, BG2X_L);
|
|
gfxBG2Changed |= 1;
|
|
break;
|
|
case 0x2A:
|
|
BG2X_H = (value & 0xFFF);
|
|
UPDATE_REG(0x2A, BG2X_H);
|
|
gfxBG2Changed |= 1;
|
|
break;
|
|
case 0x2C:
|
|
BG2Y_L = value;
|
|
UPDATE_REG(0x2C, BG2Y_L);
|
|
gfxBG2Changed |= 2;
|
|
break;
|
|
case 0x2E:
|
|
BG2Y_H = value & 0xFFF;
|
|
UPDATE_REG(0x2E, BG2Y_H);
|
|
gfxBG2Changed |= 2;
|
|
break;
|
|
case 0x30:
|
|
BG3PA = value;
|
|
UPDATE_REG(0x30, BG3PA);
|
|
break;
|
|
case 0x32:
|
|
BG3PB = value;
|
|
UPDATE_REG(0x32, BG3PB);
|
|
break;
|
|
case 0x34:
|
|
BG3PC = value;
|
|
UPDATE_REG(0x34, BG3PC);
|
|
break;
|
|
case 0x36:
|
|
BG3PD = value;
|
|
UPDATE_REG(0x36, BG3PD);
|
|
break;
|
|
case 0x38:
|
|
BG3X_L = value;
|
|
UPDATE_REG(0x38, BG3X_L);
|
|
gfxBG3Changed |= 1;
|
|
break;
|
|
case 0x3A:
|
|
BG3X_H = value & 0xFFF;
|
|
UPDATE_REG(0x3A, BG3X_H);
|
|
gfxBG3Changed |= 1;
|
|
break;
|
|
case 0x3C:
|
|
BG3Y_L = value;
|
|
UPDATE_REG(0x3C, BG3Y_L);
|
|
gfxBG3Changed |= 2;
|
|
break;
|
|
case 0x3E:
|
|
BG3Y_H = value & 0xFFF;
|
|
UPDATE_REG(0x3E, BG3Y_H);
|
|
gfxBG3Changed |= 2;
|
|
break;
|
|
case 0x40:
|
|
WIN0H = value;
|
|
UPDATE_REG(0x40, WIN0H);
|
|
CPUUpdateWindow0();
|
|
break;
|
|
case 0x42:
|
|
WIN1H = value;
|
|
UPDATE_REG(0x42, WIN1H);
|
|
CPUUpdateWindow1();
|
|
break;
|
|
case 0x44:
|
|
WIN0V = value;
|
|
UPDATE_REG(0x44, WIN0V);
|
|
break;
|
|
case 0x46:
|
|
WIN1V = value;
|
|
UPDATE_REG(0x46, WIN1V);
|
|
break;
|
|
case 0x48:
|
|
WININ = value & 0x3F3F;
|
|
UPDATE_REG(0x48, WININ);
|
|
break;
|
|
case 0x4A:
|
|
WINOUT = value & 0x3F3F;
|
|
UPDATE_REG(0x4A, WINOUT);
|
|
break;
|
|
case 0x4C:
|
|
MOSAIC = value;
|
|
UPDATE_REG(0x4C, MOSAIC);
|
|
break;
|
|
case 0x50:
|
|
BLDMOD = value & 0x3FFF;
|
|
UPDATE_REG(0x50, BLDMOD);
|
|
fxOn = ((BLDMOD>>6)&3) != 0;
|
|
CPUUpdateRender();
|
|
break;
|
|
case 0x52:
|
|
COLEV = value & 0x1F1F;
|
|
UPDATE_REG(0x52, COLEV);
|
|
break;
|
|
case 0x54:
|
|
COLY = value & 0x1F;
|
|
UPDATE_REG(0x54, COLY);
|
|
break;
|
|
case 0x60:
|
|
case 0x62:
|
|
case 0x64:
|
|
case 0x68:
|
|
case 0x6c:
|
|
case 0x70:
|
|
case 0x72:
|
|
case 0x74:
|
|
case 0x78:
|
|
case 0x7c:
|
|
case 0x80:
|
|
case 0x84:
|
|
soundEvent(address&0xFF, (uint8)(value & 0xFF));
|
|
soundEvent((address&0xFF)+1, (uint8)(value>>8));
|
|
break;
|
|
case 0x82:
|
|
case 0x88:
|
|
case 0xa0:
|
|
case 0xa2:
|
|
case 0xa4:
|
|
case 0xa6:
|
|
case 0x90:
|
|
case 0x92:
|
|
case 0x94:
|
|
case 0x96:
|
|
case 0x98:
|
|
case 0x9a:
|
|
case 0x9c:
|
|
case 0x9e:
|
|
soundEvent(address&0xFF, value);
|
|
break;
|
|
case 0xB0:
|
|
DMSAD_L[0] = value;
|
|
UPDATE_REG(0xB0, DMSAD_L[0]);
|
|
break;
|
|
case 0xB2:
|
|
DMSAD_H[0] = value & 0x07FF;
|
|
UPDATE_REG(0xB2, DMSAD_H[0]);
|
|
break;
|
|
case 0xB4:
|
|
DMDAD_L[0] = value;
|
|
UPDATE_REG(0xB4, DMDAD_L[0]);
|
|
break;
|
|
case 0xB6:
|
|
DMDAD_H[0] = value & 0x07FF;
|
|
UPDATE_REG(0xB6, DMDAD_H[0]);
|
|
break;
|
|
case 0xB8:
|
|
DMCNT_L[0] = value & 0x3FFF;
|
|
UPDATE_REG(0xB8, 0);
|
|
break;
|
|
case 0xBA:
|
|
{
|
|
bool start = ((DMCNT_H[0] ^ value) & 0x8000) ? true : false;
|
|
value &= 0xF7E0;
|
|
|
|
DMCNT_H[0] = value;
|
|
UPDATE_REG(0xBA, DMCNT_H[0]);
|
|
|
|
if(start && (value & 0x8000)) {
|
|
dmaSource[0] = DMSAD_L[0] | (DMSAD_H[0] << 16);
|
|
dmaDest[0] = DMDAD_L[0] | (DMDAD_H[0] << 16);
|
|
CPUCheckDMA(0, 1);
|
|
}
|
|
}
|
|
break;
|
|
case 0xBC:
|
|
DMSAD_L[1] = value;
|
|
UPDATE_REG(0xBC, DMSAD_L[1]);
|
|
break;
|
|
case 0xBE:
|
|
DMSAD_H[1] = value & 0x0FFF;
|
|
UPDATE_REG(0xBE, DMSAD_H[1]);
|
|
break;
|
|
case 0xC0:
|
|
DMDAD_L[1] = value;
|
|
UPDATE_REG(0xC0, DMDAD_L[1]);
|
|
break;
|
|
case 0xC2:
|
|
DMDAD_H[1] = value & 0x07FF;
|
|
UPDATE_REG(0xC2, DMDAD_H[1]);
|
|
break;
|
|
case 0xC4:
|
|
DMCNT_L[1] = value & 0x3FFF;
|
|
UPDATE_REG(0xC4, 0);
|
|
break;
|
|
case 0xC6:
|
|
{
|
|
bool start = ((DMCNT_H[1] ^ value) & 0x8000) ? true : false;
|
|
value &= 0xF7E0;
|
|
|
|
DMCNT_H[1] = value;
|
|
UPDATE_REG(0xC6, DMCNT_H[1]);
|
|
|
|
if(start && (value & 0x8000)) {
|
|
dmaSource[1] = DMSAD_L[1] | (DMSAD_H[1] << 16);
|
|
dmaDest[1] = DMDAD_L[1] | (DMDAD_H[1] << 16);
|
|
CPUCheckDMA(0, 2);
|
|
}
|
|
}
|
|
break;
|
|
case 0xC8:
|
|
DMSAD_L[2] = value;
|
|
UPDATE_REG(0xC8, DMSAD_L[2]);
|
|
break;
|
|
case 0xCA:
|
|
DMSAD_H[2] = value & 0x0FFF;
|
|
UPDATE_REG(0xCA, DMSAD_H[2]);
|
|
break;
|
|
case 0xCC:
|
|
DMDAD_L[2] = value;
|
|
UPDATE_REG(0xCC, DMDAD_L[2]);
|
|
break;
|
|
case 0xCE:
|
|
DMDAD_H[2] = value & 0x07FF;
|
|
UPDATE_REG(0xCE, DMDAD_H[2]);
|
|
break;
|
|
case 0xD0:
|
|
DMCNT_L[2] = value & 0x3FFF;
|
|
UPDATE_REG(0xD0, 0);
|
|
break;
|
|
case 0xD2:
|
|
{
|
|
bool start = ((DMCNT_H[2] ^ value) & 0x8000) ? true : false;
|
|
|
|
value &= 0xF7E0;
|
|
|
|
DMCNT_H[2] = value;
|
|
UPDATE_REG(0xD2, DMCNT_H[2]);
|
|
|
|
if(start && (value & 0x8000)) {
|
|
dmaSource[2] = DMSAD_L[2] | (DMSAD_H[2] << 16);
|
|
dmaDest[2] = DMDAD_L[2] | (DMDAD_H[2] << 16);
|
|
|
|
CPUCheckDMA(0, 4);
|
|
}
|
|
}
|
|
break;
|
|
case 0xD4:
|
|
DMSAD_L[3] = value;
|
|
UPDATE_REG(0xD4, DMSAD_L[3]);
|
|
break;
|
|
case 0xD6:
|
|
DMSAD_H[3] = value & 0x0FFF;
|
|
UPDATE_REG(0xD6, DMSAD_H[3]);
|
|
break;
|
|
case 0xD8:
|
|
DMDAD_L[3] = value;
|
|
UPDATE_REG(0xD8, DMDAD_L[3]);
|
|
break;
|
|
case 0xDA:
|
|
DMDAD_H[3] = value & 0x0FFF;
|
|
UPDATE_REG(0xDA, DMDAD_H[3]);
|
|
break;
|
|
case 0xDC:
|
|
DMCNT_L[3] = value;
|
|
UPDATE_REG(0xDC, 0);
|
|
break;
|
|
case 0xDE:
|
|
{
|
|
bool start = ((DMCNT_H[3] ^ value) & 0x8000) ? true : false;
|
|
|
|
value &= 0xFFE0;
|
|
|
|
DMCNT_H[3] = value;
|
|
UPDATE_REG(0xDE, DMCNT_H[3]);
|
|
|
|
if(start && (value & 0x8000)) {
|
|
dmaSource[3] = DMSAD_L[3] | (DMSAD_H[3] << 16);
|
|
dmaDest[3] = DMDAD_L[3] | (DMDAD_H[3] << 16);
|
|
CPUCheckDMA(0,8);
|
|
}
|
|
}
|
|
break;
|
|
case 0x100:
|
|
timers[0].Reload = value;
|
|
break;
|
|
case 0x102:
|
|
timers[0].Value = value;
|
|
timerOnOffDelay|=1;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x104:
|
|
timers[1].Reload = value;
|
|
break;
|
|
case 0x106:
|
|
timers[1].Value = value;
|
|
timerOnOffDelay|=2;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x108:
|
|
timers[2].Reload = value;
|
|
break;
|
|
case 0x10A:
|
|
timers[2].Value = value;
|
|
timerOnOffDelay|=4;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x10C:
|
|
timers[3].Reload = value;
|
|
break;
|
|
case 0x10E:
|
|
timers[3].Value = value;
|
|
timerOnOffDelay|=8;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x128:
|
|
if(value & 0x80) {
|
|
value &= 0xff7f;
|
|
if(value & 1 && (value & 0x4000)) {
|
|
UPDATE_REG(0x12a, 0xFF);
|
|
IF |= 0x80;
|
|
UPDATE_REG(0x202, IF);
|
|
value &= 0x7f7f;
|
|
}
|
|
}
|
|
UPDATE_REG(0x128, value);
|
|
break;
|
|
case 0x130:
|
|
P1 |= (value & 0x3FF);
|
|
UPDATE_REG(0x130, P1);
|
|
break;
|
|
case 0x132:
|
|
UPDATE_REG(0x132, value & 0xC3FF);
|
|
break;
|
|
case 0x200:
|
|
IE = value & 0x3FFF;
|
|
UPDATE_REG(0x200, IE);
|
|
if ((IME & 1) && (IF & IE) && armIrqEnable)
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x202:
|
|
IF ^= (value & IF);
|
|
UPDATE_REG(0x202, IF);
|
|
break;
|
|
case 0x204:
|
|
{
|
|
memoryWait[0x0e] = memoryWaitSeq[0x0e] = gamepakRamWaitState[value & 3];
|
|
|
|
memoryWait[0x08] = memoryWait[0x09] = gamepakWaitState[(value >> 2) & 3];
|
|
memoryWaitSeq[0x08] = memoryWaitSeq[0x09] =
|
|
gamepakWaitState0[(value >> 4) & 1];
|
|
|
|
memoryWait[0x0a] = memoryWait[0x0b] = gamepakWaitState[(value >> 5) & 3];
|
|
memoryWaitSeq[0x0a] = memoryWaitSeq[0x0b] =
|
|
gamepakWaitState1[(value >> 7) & 1];
|
|
|
|
memoryWait[0x0c] = memoryWait[0x0d] = gamepakWaitState[(value >> 8) & 3];
|
|
memoryWaitSeq[0x0c] = memoryWaitSeq[0x0d] =
|
|
gamepakWaitState2[(value >> 10) & 1];
|
|
|
|
for(int i = 8; i < 15; i++) {
|
|
memoryWait32[i] = memoryWait[i] + memoryWaitSeq[i] + 1;
|
|
memoryWaitSeq32[i] = memoryWaitSeq[i]*2 + 1;
|
|
}
|
|
|
|
if((value & 0x4000) == 0x4000) {
|
|
busPrefetchEnable = true;
|
|
busPrefetch = false;
|
|
busPrefetchCount = 0;
|
|
} else {
|
|
busPrefetchEnable = false;
|
|
busPrefetch = false;
|
|
busPrefetchCount = 0;
|
|
}
|
|
UPDATE_REG(0x204, value & 0x7FFF);
|
|
|
|
}
|
|
break;
|
|
case 0x208:
|
|
IME = value & 1;
|
|
UPDATE_REG(0x208, IME);
|
|
if ((IME & 1) && (IF & IE) && armIrqEnable)
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
case 0x300:
|
|
if(value != 0)
|
|
value &= 0xFFFE;
|
|
UPDATE_REG(0x300, value);
|
|
break;
|
|
default:
|
|
UPDATE_REG(address&0x3FE, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void applyTimer ()
|
|
{
|
|
if (timerOnOffDelay & 1)
|
|
{
|
|
timers[0].ClockReload = TIMER_TICKS[timers[0].Value & 3];
|
|
if(!timers[0].On && (timers[0].Value & 0x80)) {
|
|
// reload the counter
|
|
timers[0].D = timers[0].Reload;
|
|
timers[0].Ticks = (0x10000 - timers[0].D) << timers[0].ClockReload;
|
|
UPDATE_REG(0x100, timers[0].D);
|
|
}
|
|
timers[0].On = timers[0].Value & 0x80 ? true : false;
|
|
timers[0].CNT = timers[0].Value & 0xC7;
|
|
UPDATE_REG(0x102, timers[0].CNT);
|
|
// CPUUpdateTicks();
|
|
}
|
|
if (timerOnOffDelay & 2)
|
|
{
|
|
timers[1].ClockReload = TIMER_TICKS[timers[1].Value & 3];
|
|
if(!timers[1].On && (timers[1].Value & 0x80)) {
|
|
// reload the counter
|
|
timers[1].D = timers[1].Reload;
|
|
timers[1].Ticks = (0x10000 - timers[1].D) << timers[1].ClockReload;
|
|
UPDATE_REG(0x104, timers[1].D);
|
|
}
|
|
timers[1].On = timers[1].Value & 0x80 ? true : false;
|
|
timers[1].CNT = timers[1].Value & 0xC7;
|
|
UPDATE_REG(0x106, timers[1].CNT);
|
|
}
|
|
if (timerOnOffDelay & 4)
|
|
{
|
|
timers[2].ClockReload = TIMER_TICKS[timers[2].Value & 3];
|
|
if(!timers[2].On && (timers[2].Value & 0x80)) {
|
|
// reload the counter
|
|
timers[2].D = timers[2].Reload;
|
|
timers[2].Ticks = (0x10000 - timers[2].D) << timers[2].ClockReload;
|
|
UPDATE_REG(0x108, timers[2].D);
|
|
}
|
|
timers[2].On = timers[2].Value & 0x80 ? true : false;
|
|
timers[2].CNT = timers[2].Value & 0xC7;
|
|
UPDATE_REG(0x10A, timers[2].CNT);
|
|
}
|
|
if (timerOnOffDelay & 8)
|
|
{
|
|
timers[3].ClockReload = TIMER_TICKS[timers[3].Value & 3];
|
|
if(!timers[3].On && (timers[3].Value & 0x80)) {
|
|
// reload the counter
|
|
timers[3].D = timers[3].Reload;
|
|
timers[3].Ticks = (0x10000 - timers[3].D) << timers[3].ClockReload;
|
|
UPDATE_REG(0x10C, timers[3].D);
|
|
}
|
|
timers[3].On = timers[3].Value & 0x80 ? true : false;
|
|
timers[3].CNT = timers[3].Value & 0xC7;
|
|
UPDATE_REG(0x10E, timers[3].CNT);
|
|
}
|
|
cpuNextEvent = CPUUpdateTicks();
|
|
timerOnOffDelay = 0;
|
|
}
|
|
|
|
static void FLASH_SRAM_Write(uint32 A, uint32 V)
|
|
{
|
|
// printf("%08x %08x\n", A, V);
|
|
|
|
if(cpuFlashEnabled && cpuSramEnabled)
|
|
{
|
|
if((A & 0xFFFF) == 0x5555 && (V & 0xFF) == 0xAA)
|
|
cpuSramEnabled = FALSE;
|
|
else if((A & 0xFFFF) != 0x2AAA)
|
|
cpuFlashEnabled = FALSE;
|
|
|
|
if(!cpuFlashEnabled || !cpuSramEnabled)
|
|
printf("%s emulation disabled by write to: %08x %08x\n", cpuSramEnabled ? "FLASH" : "SRAM", A, V);
|
|
}
|
|
|
|
if(cpuSramEnabled)
|
|
sramWrite(A, V);
|
|
|
|
if(cpuFlashEnabled)
|
|
flashWrite(A, V);
|
|
}
|
|
|
|
void CPUWriteMemory(uint32 address, uint32 value)
|
|
{
|
|
switch(address >> 24)
|
|
{
|
|
case 0x02:
|
|
WRITE32LE(((uint32 *)&workRAM[address & 0x3FFFC]), value);
|
|
break; \
|
|
case 0x03: \
|
|
WRITE32LE(((uint32 *)&internalRAM[address & 0x7ffC]), value); \
|
|
break; \
|
|
case 0x04: \
|
|
if(address < 0x4000400) { \
|
|
CPUUpdateRegister((address & 0x3FC), value & 0xFFFF); \
|
|
CPUUpdateRegister((address & 0x3FC) + 2, (value >> 16)); \
|
|
} \
|
|
break; \
|
|
case 0x05: \
|
|
WRITE32LE(((uint32 *)&paletteRAM[address & 0x3FC]), value); \
|
|
break; \
|
|
case 0x06: \
|
|
address = (address & 0x1fffc);
|
|
if (((DISPCNT & 7) >2) && ((address & 0x1C000) == 0x18000))
|
|
return;
|
|
if ((address & 0x18000) == 0x18000)
|
|
address &= 0x17fff;
|
|
WRITE32LE(((uint32 *)&vram[address]), value);
|
|
break; \
|
|
|
|
case 0x07:
|
|
WRITE32LE(((uint32 *)&oam[address & 0x3fc]), value);
|
|
break;
|
|
|
|
case 0x0D:
|
|
if(cpuEEPROMEnabled)
|
|
{
|
|
eepromWrite(address, value);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x0E:
|
|
FLASH_SRAM_Write(address, value);
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void CPUWriteHalfWord(uint32 address, uint16 value)
|
|
{
|
|
switch(address >> 24)
|
|
{
|
|
case 2:
|
|
WRITE16LE(((uint16 *)&workRAM[address & 0x3FFFE]),value);
|
|
break;
|
|
case 3:
|
|
WRITE16LE(((uint16 *)&internalRAM[address & 0x7ffe]), value);
|
|
break;
|
|
case 4:
|
|
if(address < 0x4000400)
|
|
CPUUpdateRegister(address & 0x3fe, value);
|
|
else goto unwritable;
|
|
break;
|
|
case 5:
|
|
WRITE16LE(((uint16 *)&paletteRAM[address & 0x3fe]), value);
|
|
break;
|
|
case 6:
|
|
address = (address & 0x1fffe);
|
|
if (((DISPCNT & 7) >2) && ((address & 0x1C000) == 0x18000))
|
|
return;
|
|
if ((address & 0x18000) == 0x18000)
|
|
address &= 0x17fff;
|
|
WRITE16LE(((uint16 *)&vram[address]), value);
|
|
break;
|
|
case 7:
|
|
WRITE16LE(((uint16 *)&oam[address & 0x3fe]), value);
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
if(GBA_RTC && (address == 0x80000c4 || address == 0x80000c6 || address == 0x80000c8))
|
|
GBA_RTC->Write(address, value);
|
|
else
|
|
goto unwritable;
|
|
break;
|
|
case 13:
|
|
if(cpuEEPROMEnabled)
|
|
{
|
|
eepromWrite(address, (uint8)value);
|
|
break;
|
|
}
|
|
goto unwritable;
|
|
|
|
case 0x0E:
|
|
FLASH_SRAM_Write(address, value);
|
|
break;
|
|
|
|
default:
|
|
unwritable:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPUWriteByte(uint32 address, uint8 b)
|
|
{
|
|
switch(address >> 24)
|
|
{
|
|
case 2:
|
|
workRAM[address & 0x3FFFF] = b;
|
|
break;
|
|
|
|
case 3:
|
|
internalRAM[address & 0x7fff] = b;
|
|
break;
|
|
|
|
case 4:
|
|
if(address < 0x4000400)
|
|
{
|
|
switch(address & 0x3FF)
|
|
{
|
|
case 0x301:
|
|
if(b == 0x80)
|
|
stopState = true;
|
|
holdState = 1;
|
|
holdType = -1;
|
|
cpuNextEvent = cpuTotalTicks;
|
|
break;
|
|
|
|
case 0x60:
|
|
case 0x61:
|
|
case 0x62:
|
|
case 0x63:
|
|
case 0x64:
|
|
case 0x65:
|
|
case 0x68:
|
|
case 0x69:
|
|
case 0x6c:
|
|
case 0x6d:
|
|
case 0x70:
|
|
case 0x71:
|
|
case 0x72:
|
|
case 0x73:
|
|
case 0x74:
|
|
case 0x75:
|
|
case 0x78:
|
|
case 0x79:
|
|
case 0x7c:
|
|
case 0x7d:
|
|
case 0x80:
|
|
case 0x81:
|
|
case 0x84:
|
|
case 0x85:
|
|
case 0x90:
|
|
case 0x91:
|
|
case 0x92:
|
|
case 0x93:
|
|
case 0x94:
|
|
case 0x95:
|
|
case 0x96:
|
|
case 0x97:
|
|
case 0x98:
|
|
case 0x99:
|
|
case 0x9a:
|
|
case 0x9b:
|
|
case 0x9c:
|
|
case 0x9d:
|
|
case 0x9e:
|
|
case 0x9f:
|
|
soundEvent(address&0xFF, b);
|
|
break;
|
|
default:
|
|
if(address & 1)
|
|
CPUUpdateRegister(address & 0x3fe,
|
|
((READ16LE(((uint16 *)&ioMem[address & 0x3fe])))
|
|
& 0x00FF) |
|
|
b<<8);
|
|
else
|
|
CPUUpdateRegister(address & 0x3fe,
|
|
((READ16LE(((uint16 *)&ioMem[address & 0x3fe])) & 0xFF00) | b));
|
|
}
|
|
break;
|
|
} else goto unwritable;
|
|
break;
|
|
case 5:
|
|
// no need to switch
|
|
*((uint16 *)&paletteRAM[address & 0x3FE]) = (b << 8) | b;
|
|
break;
|
|
case 6:
|
|
address = (address & 0x1fffe);
|
|
if (((DISPCNT & 7) >2) && ((address & 0x1C000) == 0x18000))
|
|
return;
|
|
if ((address & 0x18000) == 0x18000)
|
|
address &= 0x17fff;
|
|
// no need to switch
|
|
// byte writes to OBJ VRAM are ignored
|
|
if ((address) < objTilesAddress[((DISPCNT&7)+1)>>2])
|
|
*((uint16 *)&vram[address]) = (b << 8) | b;
|
|
break;
|
|
case 7:
|
|
// no need to switch
|
|
// byte writes to OAM are ignored
|
|
// *((uint16 *)&oam[address & 0x3FE]) = (b << 8) | b;
|
|
break;
|
|
|
|
case 0xD:
|
|
if(cpuEEPROMEnabled) {
|
|
eepromWrite(address, b);
|
|
break;
|
|
}
|
|
goto unwritable;
|
|
|
|
case 0xE:
|
|
FLASH_SRAM_Write(address, b);
|
|
break;
|
|
|
|
// default
|
|
default:
|
|
unwritable:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8 cpuBitsSet[256];
|
|
uint8 cpuLowestBitSet[256];
|
|
|
|
static bool CPUInit(const std::string bios_fn)
|
|
{
|
|
FILE *memfp;
|
|
|
|
FlashSizeSet = FALSE;
|
|
|
|
cpuSramEnabled = true;
|
|
cpuFlashEnabled = true;
|
|
cpuEEPROMEnabled = true;
|
|
cpuEEPROMSensorEnabled = true;
|
|
|
|
memfp = fopen(MDFN_MakeFName(MDFNMKF_SAV, 0, "type").c_str(), "rb");
|
|
if(memfp)
|
|
{
|
|
char linebuffer[256];
|
|
|
|
cpuSramEnabled = FALSE;
|
|
cpuFlashEnabled = FALSE;
|
|
cpuEEPROMEnabled = FALSE;
|
|
cpuEEPROMSensorEnabled = FALSE;
|
|
|
|
while(fgets(linebuffer, 256, memfp))
|
|
{
|
|
char args[2][256];
|
|
int acount = sscanf(linebuffer, "%255s %255s", args[0], args[1]);
|
|
|
|
if(acount < 1)
|
|
continue;
|
|
|
|
//MDFN_printf(_("Backup memory type override: %s %s\n"), linebuffer, (acount > 1) ? args[1] : "");
|
|
|
|
if(!strcasecmp(args[0], "sram"))
|
|
{
|
|
cpuSramEnabled = TRUE;
|
|
}
|
|
else if(!strcasecmp(args[0], "flash"))
|
|
{
|
|
cpuFlashEnabled = TRUE;
|
|
if(acount == 2)
|
|
{
|
|
int size_temp = atoi(args[1]);
|
|
|
|
if(size_temp == 0x10000 || size_temp == 0x20000)
|
|
{
|
|
flashSetSize(size_temp);
|
|
FlashSizeSet = TRUE;
|
|
}
|
|
else if(size_temp == 64 || size_temp == 128)
|
|
{
|
|
flashSetSize(size_temp * 1024);
|
|
FlashSizeSet = TRUE;
|
|
}
|
|
else
|
|
puts("Flash size error");
|
|
}
|
|
}
|
|
else if(!strcasecmp(args[0], "eeprom"))
|
|
cpuEEPROMEnabled = TRUE;
|
|
else if(!strcasecmp(args[0], "sensor"))
|
|
cpuEEPROMSensorEnabled = TRUE;
|
|
else if(!strcasecmp(args[0], "rtc"))
|
|
GBA_RTC = new RTC();
|
|
}
|
|
fclose(memfp);
|
|
}
|
|
|
|
useBios = false;
|
|
|
|
if(bios_fn != "" && bios_fn != "0" && bios_fn != "none")
|
|
{
|
|
static const FileExtensionSpecStruct KnownBIOSExtensions[] =
|
|
{
|
|
{ ".gba", "GameBoy Advance ROM Image" },
|
|
{ ".agb", "GameBoy Advance ROM Image" },
|
|
{ ".bin", "GameBoy Advance ROM Image" },
|
|
{ ".bios", "BIOS Image" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
MDFNFILE bios_fp;
|
|
|
|
if(bios_fp.Open(MDFN_MakeFName(MDFNMKF_FIRMWARE, 0, bios_fn.c_str()), KnownBIOSExtensions, _("GBA BIOS")))
|
|
{
|
|
if(GET_FSIZE(bios_fp) == 0x4000)
|
|
{
|
|
memcpy(bios, GET_FDATA(bios_fp), 0x4000);
|
|
useBios = true;
|
|
}
|
|
else
|
|
log_cb(RETRO_LOG_WARN, "Invalid BIOS file size");
|
|
bios_fp.Close();
|
|
}
|
|
else
|
|
log_cb(RETRO_LOG_WARN, "Cannot find GBA bios file. Using high-level bios emulation.\n");
|
|
}
|
|
|
|
if(!useBios)
|
|
{
|
|
memcpy(bios, myROM, sizeof(myROM));
|
|
Endian_A32_NE_to_LE(bios, sizeof(myROM) / 4);
|
|
}
|
|
|
|
int i = 0;
|
|
|
|
biosProtected[0] = 0x00;
|
|
biosProtected[1] = 0xf0;
|
|
biosProtected[2] = 0x29;
|
|
biosProtected[3] = 0xe1;
|
|
|
|
for(i = 0; i < 256; i++) {
|
|
int count = 0;
|
|
int j;
|
|
for(j = 0; j < 8; j++)
|
|
if(i & (1 << j))
|
|
count++;
|
|
cpuBitsSet[i] = count;
|
|
|
|
for(j = 0; j < 8; j++)
|
|
if(i & (1 << j))
|
|
break;
|
|
cpuLowestBitSet[i] = j;
|
|
}
|
|
|
|
for(i = 0; i < 0x400; i++)
|
|
ioReadable[i] = true;
|
|
for(i = 0x10; i < 0x48; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x4c; i < 0x50; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x54; i < 0x60; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x8c; i < 0x90; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0xa0; i < 0xb8; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0xbc; i < 0xc4; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0xc8; i < 0xd0; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0xd4; i < 0xdc; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0xe0; i < 0x100; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x110; i < 0x120; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x12c; i < 0x130; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x138; i < 0x140; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x144; i < 0x150; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x15c; i < 0x200; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x20c; i < 0x300; i++)
|
|
ioReadable[i] = false;
|
|
for(i = 0x304; i < 0x400; i++)
|
|
ioReadable[i] = false;
|
|
|
|
if(romSize < 0x1fe2000) {
|
|
*((uint16 *)&rom[0x1fe209c]) = 0xdffa; // SWI 0xFA
|
|
*((uint16 *)&rom[0x1fe209e]) = 0x4770; // BX LR
|
|
} else {
|
|
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
static void CPUReset(void) MDFN_COLD;
|
|
static void CPUReset(void)
|
|
{
|
|
if(GBA_RTC)
|
|
GBA_RTC->Reset();
|
|
|
|
// clean registers
|
|
memset(®[0], 0, sizeof(reg));
|
|
// clean OAM
|
|
memset(oam, 0, 0x400);
|
|
// clean palette
|
|
memset(paletteRAM, 0, 0x400);
|
|
// clean vram
|
|
memset(vram, 0, 0x20000);
|
|
// clean io memory
|
|
memset(ioMem, 0, 0x400);
|
|
|
|
memset(internalRAM, 0, 0x8000);
|
|
|
|
memset(workRAM, 0x00, 0x40000);
|
|
|
|
DISPCNT = 0x0080;
|
|
DISPSTAT = 0x0000;
|
|
VCOUNT = (useBios && !skipBios) ? 0 :0x007E;
|
|
BG0CNT = 0x0000;
|
|
BG1CNT = 0x0000;
|
|
BG2CNT = 0x0000;
|
|
BG3CNT = 0x0000;
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
BGHOFS[i] = 0;
|
|
BGVOFS[i] = 0;
|
|
}
|
|
|
|
BG2PA = 0x0100;
|
|
BG2PB = 0x0000;
|
|
BG2PC = 0x0000;
|
|
BG2PD = 0x0100;
|
|
BG2X_L = 0x0000;
|
|
BG2X_H = 0x0000;
|
|
BG2Y_L = 0x0000;
|
|
BG2Y_H = 0x0000;
|
|
BG3PA = 0x0100;
|
|
BG3PB = 0x0000;
|
|
BG3PC = 0x0000;
|
|
BG3PD = 0x0100;
|
|
BG3X_L = 0x0000;
|
|
BG3X_H = 0x0000;
|
|
BG3Y_L = 0x0000;
|
|
BG3Y_H = 0x0000;
|
|
WIN0H = 0x0000;
|
|
WIN1H = 0x0000;
|
|
WIN0V = 0x0000;
|
|
WIN1V = 0x0000;
|
|
WININ = 0x0000;
|
|
WINOUT = 0x0000;
|
|
MOSAIC = 0x0000;
|
|
BLDMOD = 0x0000;
|
|
COLEV = 0x0000;
|
|
COLY = 0x0000;
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
DMSAD_L[i] = DMSAD_H[i] = 0;
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
timers[i].D = 0;
|
|
timers[i].CNT = 0;
|
|
}
|
|
|
|
P1 = 0x03FF;
|
|
IE = 0x0000;
|
|
IF = 0x0000;
|
|
IME = 0x0000;
|
|
|
|
armMode = 0x1F;
|
|
|
|
if(cpuIsMultiBoot) {
|
|
reg[13].I = 0x03007F00;
|
|
reg[15].I = 0x02000000;
|
|
reg[16].I = 0x00000000;
|
|
reg[R13_IRQ].I = 0x03007FA0;
|
|
reg[R13_SVC].I = 0x03007FE0;
|
|
armIrqEnable = true;
|
|
} else {
|
|
if(useBios && !skipBios) {
|
|
reg[15].I = 0x00000000;
|
|
armMode = 0x13;
|
|
armIrqEnable = false;
|
|
} else {
|
|
reg[13].I = 0x03007F00;
|
|
reg[15].I = 0x08000000;
|
|
reg[16].I = 0x00000000;
|
|
reg[R13_IRQ].I = 0x03007FA0;
|
|
reg[R13_SVC].I = 0x03007FE0;
|
|
armIrqEnable = true;
|
|
}
|
|
}
|
|
armState = true;
|
|
C_FLAG = false;
|
|
V_FLAG = false;
|
|
N_FLAG = false;
|
|
Z_FLAG = false;
|
|
|
|
UPDATE_REG(0x00, DISPCNT);
|
|
UPDATE_REG(0x06, VCOUNT);
|
|
UPDATE_REG(0x20, BG2PA);
|
|
UPDATE_REG(0x26, BG2PD);
|
|
UPDATE_REG(0x30, BG3PA);
|
|
UPDATE_REG(0x36, BG3PD);
|
|
UPDATE_REG(0x130, P1);
|
|
UPDATE_REG(0x88, 0x200);
|
|
|
|
// disable FIQ
|
|
reg[16].I |= 0x40;
|
|
|
|
CPUUpdateCPSR();
|
|
|
|
armNextPC = reg[15].I;
|
|
reg[15].I += 4;
|
|
|
|
// reset internal state
|
|
holdState = false;
|
|
holdType = 0;
|
|
|
|
biosProtected[0] = 0x00;
|
|
biosProtected[1] = 0xf0;
|
|
biosProtected[2] = 0x29;
|
|
biosProtected[3] = 0xe1;
|
|
|
|
lcdTicks = (useBios && !skipBios) ? 1008 : 208;
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
timers[i].On = false;
|
|
timers[i].Ticks = 0;
|
|
timers[i].Reload = 0;
|
|
timers[i].ClockReload = 0;
|
|
}
|
|
|
|
dmaSource[0] = 0;
|
|
dmaDest[0] = 0;
|
|
dmaSource[1] = 0;
|
|
dmaDest[1] = 0;
|
|
dmaSource[2] = 0;
|
|
dmaDest[2] = 0;
|
|
dmaSource[3] = 0;
|
|
dmaDest[3] = 0;
|
|
|
|
renderLine = mode0RenderLine;
|
|
fxOn = false;
|
|
windowOn = false;
|
|
saveType = 0;
|
|
layerEnable = DISPCNT & layerSettings;
|
|
|
|
CPUUpdateRenderBuffers(true);
|
|
|
|
for(int i = 0; i < 256; i++) {
|
|
map[i].address = (uint8 *)&dummyAddress;
|
|
map[i].mask = 0;
|
|
}
|
|
|
|
map[0].address = bios;
|
|
map[0].mask = 0x3FFF;
|
|
map[2].address = workRAM;
|
|
map[2].mask = 0x3FFFF;
|
|
map[3].address = internalRAM;
|
|
map[3].mask = 0x7FFF;
|
|
map[4].address = ioMem;
|
|
map[4].mask = 0x3FF;
|
|
map[5].address = paletteRAM;
|
|
map[5].mask = 0x3FF;
|
|
map[6].address = vram;
|
|
map[6].mask = 0x1FFFF;
|
|
map[7].address = oam;
|
|
map[7].mask = 0x3FF;
|
|
map[8].address = rom;
|
|
map[8].mask = 0x1FFFFFF;
|
|
map[9].address = rom;
|
|
map[9].mask = 0x1FFFFFF;
|
|
map[10].address = rom;
|
|
map[10].mask = 0x1FFFFFF;
|
|
map[12].address = rom;
|
|
map[12].mask = 0x1FFFFFF;
|
|
map[14].address = flashSaveMemory;
|
|
map[14].mask = 0xFFFF;
|
|
|
|
eepromReset();
|
|
GBA_Flash_Reset();
|
|
|
|
soundReset();
|
|
|
|
CPUUpdateWindow0();
|
|
CPUUpdateWindow1();
|
|
|
|
// make sure registers are correctly initialized if not using BIOS
|
|
if(!useBios) {
|
|
if(cpuIsMultiBoot)
|
|
BIOS_RegisterRamReset(0xfe);
|
|
else
|
|
BIOS_RegisterRamReset(0xff);
|
|
} else {
|
|
if(cpuIsMultiBoot)
|
|
BIOS_RegisterRamReset(0xfe);
|
|
}
|
|
|
|
ARM_PREFETCH;
|
|
|
|
cpuDmaHack = false;
|
|
|
|
SWITicks = 0;
|
|
}
|
|
|
|
void CPUInterrupt()
|
|
{
|
|
uint32 PC = reg[15].I;
|
|
bool savedState = armState;
|
|
CPUSwitchMode(0x12, true, false);
|
|
reg[14].I = PC;
|
|
if(!savedState)
|
|
reg[14].I += 2;
|
|
reg[15].I = 0x18;
|
|
armState = true;
|
|
armIrqEnable = false;
|
|
|
|
armNextPC = reg[15].I;
|
|
reg[15].I += 4;
|
|
ARM_PREFETCH;
|
|
|
|
// if(!holdState)
|
|
biosProtected[0] = 0x02;
|
|
biosProtected[1] = 0xc0;
|
|
biosProtected[2] = 0x5e;
|
|
biosProtected[3] = 0xe5;
|
|
}
|
|
|
|
uint32 soundTS = 0;
|
|
static uint8 *padq;
|
|
|
|
static void SetInput(unsigned port, const char *type, void *ptr)
|
|
{
|
|
padq = (uint8*)ptr;
|
|
}
|
|
|
|
int32 MDFNGBA_GetTimerPeriod(int which)
|
|
{
|
|
int32 ret = ((0x10000 - timers[which].Reload) << timers[which].ClockReload);
|
|
// printf("%d, %08x\n", ret, timers[which].Reload);
|
|
return(ret);
|
|
}
|
|
|
|
static int frameready;
|
|
static int HelloSkipper;
|
|
|
|
static void CPULoop(EmulateSpecStruct* espec, int ticks)
|
|
{
|
|
MDFN_Surface* surface = espec->surface;
|
|
int clockTicks;
|
|
int timerOverflow = 0;
|
|
// variable used by the CPU core
|
|
cpuTotalTicks = 0;
|
|
cpuBreakLoop = false;
|
|
cpuNextEvent = CPUUpdateTicks();
|
|
if(cpuNextEvent > ticks)
|
|
cpuNextEvent = ticks;
|
|
|
|
|
|
for(;;) {
|
|
if(!holdState && !SWITicks) {
|
|
if(armState) {
|
|
clockTicks = RunARM();
|
|
} else {
|
|
clockTicks = RunTHUMB();
|
|
}
|
|
} else
|
|
clockTicks = CPUUpdateTicks();
|
|
|
|
cpuTotalTicks += clockTicks;
|
|
|
|
if(cpuTotalTicks >= cpuNextEvent) {
|
|
int remainingTicks = cpuTotalTicks - cpuNextEvent;
|
|
|
|
if (SWITicks)
|
|
{
|
|
SWITicks-=clockTicks;
|
|
if (SWITicks<0)
|
|
SWITicks = 0;
|
|
}
|
|
|
|
clockTicks = cpuNextEvent;
|
|
cpuTotalTicks = 0;
|
|
cpuDmaHack = false;
|
|
|
|
updateLoop:
|
|
|
|
if (IRQTicks)
|
|
{
|
|
IRQTicks -= clockTicks;
|
|
if (IRQTicks<0)
|
|
IRQTicks = 0;
|
|
}
|
|
soundTS += clockTicks;
|
|
lcdTicks -= clockTicks;
|
|
|
|
|
|
if(lcdTicks <= 0) {
|
|
if(DISPSTAT & 1) { // V-BLANK
|
|
// if in V-Blank mode, keep computing...
|
|
if(DISPSTAT & 2) {
|
|
lcdTicks += 1008;
|
|
VCOUNT++;
|
|
UPDATE_REG(0x06, VCOUNT);
|
|
DISPSTAT &= 0xFFFD;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
CPUCompareVCOUNT();
|
|
} else {
|
|
lcdTicks += 224;
|
|
DISPSTAT |= 2;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
if(DISPSTAT & 16) {
|
|
IF |= 2;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
|
|
if(VCOUNT >= 228) { //Reaching last line
|
|
DISPSTAT &= 0xFFFC;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
VCOUNT = 0;
|
|
UPDATE_REG(0x06, VCOUNT);
|
|
CPUCompareVCOUNT();
|
|
}
|
|
} else {
|
|
if(DISPSTAT & 2) {
|
|
// if in H-Blank, leave it and move to drawing mode
|
|
VCOUNT++;
|
|
UPDATE_REG(0x06, VCOUNT);
|
|
|
|
lcdTicks += 1008;
|
|
DISPSTAT &= 0xFFFD;
|
|
if(VCOUNT == 160)
|
|
{
|
|
//ticks = 0;
|
|
//puts("VBlank");
|
|
uint32 joy = padbufblah;
|
|
P1 = 0x03FF ^ (joy & 0x3FF);
|
|
//if(cpuEEPROMSensorEnabled)
|
|
//systemUpdateMotionSensor();
|
|
UPDATE_REG(0x130, P1);
|
|
uint16 P1CNT = READ16LE(((uint16 *)&ioMem[0x132]));
|
|
// this seems wrong, but there are cases where the game
|
|
// can enter the stop state without requesting an IRQ from
|
|
// the joypad.
|
|
if((P1CNT & 0x4000) || stopState) {
|
|
uint16 p1 = (0x3FF ^ P1) & 0x3FF;
|
|
if(P1CNT & 0x8000) {
|
|
if(p1 == (P1CNT & 0x3FF)) {
|
|
IF |= 0x1000;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
} else {
|
|
if(p1 & P1CNT) {
|
|
IF |= 0x1000;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DISPSTAT |= 1;
|
|
DISPSTAT &= 0xFFFD;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
if(DISPSTAT & 0x0008) {
|
|
IF |= 1;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
CPUCheckDMA(1, 0x0f);
|
|
}
|
|
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
CPUCompareVCOUNT();
|
|
|
|
} else {
|
|
if(!HelloSkipper)
|
|
{
|
|
//printf("RL: %d\n", VCOUNT);
|
|
const uint32 *src = lineMix;
|
|
|
|
(*renderLine)();
|
|
|
|
if(surface->format.bpp == 32)
|
|
{
|
|
const uint32* cm = systemColorMap->v32;
|
|
uint32 *dest = surface->pixels + VCOUNT * surface->pitch32;
|
|
|
|
for(int x = 120; x; x--)
|
|
{
|
|
*dest = cm[*src & 0xFFFF];
|
|
dest++;
|
|
src++;
|
|
*dest = cm[*src & 0xFFFF];
|
|
dest++;
|
|
src++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const uint16* cm = systemColorMap->v16;
|
|
uint16* dest = surface->pixels16 + VCOUNT * surface->pitchinpix;
|
|
|
|
for(int x = 0; x < 240; x += 2)
|
|
{
|
|
dest[x + 0] = cm[(uint16)src[x + 0]];
|
|
dest[x + 1] = cm[(uint16)src[x + 1]];
|
|
}
|
|
}
|
|
MDFN_MidLineUpdate(espec, VCOUNT);
|
|
}
|
|
// entering H-Blank
|
|
DISPSTAT |= 2;
|
|
UPDATE_REG(0x04, DISPSTAT);
|
|
lcdTicks += 224;
|
|
CPUCheckDMA(2, 0x0f);
|
|
if(DISPSTAT & 16) {
|
|
IF |= 2;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
if(VCOUNT == 159)
|
|
{
|
|
frameready = 1;
|
|
cpuBreakLoop = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!stopState) {
|
|
if(timers[0].On) {
|
|
timers[0].Ticks -= clockTicks;
|
|
if(timers[0].Ticks <= 0) {
|
|
timers[0].Ticks += (0x10000 - timers[0].Reload) << timers[0].ClockReload;
|
|
timerOverflow |= 1;
|
|
soundTimerOverflow(0);
|
|
if(timers[0].CNT & 0x40) {
|
|
IF |= 0x08;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
timers[0].D = 0xFFFF - (timers[0].Ticks >> timers[0].ClockReload);
|
|
UPDATE_REG(0x100, timers[0].D);
|
|
}
|
|
|
|
if(timers[1].On) {
|
|
if(timers[1].CNT & 4) {
|
|
if(timerOverflow & 1) {
|
|
timers[1].D++;
|
|
if(timers[1].D == 0) {
|
|
timers[1].D += timers[1].Reload;
|
|
timerOverflow |= 2;
|
|
soundTimerOverflow(1);
|
|
if(timers[1].CNT & 0x40) {
|
|
IF |= 0x10;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
UPDATE_REG(0x104, timers[1].D);
|
|
}
|
|
} else {
|
|
timers[1].Ticks -= clockTicks;
|
|
if(timers[1].Ticks <= 0) {
|
|
timers[1].Ticks += (0x10000 - timers[1].Reload) << timers[1].ClockReload;
|
|
timerOverflow |= 2;
|
|
soundTimerOverflow(1);
|
|
if(timers[1].CNT & 0x40) {
|
|
IF |= 0x10;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
timers[1].D = 0xFFFF - (timers[1].Ticks >> timers[1].ClockReload);
|
|
UPDATE_REG(0x104, timers[1].D);
|
|
}
|
|
}
|
|
|
|
if(timers[2].On) {
|
|
if(timers[2].CNT & 4) {
|
|
if(timerOverflow & 2) {
|
|
timers[2].D++;
|
|
if(timers[2].D == 0) {
|
|
timers[2].D += timers[2].Reload;
|
|
timerOverflow |= 4;
|
|
if(timers[2].CNT & 0x40) {
|
|
IF |= 0x20;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
UPDATE_REG(0x108, timers[2].D);
|
|
}
|
|
} else {
|
|
timers[2].Ticks -= clockTicks;
|
|
if(timers[2].Ticks <= 0) {
|
|
timers[2].Ticks += (0x10000 - timers[2].Reload) << timers[2].ClockReload;
|
|
timerOverflow |= 4;
|
|
if(timers[2].CNT & 0x40) {
|
|
IF |= 0x20;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
timers[2].D = 0xFFFF - (timers[2].Ticks >> timers[2].ClockReload);
|
|
UPDATE_REG(0x108, timers[2].D);
|
|
}
|
|
}
|
|
|
|
if(timers[3].On) {
|
|
if(timers[3].CNT & 4) {
|
|
if(timerOverflow & 4) {
|
|
timers[3].D++;
|
|
if(timers[3].D == 0) {
|
|
timers[3].D += timers[3].Reload;
|
|
if(timers[3].CNT & 0x40) {
|
|
IF |= 0x40;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
UPDATE_REG(0x10C, timers[3].D);
|
|
}
|
|
} else {
|
|
timers[3].Ticks -= clockTicks;
|
|
if(timers[3].Ticks <= 0) {
|
|
timers[3].Ticks += (0x10000 - timers[3].Reload) << timers[3].ClockReload;
|
|
if(timers[3].CNT & 0x40) {
|
|
IF |= 0x40;
|
|
UPDATE_REG(0x202, IF);
|
|
}
|
|
}
|
|
timers[3].D = 0xFFFF - (timers[3].Ticks >> timers[3].ClockReload);
|
|
UPDATE_REG(0x10C, timers[3].D);
|
|
}
|
|
}
|
|
}
|
|
|
|
timerOverflow = 0;
|
|
|
|
ticks -= clockTicks;
|
|
|
|
cpuNextEvent = CPUUpdateTicks();
|
|
|
|
if(cpuDmaTicksToUpdate > 0) {
|
|
if(cpuDmaTicksToUpdate > cpuNextEvent)
|
|
clockTicks = cpuNextEvent;
|
|
else
|
|
clockTicks = cpuDmaTicksToUpdate;
|
|
cpuDmaTicksToUpdate -= clockTicks;
|
|
if(cpuDmaTicksToUpdate < 0)
|
|
cpuDmaTicksToUpdate = 0;
|
|
cpuDmaHack = true;
|
|
goto updateLoop;
|
|
}
|
|
|
|
if(IF && (IME & 1) && armIrqEnable) {
|
|
int res = IF & IE;
|
|
if(stopState)
|
|
res &= 0x3080;
|
|
if(res) {
|
|
if (intState)
|
|
{
|
|
if (!IRQTicks)
|
|
{
|
|
CPUInterrupt();
|
|
intState = false;
|
|
holdState = false;
|
|
stopState = false;
|
|
holdType = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!holdState)
|
|
{
|
|
intState = true;
|
|
IRQTicks=7;
|
|
if (cpuNextEvent> IRQTicks)
|
|
cpuNextEvent = IRQTicks;
|
|
}
|
|
else
|
|
{
|
|
CPUInterrupt();
|
|
holdState = false;
|
|
stopState = false;
|
|
holdType = 0;
|
|
}
|
|
}
|
|
|
|
// Stops the SWI Ticks emulation if an IRQ is executed
|
|
//(to avoid problems with nested IRQ/SWI)
|
|
if (SWITicks)
|
|
SWITicks = 0;
|
|
}
|
|
}
|
|
|
|
if(remainingTicks > 0) {
|
|
if(remainingTicks > cpuNextEvent)
|
|
clockTicks = cpuNextEvent;
|
|
else
|
|
clockTicks = remainingTicks;
|
|
remainingTicks -= clockTicks;
|
|
if(remainingTicks < 0)
|
|
remainingTicks = 0;
|
|
goto updateLoop;
|
|
}
|
|
|
|
if (timerOnOffDelay)
|
|
applyTimer();
|
|
|
|
if(cpuNextEvent > ticks)
|
|
cpuNextEvent = ticks;
|
|
|
|
if(ticks <= 0 || cpuBreakLoop)
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Emulate(EmulateSpecStruct *espec)
|
|
{
|
|
espec->DisplayRect.x = 0;
|
|
espec->DisplayRect.y = 0;
|
|
espec->DisplayRect.w = 240;
|
|
espec->DisplayRect.h = 160;
|
|
|
|
#if 0
|
|
{
|
|
static bool firstcat = true;
|
|
MDFN_PixelFormat nf;
|
|
|
|
nf.bpp = 16;
|
|
nf.colorspace = MDFN_COLORSPACE_RGB;
|
|
nf.Rshift = 11;
|
|
nf.Gshift = 5;
|
|
nf.Bshift = 0;
|
|
nf.Ashift = 16;
|
|
|
|
nf.Rprec = 5;
|
|
nf.Gprec = 5;
|
|
nf.Bprec = 5;
|
|
nf.Aprec = 8;
|
|
|
|
espec->surface->SetFormat(nf, false);
|
|
espec->VideoFormatChanged = firstcat;
|
|
firstcat = false;
|
|
}
|
|
#endif
|
|
|
|
if(espec->VideoFormatChanged)
|
|
RedoColorMap(espec->surface->format); //espec->surface->format.Rshift, espec->surface->format.Gshift, espec->surface->format.Bshift);
|
|
|
|
if(espec->SoundFormatChanged)
|
|
MDFNGBA_SetSoundRate(espec->SoundRate);
|
|
|
|
#if 0
|
|
int cursong = workRAM[0x2020c96- (0x2 << 24)];
|
|
static int last_song = 0;
|
|
|
|
if(cursong != last_song)
|
|
{
|
|
if(last_song == 250)
|
|
{
|
|
MDFNI_EndWaveRecord();
|
|
exit(1);
|
|
}
|
|
else
|
|
{
|
|
char wavepath[256];
|
|
if(last_song != 0)
|
|
MDFNI_EndWaveRecord();
|
|
trio_snprintf(wavepath, 256, "/meow/mother3-%d.wav", cursong);
|
|
MDFNI_BeginWaveRecord(FSettings.SndRate, 2, wavepath);
|
|
}
|
|
last_song = cursong;
|
|
}
|
|
#endif
|
|
|
|
padbufblah = padq[0] | (padq[1] << 8);
|
|
|
|
frameready = 0;
|
|
|
|
HelloSkipper = espec->skip;
|
|
|
|
MDFNMP_ApplyPeriodicCheats();
|
|
|
|
while(!frameready && (soundTS < 300000))
|
|
CPULoop(espec, 300000);
|
|
|
|
if(GBA_RTC)
|
|
GBA_RTC->AddTime(soundTS);
|
|
|
|
espec->MasterCycles = soundTS;
|
|
|
|
espec->SoundBufSize = MDFNGBASOUND_Flush(espec->SoundBuf, espec->SoundBufMaxSize);
|
|
}
|
|
static void SetLayerEnableMask(uint64 mask)
|
|
{
|
|
layerSettings = mask << 8;
|
|
layerEnable = layerSettings & DISPCNT;
|
|
|
|
CPUUpdateRender();
|
|
CPUUpdateRenderBuffers(true);
|
|
CPUUpdateWindow0();
|
|
CPUUpdateWindow1();
|
|
}
|
|
|
|
static void DoSimpleCommand(int cmd)
|
|
{
|
|
switch(cmd)
|
|
{
|
|
case MDFN_MSC_POWER:
|
|
case MDFN_MSC_RESET: CPUReset(); break;
|
|
}
|
|
}
|
|
|
|
static MDFNSetting GBASettings[] =
|
|
{
|
|
{ "gba.bios", MDFNSF_EMU_STATE, "Path to optional GBA BIOS ROM image.", NULL, MDFNST_STRING, "" },
|
|
{ NULL }
|
|
};
|
|
|
|
static const InputDeviceInputInfoStruct IDII[] =
|
|
{
|
|
{ "a", "A", /*VIRTB_1,*/ 7, IDIT_BUTTON_CAN_RAPID, NULL },
|
|
|
|
{ "b", "B", /*VIRTB_0,*/ 6, IDIT_BUTTON_CAN_RAPID, NULL },
|
|
|
|
{ "select", "SELECT", /*VIRTB_SELECT,*/ 4, IDIT_BUTTON, NULL },
|
|
|
|
{ "start", "START", /*VIRTB_START,*/ 5, IDIT_BUTTON, NULL },
|
|
|
|
{ "right", "RIGHT →", /*VIRTB_DP0_R,*/ 3, IDIT_BUTTON, "left" },
|
|
|
|
{ "left", "LEFT ←", /*VIRTB_DP0_L,*/ 2, IDIT_BUTTON, "right" },
|
|
|
|
{ "up", "UP ↑", /*VIRTB_DP0_U,*/ 0, IDIT_BUTTON, "down" },
|
|
|
|
{ "down", "DOWN ↓", /*VIRTB_DP0_D,*/ 1, IDIT_BUTTON, "up" },
|
|
|
|
{ "shoulder_r", "SHOULDER R", /*VIRTB_SHLDR_L,*/ 9, IDIT_BUTTON, NULL },
|
|
|
|
{ "shoulder_l", "SHOULDER L", /*VIRTB_SHLDR_R,*/ 8, IDIT_BUTTON, NULL },
|
|
};
|
|
|
|
static InputDeviceInfoStruct InputDeviceInfo[] =
|
|
{
|
|
{
|
|
"gamepad",
|
|
"Gamepad",
|
|
NULL,
|
|
NULL,
|
|
sizeof(IDII) / sizeof(InputDeviceInputInfoStruct),
|
|
IDII,
|
|
}
|
|
};
|
|
|
|
static const InputPortInfoStruct PortInfo[] =
|
|
{
|
|
{ "builtin", "Built-In", sizeof(InputDeviceInfo) / sizeof(InputDeviceInfoStruct), InputDeviceInfo, "gamepad" }
|
|
};
|
|
|
|
static InputInfoStruct InputInfo =
|
|
{
|
|
sizeof(PortInfo) / sizeof(InputPortInfoStruct),
|
|
PortInfo
|
|
};
|
|
|
|
static const FileExtensionSpecStruct KnownExtensions[] =
|
|
{
|
|
{ ".gsf", "GSF Rip" },
|
|
{ ".minigsf", "MiniGSF Rip" },
|
|
{ ".gba", "GameBoy Advance ROM Image" },
|
|
{ ".agb", "GameBoy Advance ROM Image" },
|
|
{ ".bin", "GameBoy Advance ROM Image" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
MDFNGI EmulatedGBA =
|
|
{
|
|
GBASettings,
|
|
MDFN_MASTERCLOCK_FIXED(16777216),
|
|
(uint32)((double)4194304 / 70224 * 65536 * 256),
|
|
|
|
false, // Multires possible?
|
|
|
|
240, // lcm_width
|
|
160, // lcm_height
|
|
NULL, // Dummy
|
|
|
|
240, // Nominal width
|
|
160, // Nominal height
|
|
|
|
240, // Framebuffer width
|
|
160, // Framebuffer height
|
|
|
|
2, // Number of output sound channels
|
|
};
|
|
|
|
|
|
static void set_basename(const char *path)
|
|
{
|
|
const char *base = strrchr(path, '/');
|
|
if (!base)
|
|
base = strrchr(path, '\\');
|
|
|
|
if (base)
|
|
retro_base_name = base + 1;
|
|
else
|
|
retro_base_name = path;
|
|
|
|
retro_base_name = retro_base_name.substr(0, retro_base_name.find_last_of('.'));
|
|
}
|
|
|
|
#ifdef NEED_DEINTERLACER
|
|
static bool PrevInterlaced;
|
|
static Deinterlacer deint;
|
|
#endif
|
|
|
|
#define MEDNAFEN_CORE_NAME_MODULE "gba"
|
|
#define MEDNAFEN_CORE_NAME "Mednafen VBA-M"
|
|
#define MEDNAFEN_CORE_VERSION "v0.9.36"
|
|
#define MEDNAFEN_CORE_EXTENSIONS "gba|agb|bin"
|
|
#define MEDNAFEN_CORE_TIMING_FPS 59.73
|
|
#define MEDNAFEN_CORE_GEOMETRY_BASE_W (game->nominal_width)
|
|
#define MEDNAFEN_CORE_GEOMETRY_BASE_H (game->nominal_height)
|
|
#define MEDNAFEN_CORE_GEOMETRY_MAX_W 240
|
|
#define MEDNAFEN_CORE_GEOMETRY_MAX_H 160
|
|
#define MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO (3.0 / 2.0)
|
|
#define FB_WIDTH 240
|
|
#define FB_HEIGHT 160
|
|
|
|
#define FB_MAX_HEIGHT FB_HEIGHT
|
|
|
|
const char *mednafen_core_str = MEDNAFEN_CORE_NAME;
|
|
|
|
static void check_system_specs(void)
|
|
{
|
|
unsigned level = 0;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
|
|
}
|
|
|
|
void retro_init(void)
|
|
{
|
|
struct retro_log_callback log;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
|
|
log_cb = log.log;
|
|
else
|
|
log_cb = NULL;
|
|
|
|
MDFNI_InitializeModule();
|
|
|
|
const char *dir = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir)
|
|
{
|
|
retro_base_directory = dir;
|
|
// Make sure that we don't have any lingering slashes, etc, as they break Windows.
|
|
size_t last = retro_base_directory.find_last_not_of("/\\");
|
|
if (last != std::string::npos)
|
|
last++;
|
|
|
|
retro_base_directory = retro_base_directory.substr(0, last);
|
|
|
|
MDFNI_Initialize(retro_base_directory.c_str());
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Add proper fallback */
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_WARN, "System directory is not defined. Fallback on using same dir as ROM for system directory later ...\n");
|
|
failed_init = true;
|
|
}
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir)
|
|
{
|
|
// If save directory is defined use it, otherwise use system directory
|
|
retro_save_directory = *dir ? dir : retro_base_directory;
|
|
// Make sure that we don't have any lingering slashes, etc, as they break Windows.
|
|
size_t last = retro_save_directory.find_last_not_of("/\\");
|
|
if (last != std::string::npos)
|
|
last++;
|
|
|
|
retro_save_directory = retro_save_directory.substr(0, last);
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Add proper fallback */
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_WARN, "Save directory is not defined. Fallback on using SYSTEM directory ...\n");
|
|
retro_save_directory = retro_base_directory;
|
|
}
|
|
|
|
#if defined(WANT_16BPP) && defined(FRONTEND_SUPPORTS_RGB565)
|
|
enum retro_pixel_format rgb565 = RETRO_PIXEL_FORMAT_RGB565;
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb565) && log_cb)
|
|
log_cb(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n");
|
|
#endif
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_PERF_INTERFACE, &perf_cb))
|
|
perf_get_cpu_features_cb = perf_cb.get_cpu_features;
|
|
else
|
|
perf_get_cpu_features_cb = NULL;
|
|
|
|
check_system_specs();
|
|
}
|
|
|
|
void retro_reset(void)
|
|
{
|
|
DoSimpleCommand(MDFN_MSC_RESET);
|
|
}
|
|
|
|
bool retro_load_game_special(unsigned, const struct retro_game_info *, size_t)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static void set_volume (uint32_t *ptr, unsigned number)
|
|
{
|
|
switch(number)
|
|
{
|
|
default:
|
|
*ptr = number;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void check_variables(void)
|
|
{
|
|
struct retro_variable var = {0};
|
|
|
|
var.key = "gba_hle";
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
setting_gba_hle = 1;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
setting_gba_hle = 0;
|
|
}
|
|
}
|
|
|
|
#define MAX_PLAYERS 1
|
|
#define MAX_BUTTONS 11
|
|
static uint16_t input_buf;
|
|
|
|
static void hookup_ports(bool force)
|
|
{
|
|
if (initial_ports_hookup && !force)
|
|
return;
|
|
|
|
// Possible endian bug ...
|
|
SetInput(0, "gamepad", &input_buf);
|
|
|
|
initial_ports_hookup = true;
|
|
}
|
|
|
|
bool retro_load_game(const struct retro_game_info *info)
|
|
{
|
|
if (!info || failed_init)
|
|
return false;
|
|
|
|
struct retro_input_descriptor desc[] = {
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
{ 0 },
|
|
};
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
|
|
|
|
#ifdef WANT_32BPP
|
|
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
|
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_ERROR, "Pixel format XRGB8888 not supported by platform, cannot use %s.\n", MEDNAFEN_CORE_NAME);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
overscan = false;
|
|
environ_cb(RETRO_ENVIRONMENT_GET_OVERSCAN, &overscan);
|
|
|
|
set_basename(info->path);
|
|
|
|
check_variables();
|
|
|
|
game = MDFNI_LoadGame(MEDNAFEN_CORE_NAME_MODULE, info->path);
|
|
if (!game)
|
|
return false;
|
|
|
|
MDFN_PixelFormat pix_fmt(MDFN_COLORSPACE_RGB, 16, 8, 0, 24);
|
|
memset(&last_pixel_format, 0, sizeof(MDFN_PixelFormat));
|
|
|
|
surf = new MDFN_Surface(NULL, FB_WIDTH, FB_HEIGHT, FB_WIDTH, pix_fmt);
|
|
|
|
#ifdef NEED_DEINTERLACER
|
|
PrevInterlaced = false;
|
|
deint.ClearState();
|
|
#endif
|
|
|
|
hookup_ports(true);
|
|
|
|
check_variables();
|
|
|
|
struct retro_memory_descriptor descs[7];
|
|
struct retro_memory_map retromap;
|
|
|
|
memset(descs, 0, sizeof(descs));
|
|
|
|
descs[0].ptr = internalRAM; // Internal working RAM
|
|
descs[0].start = 0x03000000;
|
|
descs[0].len = 0x8000;
|
|
descs[0].select = 0xFF000000;
|
|
|
|
descs[1].ptr = workRAM; // Working RAM
|
|
descs[1].start = 0x02000000;
|
|
descs[1].len = 0x40000;
|
|
descs[1].select = 0xFF000000;
|
|
|
|
// TODO: if SRAM is flash, use start=0 addrspace="S" instead
|
|
descs[2].ptr = flashSaveMemory; // Save RAM
|
|
descs[2].start = 0x0E000000;
|
|
descs[2].len = flashSize;
|
|
descs[2].select = 0;
|
|
|
|
descs[3].ptr = vram; // VRAM
|
|
descs[3].start = 0x06000000;
|
|
descs[3].len = 0x20000;
|
|
descs[3].select = 0xFF000000;
|
|
|
|
descs[4].ptr = paletteRAM; // Palettes
|
|
descs[4].start = 0x05000000;
|
|
descs[4].len = 0x400;
|
|
descs[4].select = 0xFF000000;
|
|
|
|
descs[5].ptr = oam; // OAM
|
|
descs[5].start = 0x07000000;
|
|
descs[5].len = 0x400;
|
|
descs[5].select = 0xFF000000;
|
|
|
|
descs[6].ptr = ioMem; // I/O
|
|
descs[6].start = 0x04000000;
|
|
descs[6].len = 0x400;
|
|
descs[6].select = 0;
|
|
|
|
retromap.descriptors = descs;
|
|
retromap.num_descriptors = sizeof(descs) / sizeof(*descs);
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &retromap);
|
|
|
|
bool retroarchievement = true;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &retroarchievement);
|
|
|
|
return game;
|
|
}
|
|
|
|
void retro_unload_game()
|
|
{
|
|
if (!game)
|
|
return;
|
|
|
|
MDFNI_CloseGame();
|
|
}
|
|
|
|
|
|
|
|
// Hardcoded for PSX. No reason to parse lots of structures ...
|
|
// See mednafen/psx/input/gamepad.cpp
|
|
static void update_input(void)
|
|
{
|
|
input_buf = 0;
|
|
size_t map_size = 10;
|
|
static unsigned map[] = {
|
|
RETRO_DEVICE_ID_JOYPAD_A, //A button
|
|
RETRO_DEVICE_ID_JOYPAD_B, //B button
|
|
RETRO_DEVICE_ID_JOYPAD_SELECT,
|
|
RETRO_DEVICE_ID_JOYPAD_START,
|
|
RETRO_DEVICE_ID_JOYPAD_RIGHT,
|
|
RETRO_DEVICE_ID_JOYPAD_LEFT,
|
|
RETRO_DEVICE_ID_JOYPAD_UP,
|
|
RETRO_DEVICE_ID_JOYPAD_DOWN,
|
|
RETRO_DEVICE_ID_JOYPAD_R,
|
|
RETRO_DEVICE_ID_JOYPAD_L,
|
|
};
|
|
|
|
for (unsigned i = 0; i < map_size; i++)
|
|
input_buf |= map[i] != -1u &&
|
|
input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, map[i]) ? (1 << i) : 0;
|
|
|
|
#ifdef MSB_FIRST
|
|
union {
|
|
uint8_t b[2];
|
|
uint16_t s;
|
|
} u;
|
|
u.s = input_buf;
|
|
input_buf = u.b[0] | u.b[1] << 8;
|
|
#endif
|
|
}
|
|
|
|
static uint64_t video_frames, audio_frames;
|
|
|
|
|
|
void retro_run()
|
|
{
|
|
input_poll_cb();
|
|
|
|
update_input();
|
|
|
|
static int16_t sound_buf[0x10000];
|
|
static MDFN_Rect rects[FB_MAX_HEIGHT];
|
|
rects[0].w = ~0;
|
|
|
|
EmulateSpecStruct spec = {0};
|
|
spec.surface = surf;
|
|
spec.SoundRate = 44100;
|
|
spec.SoundBuf = sound_buf;
|
|
spec.LineWidths = rects;
|
|
spec.SoundBufMaxSize = sizeof(sound_buf) / 2;
|
|
spec.SoundVolume = 1.0;
|
|
spec.soundmultiplier = 1.0;
|
|
spec.SoundBufSize = 0;
|
|
spec.VideoFormatChanged = false;
|
|
spec.SoundFormatChanged = false;
|
|
|
|
if (memcmp(&last_pixel_format, &spec.surface->format, sizeof(MDFN_PixelFormat)))
|
|
{
|
|
spec.VideoFormatChanged = TRUE;
|
|
|
|
last_pixel_format = spec.surface->format;
|
|
}
|
|
|
|
if (spec.SoundRate != last_sound_rate)
|
|
{
|
|
spec.SoundFormatChanged = true;
|
|
last_sound_rate = spec.SoundRate;
|
|
}
|
|
|
|
Emulate(&spec);
|
|
|
|
#ifdef NEED_DEINTERLACER
|
|
if (spec.InterlaceOn)
|
|
{
|
|
if (!PrevInterlaced)
|
|
deint.ClearState();
|
|
|
|
deint.Process(spec.surface, spec.DisplayRect, spec.LineWidths, spec.InterlaceField);
|
|
|
|
PrevInterlaced = true;
|
|
|
|
spec.InterlaceOn = false;
|
|
spec.InterlaceField = 0;
|
|
}
|
|
else
|
|
PrevInterlaced = false;
|
|
#endif
|
|
|
|
int16 *const SoundBuf = spec.SoundBuf + spec.SoundBufSizeALMS * 2;
|
|
int32 SoundBufSize = spec.SoundBufSize - spec.SoundBufSizeALMS;
|
|
const int32 SoundBufMaxSize = spec.SoundBufMaxSize - spec.SoundBufSizeALMS;
|
|
|
|
spec.SoundBufSize = spec.SoundBufSizeALMS + SoundBufSize;
|
|
|
|
unsigned width = spec.DisplayRect.w;
|
|
unsigned height = spec.DisplayRect.h;
|
|
|
|
#if defined(WANT_32BPP)
|
|
const uint32_t *pix = surf->pixels;
|
|
video_cb(pix, width, height, FB_WIDTH << 2);
|
|
#elif defined(WANT_16BPP)
|
|
const uint16_t *pix = surf->pixels16;
|
|
video_cb(pix, width, height, FB_WIDTH << 1);
|
|
#endif
|
|
|
|
video_frames++;
|
|
audio_frames += spec.SoundBufSize;
|
|
|
|
audio_batch_cb(spec.SoundBuf, spec.SoundBufSize);
|
|
|
|
bool updated = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
|
check_variables();
|
|
}
|
|
|
|
void retro_get_system_info(struct retro_system_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
info->library_name = MEDNAFEN_CORE_NAME;
|
|
#ifndef GIT_VERSION
|
|
#define GIT_VERSION ""
|
|
#endif
|
|
info->library_version = MEDNAFEN_CORE_VERSION GIT_VERSION;
|
|
info->need_fullpath = true;
|
|
info->valid_extensions = MEDNAFEN_CORE_EXTENSIONS;
|
|
info->block_extract = false;
|
|
}
|
|
|
|
void retro_get_system_av_info(struct retro_system_av_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
info->timing.fps = MEDNAFEN_CORE_TIMING_FPS;
|
|
info->timing.sample_rate = 44100;
|
|
info->geometry.base_width = MEDNAFEN_CORE_GEOMETRY_BASE_W;
|
|
info->geometry.base_height = MEDNAFEN_CORE_GEOMETRY_BASE_H;
|
|
info->geometry.max_width = MEDNAFEN_CORE_GEOMETRY_MAX_W;
|
|
info->geometry.max_height = MEDNAFEN_CORE_GEOMETRY_MAX_H;
|
|
info->geometry.aspect_ratio = MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO;
|
|
}
|
|
|
|
void retro_deinit()
|
|
{
|
|
delete surf;
|
|
surf = NULL;
|
|
|
|
if (log_cb)
|
|
{
|
|
log_cb(RETRO_LOG_INFO, "[%s]: Samples / Frame: %.5f\n",
|
|
mednafen_core_str, (double)audio_frames / video_frames);
|
|
log_cb(RETRO_LOG_INFO, "[%s]: Estimated FPS: %.5f\n",
|
|
mednafen_core_str, (double)video_frames * 44100 / audio_frames);
|
|
}
|
|
}
|
|
|
|
unsigned retro_get_region(void)
|
|
{
|
|
return RETRO_REGION_NTSC; // FIXME: Regions for other cores.
|
|
}
|
|
|
|
unsigned retro_api_version(void)
|
|
{
|
|
return RETRO_API_VERSION;
|
|
}
|
|
|
|
void retro_set_controller_port_device(unsigned in_port, unsigned device)
|
|
{
|
|
}
|
|
|
|
void retro_set_environment(retro_environment_t cb)
|
|
{
|
|
environ_cb = cb;
|
|
|
|
static const struct retro_variable vars[] = {
|
|
{ "gba_hle", "HLE bios emulation; enabled|disabled" },
|
|
{ NULL, NULL },
|
|
};
|
|
cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)vars);
|
|
}
|
|
|
|
void retro_set_audio_sample(retro_audio_sample_t cb)
|
|
{
|
|
audio_cb = cb;
|
|
}
|
|
|
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
|
{
|
|
audio_batch_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_poll(retro_input_poll_t cb)
|
|
{
|
|
input_poll_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_state(retro_input_state_t cb)
|
|
{
|
|
input_state_cb = cb;
|
|
}
|
|
|
|
void retro_set_video_refresh(retro_video_refresh_t cb)
|
|
{
|
|
video_cb = cb;
|
|
}
|
|
|
|
static size_t serialize_size;
|
|
|
|
size_t retro_serialize_size(void)
|
|
{
|
|
StateMem st;
|
|
|
|
st.data = NULL;
|
|
st.loc = 0;
|
|
st.len = 0;
|
|
st.malloced = 0;
|
|
st.initial_malloc = 0;
|
|
|
|
if (!MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL))
|
|
return 0;
|
|
|
|
free(st.data);
|
|
|
|
return serialize_size = st.len;
|
|
}
|
|
|
|
bool retro_serialize(void *data, size_t size)
|
|
{
|
|
StateMem st;
|
|
bool ret = false;
|
|
uint8_t *_dat = (uint8_t*)malloc(size);
|
|
|
|
if (!_dat)
|
|
return false;
|
|
|
|
/* Mednafen can realloc the buffer so we need to ensure this is safe. */
|
|
st.data = _dat;
|
|
st.loc = 0;
|
|
st.len = 0;
|
|
st.malloced = size;
|
|
st.initial_malloc = 0;
|
|
|
|
ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL);
|
|
|
|
memcpy(data, st.data, size);
|
|
free(st.data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool retro_unserialize(const void *data, size_t size)
|
|
{
|
|
StateMem st;
|
|
memset(&st, 0, sizeof(st));
|
|
st.data = (uint8_t*)data;
|
|
st.len = size;
|
|
|
|
return MDFNSS_LoadSM(&st, 0, 0);
|
|
}
|
|
|
|
void *retro_get_memory_data(unsigned)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
size_t retro_get_memory_size(unsigned)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void retro_cheat_reset(void)
|
|
{}
|
|
|
|
void retro_cheat_set(unsigned, bool, const char *)
|
|
{}
|
|
|
|
#ifdef _WIN32
|
|
static void sanitize_path(std::string &path)
|
|
{
|
|
size_t size = path.size();
|
|
for (size_t i = 0; i < size; i++)
|
|
if (path[i] == '/')
|
|
path[i] = '\\';
|
|
}
|
|
#endif
|
|
|
|
// Use a simpler approach to make sure that things go right for libretro.
|
|
std::string MDFN_MakeFName(MakeFName_Type type, int id1, const char *cd1)
|
|
{
|
|
char slash;
|
|
#ifdef _WIN32
|
|
slash = '\\';
|
|
#else
|
|
slash = '/';
|
|
#endif
|
|
std::string ret;
|
|
switch (type)
|
|
{
|
|
case MDFNMKF_SAV:
|
|
ret = retro_save_directory +slash + retro_base_name +
|
|
std::string(".") +
|
|
#ifndef _XBOX
|
|
md5_context::asciistr(MDFNGameInfo->MD5, 0) + std::string(".") +
|
|
#endif
|
|
std::string(cd1);
|
|
break;
|
|
case MDFNMKF_FIRMWARE:
|
|
ret = retro_base_directory + slash + std::string(cd1);
|
|
#ifdef _WIN32
|
|
sanitize_path(ret); // Because Windows path handling is mongoloid.
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "MDFN_MakeFName: %s\n", ret.c_str());
|
|
return ret;
|
|
}
|
|
|
|
void MDFND_DispMessage(unsigned char *str)
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "%s\n", str);
|
|
}
|
|
|
|
void MDFND_Message(const char *str)
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "%s\n", str);
|
|
}
|
|
|
|
void MDFND_MidSync(const EmulateSpecStruct *)
|
|
{}
|
|
|
|
void MDFN_MidLineUpdate(EmulateSpecStruct *espec, int y)
|
|
{
|
|
//MDFND_MidLineUpdate(espec, y);
|
|
}
|
|
|
|
void MDFND_PrintError(const char* err)
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_ERROR, "%s\n", err);
|
|
}
|
|
|
|
MDFNGI *MDFNGameInfo = NULL;
|
|
|
|
#if defined(WANT_NES_EMU)
|
|
extern MDFNGI EmulatedNES;
|
|
#define MDFNGI_CORE &EmulatedNES
|
|
#elif defined WANT_SNES_EMU
|
|
extern MDFNGI EmulatedSNES;
|
|
#define MDFNGI_CORE &EmulatedSNES
|
|
extern MDFNGI EmulatedGB;
|
|
#elif defined WANT_GB_EMU
|
|
#define MDFNGI_CORE &EmulatedGB
|
|
#elif defined WANT_GBA_EMU
|
|
extern MDFNGI EmulatedGBA;
|
|
#define MDFNGI_CORE &EmulatedGBA
|
|
#elif defined WANT_PCE_EMU
|
|
extern MDFNGI EmulatedPCE;
|
|
#define MDFNGI_CORE &EmulatedPCE
|
|
#elif defined WANT_LYNX_EMU
|
|
extern MDFNGI EmulatedLynx;
|
|
#define MDFNGI_CORE &EmulatedLynx
|
|
#elif defined WANT_MD_EMU
|
|
extern MDFNGI EmulatedMD;
|
|
#define MDFNGI_CORE &EmulatedMD
|
|
#elif defined WANT_PCFX_EMU
|
|
extern MDFNGI EmulatedPCFX;
|
|
#define MDFNGI_CORE &EmulatedPCFX
|
|
#elif defined WANT_SMS_EMU
|
|
extern MDFNGI EmulatedSMS;
|
|
#define MDFNGI_CORE &EmulatedSMS
|
|
#elif defined(WANT_SMS_EMU) && defined(WANT_GG_EMU)
|
|
extern MDFNGI EmulatedGG;
|
|
#define MDFNGI_CORE &EmulatedGG
|
|
#endif
|
|
|
|
|
|
/* forward declarations */
|
|
extern void MDFND_DispMessage(unsigned char *str);
|
|
|
|
void MDFN_DispMessage(const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap,format);
|
|
char *msg = new char[4096];
|
|
|
|
vsnprintf(msg, 4096, format,ap);
|
|
va_end(ap);
|
|
|
|
MDFND_DispMessage((UTF8*)msg);
|
|
}
|
|
|
|
void MDFN_ResetMessages(void)
|
|
{
|
|
MDFND_DispMessage(NULL);
|
|
}
|
|
|
|
|
|
MDFNGI *MDFNI_LoadGame(const char *force_module, const char *name)
|
|
{
|
|
MDFNFILE GameFile;
|
|
std::vector<FileExtensionSpecStruct> valid_iae;
|
|
MDFNGameInfo = MDFNGI_CORE;
|
|
|
|
MDFN_printf(_("Loading %s...\n"),name);
|
|
|
|
MDFN_indent(1);
|
|
|
|
// Construct a NULL-delimited list of known file extensions for MDFN_fopen()
|
|
const FileExtensionSpecStruct *curexts = KnownExtensions;
|
|
|
|
while(curexts->extension && curexts->description)
|
|
{
|
|
valid_iae.push_back(*curexts);
|
|
curexts++;
|
|
}
|
|
|
|
if(!GameFile.Open(name, &valid_iae[0], _("game")))
|
|
{
|
|
MDFNGameInfo = NULL;
|
|
return 0;
|
|
}
|
|
|
|
MDFN_printf(_("Using module: gba\n\n"));
|
|
MDFN_indent(1);
|
|
|
|
//
|
|
// Load per-game settings
|
|
//
|
|
// Maybe we should make a "pgcfg" subdir, and automatically load all files in it?
|
|
// End load per-game settings
|
|
//
|
|
|
|
if(Load(name, &GameFile) <= 0)
|
|
{
|
|
GameFile.Close();
|
|
MDFN_indent(-2);
|
|
MDFNGameInfo = NULL;
|
|
return(0);
|
|
}
|
|
|
|
MDFN_LoadGameCheats(NULL);
|
|
MDFNMP_InstallReadPatches();
|
|
|
|
MDFN_ResetMessages(); // Save state, status messages, etc.
|
|
|
|
MDFN_indent(-2);
|
|
|
|
return(MDFNGameInfo);
|
|
}
|
|
|
|
void MDFNI_CloseGame(void)
|
|
{
|
|
if(!MDFNGameInfo)
|
|
return;
|
|
|
|
MDFN_FlushGameCheats(0);
|
|
|
|
CloseGame();
|
|
|
|
MDFNMP_Kill();
|
|
|
|
MDFNGameInfo = NULL;
|
|
}
|
|
|
|
bool MDFNI_InitializeModule(void)
|
|
{
|
|
return(1);
|
|
}
|
|
|
|
int MDFNI_Initialize(const char *basedir)
|
|
{
|
|
return(1);
|
|
}
|
|
|
|
static int curindent = 0;
|
|
|
|
void MDFN_indent(int indent)
|
|
{
|
|
curindent += indent;
|
|
}
|
|
|
|
static uint8 lastchar = 0;
|
|
|
|
void MDFN_printf(const char *format, ...)
|
|
{
|
|
char *format_temp;
|
|
char *temp;
|
|
unsigned int x, newlen;
|
|
|
|
va_list ap;
|
|
va_start(ap,format);
|
|
|
|
|
|
// First, determine how large our format_temp buffer needs to be.
|
|
uint8 lastchar_backup = lastchar; // Save lastchar!
|
|
for(newlen=x=0;x<strlen(format);x++)
|
|
{
|
|
if(lastchar == '\n' && format[x] != '\n')
|
|
{
|
|
int y;
|
|
for(y=0;y<curindent;y++)
|
|
newlen++;
|
|
}
|
|
newlen++;
|
|
lastchar = format[x];
|
|
}
|
|
|
|
format_temp = (char *)malloc(newlen + 1); // Length + NULL character, duh
|
|
|
|
// Now, construct our format_temp string
|
|
lastchar = lastchar_backup; // Restore lastchar
|
|
for(newlen=x=0;x<strlen(format);x++)
|
|
{
|
|
if(lastchar == '\n' && format[x] != '\n')
|
|
{
|
|
int y;
|
|
for(y=0;y<curindent;y++)
|
|
format_temp[newlen++] = ' ';
|
|
}
|
|
format_temp[newlen++] = format[x];
|
|
lastchar = format[x];
|
|
}
|
|
|
|
format_temp[newlen] = 0;
|
|
|
|
temp = new char[4096];
|
|
vsnprintf(temp, 4096, format_temp, ap);
|
|
free(format_temp);
|
|
|
|
MDFND_Message(temp);
|
|
free(temp);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
void MDFN_PrintError(const char *format, ...)
|
|
{
|
|
char *temp;
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
temp = new char[4096];
|
|
vsnprintf(temp, 4096, format, ap);
|
|
MDFND_PrintError(temp);
|
|
free(temp);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
void MDFN_DebugPrintReal(const char *file, const int line, const char *format, ...)
|
|
{
|
|
char *temp;
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
temp = new char[4096];
|
|
vsnprintf(temp, 4096, format, ap);
|
|
fprintf(stderr, "%s:%d %s\n", file, line, temp);
|
|
free(temp);
|
|
|
|
va_end(ap);
|
|
}
|