desmume/src/utils/task_vita.cpp 000664 001750 001750 00000007114 12755534123 017773 0 ustar 00sergio sergio 000000 000000 /*
Copyright (C) 2009-2015 DeSmuME team
Copyright (C) 2015 Sergi Granell (xerpi)
This file 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 of the License, or
(at your option) any later version.
This file 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 the this software. If not, see .
*/
#include
#include
#include "../utils/task.h"
#include "debug.h"
class Task::Impl {
private:
SceUID _thread;
bool _isThreadRunning;
public:
Impl();
~Impl();
void start();
void execute(const TWork &work, void *param);
void* finish();
void shutdown();
//slock_t *mutex;
SceUID condWork;
TWork workFunc;
void *workFuncParam;
void *ret;
bool exitThread;
};
#define EVENT_WORK_START 1
#define EVENT_WORK_END 2
static int taskProc(SceSize args, void *argp)
{
Task::Impl *ctx = *(Task::Impl **)argp;
do {
while (!ctx->workFunc && !ctx->exitThread)
sceKernelWaitEventFlag(ctx->condWork, EVENT_WORK_START,
SCE_EVENT_WAITAND, NULL, NULL);
sceKernelClearEventFlag(ctx->condWork, ~EVENT_WORK_START);
if (ctx->workFunc)
ctx->ret = ctx->workFunc(ctx->workFuncParam);
else
ctx->ret = NULL;
ctx->workFunc = NULL;
sceKernelSetEventFlag(ctx->condWork, EVENT_WORK_END);
} while(!ctx->exitThread);
return 0;
}
Task::Impl::Impl()
{
_isThreadRunning = false;
workFunc = NULL;
workFuncParam = NULL;
ret = NULL;
exitThread = false;
condWork = sceKernelCreateEventFlag("desmume_cond_work", 0, 0, NULL);
}
Task::Impl::~Impl()
{
shutdown();
sceKernelDeleteEventFlag(condWork);
}
void Task::Impl::start()
{
if (this->_isThreadRunning)
{
return;
}
this->workFunc = NULL;
this->workFuncParam = NULL;
this->ret = NULL;
this->exitThread = false;
this->_thread = sceKernelCreateThread("desmume_task", taskProc,
0x10000100, 0x1000, 0, 0, NULL);
sceKernelClearEventFlag(condWork, ~(EVENT_WORK_START | EVENT_WORK_END));
Task::Impl *_this = this;
sceKernelStartThread(this->_thread, sizeof(_this), &_this);
this->_isThreadRunning = true;
}
void Task::Impl::execute(const TWork &work, void *param)
{
if (!work || !this->_isThreadRunning)
return;
this->workFunc = work;
this->workFuncParam = param;
sceKernelSetEventFlag(condWork, EVENT_WORK_START);
}
void* Task::Impl::finish()
{
void *returnValue = NULL;
if (!this->_isThreadRunning)
goto end;
while (this->workFunc != NULL)
sceKernelWaitEventFlag(condWork, EVENT_WORK_END,
SCE_EVENT_WAITAND, NULL, NULL);
sceKernelClearEventFlag(condWork, ~EVENT_WORK_END);
returnValue = this->ret;
end:
return returnValue;
}
void Task::Impl::shutdown()
{
if (!this->_isThreadRunning)
return;
this->workFunc = NULL;
this->exitThread = true;
sceKernelSetEventFlag(condWork, EVENT_WORK_START | EVENT_WORK_END);
sceKernelWaitThreadEnd(_thread, NULL, NULL);
sceKernelDeleteThread(_thread);
this->_isThreadRunning = false;
}
void Task::start() { impl->start(); }
void Task::shutdown() { impl->shutdown(); }
Task::Task() : impl(new Task::Impl()) {}
Task::~Task() { delete impl; }
void Task::execute(const TWork &work, void* param) { impl->execute(work,param); }
void* Task::finish() { return impl->finish(); }
desmume/src/libretro-common/lists/ 000700 001750 001750 00000000000 12756420131 020363 5 ustar 00sergio sergio 000000 000000 desmume/src/libretro-common/include/glsm/glsm.h 000664 001750 001750 00000011446 12755534123 022755 0 ustar 00sergio sergio 000000 000000 /* Copyright (C) 2010-2016 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this libretro SDK code part (glsm.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBRETRO_SDK_GLSM_H
#define LIBRETRO_SDK_GLSM_H
#include
#include
#include
#include
RETRO_BEGIN_DECLS
#ifdef HAVE_OPENGLES2
typedef GLfloat GLdouble;
typedef GLclampf GLclampd;
#endif
#if defined(HAVE_OPENGLES2)
#define RARCH_GL_RENDERBUFFER GL_RENDERBUFFER
#define RARCH_GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_OES
#define RARCH_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT
#define RARCH_GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT
#elif defined(OSX_PPC)
#define RARCH_GL_RENDERBUFFER GL_RENDERBUFFER_EXT
#define RARCH_GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_EXT
#define RARCH_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT_EXT
#define RARCH_GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT_EXT
#elif defined(HAVE_PSGL)
#define RARCH_GL_RENDERBUFFER GL_RENDERBUFFER_OES
#define RARCH_GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_SCE
#define RARCH_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT_OES
#define RARCH_GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT_OES
#else
#define RARCH_GL_RENDERBUFFER GL_RENDERBUFFER
#define RARCH_GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8
#define RARCH_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT
#define RARCH_GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT
#endif
#if defined(HAVE_PSGL)
#define RARCH_GL_FRAMEBUFFER GL_FRAMEBUFFER_OES
#define RARCH_GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_OES
#define RARCH_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT
#elif defined(OSX_PPC)
#define RARCH_GL_FRAMEBUFFER GL_FRAMEBUFFER_EXT
#define RARCH_GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_EXT
#define RARCH_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT
#else
#define RARCH_GL_FRAMEBUFFER GL_FRAMEBUFFER
#define RARCH_GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE
#define RARCH_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0
#endif
#ifndef GL_FOG
#define GL_FOG 0x0B60
#endif
#ifndef GL_ALPHA_TEST
#define GL_ALPHA_TEST 0x0BC0
#endif
#ifndef GL_CLIP_DISTANCE0
#define GL_CLIP_DISTANCE0 0x3000
#endif
#define MAX_ATTRIB 8
enum
{
SGL_DEPTH_TEST = 0,
SGL_BLEND,
SGL_POLYGON_OFFSET_FILL,
SGL_FOG,
SGL_CULL_FACE,
SGL_ALPHA_TEST,
SGL_SCISSOR_TEST,
SGL_STENCIL_TEST,
#if !defined(HAVE_OPENGLES)
SGL_DEPTH_CLAMP,
SGL_CLIP_DISTANCE0,
#endif
SGL_DITHER,
SGL_SAMPLE_ALPHA_TO_COVERAGE,
SGL_SAMPLE_COVERAGE,
#ifndef HAVE_OPENGLES
SGL_COLOR_LOGIC_OP,
#endif
SGL_CAP_MAX
};
enum glsm_state_ctl
{
GLSM_CTL_NONE = 0,
GLSM_CTL_STATE_SETUP,
GLSM_CTL_STATE_BIND,
GLSM_CTL_STATE_UNBIND,
GLSM_CTL_STATE_CONTEXT_RESET,
GLSM_CTL_STATE_CONTEXT_DESTROY,
GLSM_CTL_STATE_CONTEXT_INIT,
GLSM_CTL_IS_IMM_VBO,
GLSM_CTL_SET_IMM_VBO,
GLSM_CTL_UNSET_IMM_VBO,
GLSM_CTL_IMM_VBO_DISABLE,
GLSM_CTL_IMM_VBO_DRAW,
GLSM_CTL_PROC_ADDRESS_GET
};
typedef bool (*glsm_imm_vbo_draw)(void *);
typedef bool (*glsm_imm_vbo_disable)(void *);
typedef bool (*glsm_framebuffer_lock)(void *);
typedef struct glsm_ctx_proc_address
{
retro_get_proc_address_t addr;
} glsm_ctx_proc_address_t;
typedef struct glsm_ctx_params
{
glsm_framebuffer_lock framebuffer_lock;
glsm_imm_vbo_draw imm_vbo_draw;
glsm_imm_vbo_disable imm_vbo_disable;
retro_hw_context_reset_t context_reset;
retro_hw_context_reset_t context_destroy;
retro_environment_t environ_cb;
bool stencil;
unsigned major;
unsigned minor;
} glsm_ctx_params_t;
GLuint glsm_get_current_framebuffer(void);
bool glsm_ctl(enum glsm_state_ctl state, void *data);
RETRO_END_DECLS
#endif
desmume/src/libretro-common/include/gfx/math/vector_2.h 000664 001750 001750 00000003351 12755534123 024305 0 ustar 00sergio sergio 000000 000000 /* Copyright (C) 2010-2016 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vector_2.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_GFX_MATH_VECTOR_3_H__
#define __LIBRETRO_SDK_GFX_MATH_VECTOR_3_H__
#include
typedef float vec2_t[2];
float vec2_dot(const float *a, const float *b);
float vec2_cross(const float *a, const float *b) ;
void vec2_add(float *dst, const float *src);
void vec2_subtract(float *dst, const float *src);
void vec2_copy(float *dst, const float *src);
#endif
desmume/src/NDSSystem.cpp 000664 001750 001750 00000234647 12755534123 016514 0 ustar 00sergio sergio 000000 000000 /*
Copyright (C) 2006 yopyop
Copyright (C) 2008-2015 DeSmuME team
This file 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 of the License, or
(at your option) any later version.
This file 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 the this software. If not, see .
*/
#include "NDSSystem.h"
#include
#include
#include
#include
#include
extern unsigned long crc32(unsigned long, const unsigned char*,unsigned int);
#include "utils/dlditool.h"
#include "utils/decrypt/decrypt.h"
#include "utils/decrypt/crc.h"
#include "utils/advanscene.h"
#include "utils/task.h"
#include "utils/bits.h"
#include "common.h"
#include "armcpu.h"
#include "render3D.h"
#include "MMU.h"
#include "gfx3d.h"
#include "GPU.h"
#include "cp15.h"
#include "bios.h"
#include "debug.h"
#include "cheatSystem.h"
#include "movie.h"
#include "emufile.h"
#ifdef DEBUG
#include "Disassembler.h"
#endif
#include "FIFO.h"
#include "readwrite.h"
#include "registers.h"
#ifdef DEBUG
#include "debug.h"
#endif
#include "driver.h"
#include "firmware.h"
#include "version.h"
#include "path.h"
#include "slot1.h"
#include "slot2.h"
#include "SPU.h"
#include "wifi.h"
//int xxctr=0;
//#define LOG_ARM9
//#define LOG_ARM7
//#define dolog (currFrameCounter>30)
bool dolog = false;
//#define LOG_TO_FILE
//#define LOG_TO_FILE_REGS
//===============================================================
FILE *fp_dis7 = NULL;
FILE *fp_dis9 = NULL;
PathInfo path;
TCommonSettings CommonSettings;
static BaseDriver _stub_driver;
BaseDriver* driver = &_stub_driver;
static BOOL LidClosed = FALSE;
static u8 countLid = 0;
GameInfo gameInfo;
NDSSystem nds;
CFIRMWARE *firmware = NULL;
using std::min;
using std::max;
bool singleStep;
bool nds_debug_continuing[2];
TSCalInfo TSCal;
extern "C" int DLDI_tryPatch(void* data, size_t size, unsigned int device);
void Desmume_InitOnce()
{
static bool initOnce = false;
if(initOnce) return;
initOnce = true;
}
int NDS_GetCPUCoreCount(void)
{
int amount = cpu_features_get_core_amount();
return amount;
}
void NDS_SetupDefaultFirmware()
{
NDS_FillDefaultFirmwareConfigData(&CommonSettings.fw_config);
}
void NDS_RunAdvansceneAutoImport()
{
if(CommonSettings.run_advanscene_import != "")
{
std::string fname = CommonSettings.run_advanscene_import;
std::string fname_out = fname + ".ddb";
EMUFILE_FILE outf(fname_out,"wb");
u32 ret = advsc.convertDB(fname.c_str(),&outf);
if(ret == 0)
exit(0);
else exit(1);
}
}
int NDS_Init()
{
nds.idleFrameCounter = 0;
memset(nds.runCycleCollector,0,sizeof(nds.runCycleCollector));
MMU_Init();
//got to print this somewhere..
printf("%s\n", EMU_DESMUME_NAME_AND_VERSION());
{
char buf[PATH_MAX_LENGTH];
memset(buf, 0, sizeof(buf));
strcpy(buf, path.pathToModule);
strcat(buf, "desmume.ddb"); // DeSmuME database :)
advsc.setDatabase(buf);
//why is this done here? shitty engineering. not intended.
NDS_RunAdvansceneAutoImport();
}
armcpu_new(&NDS_ARM9,0);
NDS_ARM9.SetBaseMemoryInterface(&arm9_base_memory_iface);
NDS_ARM9.SetBaseMemoryInterfaceData(NULL);
NDS_ARM9.ResetMemoryInterfaceToBase();
armcpu_new(&NDS_ARM7,1);
NDS_ARM7.SetBaseMemoryInterface(&arm7_base_memory_iface);
NDS_ARM7.SetBaseMemoryInterfaceData(NULL);
NDS_ARM7.ResetMemoryInterfaceToBase();
if (GPU != NULL)
delete GPU;
GPU = new GPUSubsystem;
if (SPU_Init(SNDCORE_DUMMY, 740) != 0)
return -1;
WIFI_Init() ;
cheats = new CHEATS();
return 0;
}
void NDS_DeInit(void)
{
gameInfo.closeROM();
SPU_DeInit();
delete GPU;
GPU = NULL;
MMU_DeInit();
WIFI_DeInit();
delete cheats;
cheats = NULL;
#ifdef HAVE_JIT
arm_jit_close();
#endif
#ifdef LOG_ARM7
if (fp_dis7 != NULL)
{
fclose(fp_dis7);
fp_dis7 = NULL;
}
#endif
#ifdef LOG_ARM9
if (fp_dis9 != NULL)
{
fclose(fp_dis9);
fp_dis9 = NULL;
}
#endif
}
NDS_header* NDS_getROMHeader(void)
{
NDS_header *newHeader = new NDS_header;
memcpy(newHeader, &gameInfo.header, sizeof(NDS_header));
return newHeader;
}
#ifdef DEBUG
void debug()
{
//if(NDS_ARM9.R[15]==0x020520DC) emu_halt();
//DSLinux
//if(NDS_ARM9.CPSR.bits.mode == 0) emu_halt();
//if((NDS_ARM9.R[15]&0xFFFFF000)==0) emu_halt();
//if((NDS_ARM9.R[15]==0x0201B4F4)/*&&(NDS_ARM9.R[1]==0x0)*/) emu_halt();
//AOE
//if((NDS_ARM9.R[15]==0x01FFE194)&&(NDS_ARM9.R[0]==0)) emu_halt();
//if((NDS_ARM9.R[15]==0x01FFE134)&&(NDS_ARM9.R[0]==0)) emu_halt();
//BBMAN
//if(NDS_ARM9.R[15]==0x02098B4C) emu_halt();
//if(NDS_ARM9.R[15]==0x02004924) emu_halt();
//if(NDS_ARM9.R[15]==0x02004890) emu_halt();
//if(NDS_ARM9.R[15]==0x0202B800) emu_halt();
//if(NDS_ARM9.R[15]==0x0202B3DC) emu_halt();
//if((NDS_ARM9.R[1]==0x9AC29AC1)&&(!fait)) {emu_halt();fait = TRUE;}
//if(NDS_ARM9.R[1]==0x0400004A) {emu_halt();fait = TRUE;}
/*if(NDS_ARM9.R[4]==0x2E33373C) emu_halt();
if(NDS_ARM9.R[15]==0x02036668) //emu_halt();
{
nds.logcount++;
sprintf(logbuf, "%d %08X", nds.logcount, NDS_ARM9.R[13]);
log::ajouter(logbuf);
if(nds.logcount==89) execute=FALSE;
}*/
//if(NDS_ARM9.instruction==0) emu_halt();
//if((NDS_ARM9.R[15]>>28)) emu_halt();
}
#endif
RomBanner::RomBanner(bool defaultInit)
{
if(!defaultInit) return;
version = 1; //Version (0001h)
crc16 = 0; //CRC16 across entries 020h..83Fh
memset(reserved,0,sizeof(reserved));
memset(bitmap,0,sizeof(bitmap));
memset(palette,0,sizeof(palette));
memset(titles,0,sizeof(titles));
memset(end0xFF,0,sizeof(end0xFF));
}
bool GameInfo::hasRomBanner()
{
if(header.IconOff + sizeof(RomBanner) > romsize)
return false;
else return true;
}
const RomBanner& GameInfo::getRomBanner()
{
return banner;
}
void GameInfo::populate()
{
const char regions_index[] = "JPFSEODIRKHXVWUC";
const char *regions[] = {
"???",
"JPN", // J
"EUR", // P
"FRA", // F
"ESP", // S
"USA", // E
"INT", // O
"NOE", // D
"ITA", // I
"RUS", // R
"KOR", // K
"HOL", // H
"EUU", // X
"EUU", // V
"EUU", // W
"AUS", // U
"CHN", // C
};
memset(ROMserial, 0, sizeof(ROMserial));
memset(ROMname, 0, sizeof(ROMname));
if(isHomebrew())
{
//we can't really make a serial for a homebrew game that hasnt set a game code
strcpy(ROMserial, "Homebrew");
}
else
{
if (isDSiEnhanced())
strcpy(ROMserial,"TWL- -");
else
strcpy(ROMserial,"NTR- -");
memcpy(ROMserial+4, header.gameCode, 4);
u32 regions_num = ARRAY_SIZE(regions);
u32 region = (u32)(std::max(strchr(regions_index,header.gameCode[3]) - regions_index + 1, 0));
if (region < regions_num)
strcat(ROMserial, regions[region]);
else
strcat(ROMserial, "???");
}
//rom name is probably set even in homebrew, so do it regardless
memset(ROMname, 0, sizeof(ROMname));
memcpy(ROMname, header.gameTile, 12);
string_trim_whitespace(ROMname);
/*if(header.IconOff < romsize)
{
u8 num = (T1ReadByte((u8*)romdata, header.IconOff) == 1)?6:7;
for (int i = 0; i < num; i++)
{
wcstombs(ROMfullName[i], (wchar_t *)(romdata+header.IconOff+0x240+(i*0x100)), 0x100);
trim(ROMfullName[i]);
}
}*/
}
bool GameInfo::loadROM(std::string fname, u32 type)
{
//printf("ROM %s\n", CommonSettings.loadToMemory?"loaded to RAM":"stream from disk");
closeROM();
fROM = fopen(fname.c_str(), "rb");
if (!fROM) return false;
headerOffset = (type == ROM_DSGBA)?DSGBA_LOADER_SIZE:0;
fseek(fROM, 0, SEEK_END);
romsize = ftell(fROM) - headerOffset;
fseek(fROM, headerOffset, SEEK_SET);
bool res = (fread(&header, 1, sizeof(header), fROM) == sizeof(header));
if (res)
{
#ifdef MSB_FIRST
//endian swap necessary fields. It would be better if we made accessors for these. I wonder if you could make a macro for a field accessor that would take the bitsize and do the swap on the fly
struct FieldSwap {
const size_t offset;
const size_t bytes;
};
static const FieldSwap fieldSwaps[] = {
{ offsetof(NDS_header,makerCode), 2},
{ offsetof(NDS_header,ARM9src), 4},
{ offsetof(NDS_header,ARM9exe), 4},
{ offsetof(NDS_header,ARM9cpy), 4},
{ offsetof(NDS_header,ARM9binSize), 4},
{ offsetof(NDS_header,ARM7src), 4},
{ offsetof(NDS_header,ARM7exe), 4},
{ offsetof(NDS_header,ARM7cpy), 4},
{ offsetof(NDS_header,ARM7binSize), 4},
{ offsetof(NDS_header,FNameTblOff), 4},
{ offsetof(NDS_header,FNameTblSize), 4},
{ offsetof(NDS_header,FATOff), 4},
{ offsetof(NDS_header,FATSize), 4},
{ offsetof(NDS_header,ARM9OverlayOff), 4},
{ offsetof(NDS_header,ARM9OverlaySize), 4},
{ offsetof(NDS_header,ARM7OverlayOff), 4},
{ offsetof(NDS_header,ARM7OverlaySize), 4},
{ offsetof(NDS_header,normalCmd), 4},
{ offsetof(NDS_header,Key1Cmd), 4},
{ offsetof(NDS_header,IconOff), 4},
{ offsetof(NDS_header,CRC16), 2},
{ offsetof(NDS_header,ROMtimeout), 2},
{ offsetof(NDS_header,ARM9autoload), 4},
{ offsetof(NDS_header,ARM7autoload), 4},
{ offsetof(NDS_header,endROMoffset), 4},
{ offsetof(NDS_header,HeaderSize), 4},
{ offsetof(NDS_header, ARM9module), 4},
{ offsetof(NDS_header, ARM7module), 4},
{ offsetof(NDS_header,logoCRC16), 2},
{ offsetof(NDS_header,headerCRC16), 2},
};
for(size_t i = 0; i < ARRAY_SIZE(fieldSwaps); i++)
{
const u8 *fieldAddr = (u8 *)&header + fieldSwaps[i].offset;
switch(fieldSwaps[i].bytes)
{
case 2:
*(u16 *)fieldAddr = LE_TO_LOCAL_16(*(u16 *)fieldAddr);
break;
case 4:
*(u32 *)fieldAddr = LE_TO_LOCAL_32(*(u32 *)fieldAddr);
break;
}
}
#endif
cardSize = (128 * 1024) << header.cardSize;
if (cardSize < romsize)
{
msgbox->warn("The ROM header is invalid.\nThe device size has been increased to allow for the provided file size.\n");
for (u32 i = header.cardSize; i < 0xF; i++)
{
if (((128 * 1024) << i) >= romsize)
{
header.cardSize = i;
cardSize = (128 * 1024) << i;
break;
}
}
}
mask = (cardSize - 1);
mask |= (mask >>1);
mask |= (mask >>2);
mask |= (mask >>4);
mask |= (mask >>8);
mask |= (mask >>16);
if (type == ROM_NDS)
{
size_t elements_read;
fseek(fROM, 0x4000 + headerOffset, SEEK_SET);
elements_read = fread(&secureArea[0], 1, 0x4000, fROM);
if (elements_read != 0x4000)
printf("Unexpectedly short post-header bit.\n");
}
if (CommonSettings.loadToMemory)
{
fseek(fROM, headerOffset, SEEK_SET);
romdata = new u8[romsize + 4];
if (fread(romdata, 1, romsize, fROM) != romsize)
{
delete [] romdata; romdata = NULL;
romsize = 0;
return false;
}
if(hasRomBanner())
{
memcpy(&banner, romdata + header.IconOff, sizeof(RomBanner));
banner.version = LE_TO_LOCAL_16(banner.version);
banner.crc16 = LE_TO_LOCAL_16(banner.crc16);
for(size_t i = 0; i < ARRAY_SIZE(banner.palette); i++)
{
banner.palette[i] = LE_TO_LOCAL_16(banner.palette[i]);
}
}
_isDSiEnhanced = (LE_TO_LOCAL_32(*(u32*)(romdata + 0x180) == 0x8D898581U) && LE_TO_LOCAL_32(*(u32*)(romdata + 0x184) == 0x8C888480U));
fclose(fROM); fROM = NULL;
return true;
}
_isDSiEnhanced = ((readROM(0x180) == 0x8D898581U) && (readROM(0x184) == 0x8C888480U));
if (hasRomBanner())
{
size_t elements_read;
const size_t elements_to_read = sizeof(RomBanner);
fseek(fROM, header.IconOff + headerOffset, SEEK_SET);
elements_read = fread(&banner, 1, elements_to_read, fROM);
if (elements_read != elements_to_read)
printf("Unexpectedly short post-header bit.\n");
banner.version = LE_TO_LOCAL_16(banner.version);
banner.crc16 = LE_TO_LOCAL_16(banner.crc16);
for(size_t i = 0; i < ARRAY_SIZE(banner.palette); i++)
{
banner.palette[i] = LE_TO_LOCAL_16(banner.palette[i]);
}
}
fseek(fROM, headerOffset, SEEK_SET);
lastReadPos = 0;
return true;
}
romsize = 0;
fclose(fROM); fROM = NULL;
return false;
}
void GameInfo::closeROM()
{
if (fROM)
fclose(fROM);
if (romdata)
delete [] romdata;
fROM = NULL;
romdata = NULL;
romsize = 0;
lastReadPos = 0xFFFFFFFF;
}
u32 GameInfo::readROM(u32 pos)
{
if (!romdata)
{
u32 data;
if (lastReadPos != pos)
fseek(fROM, pos + headerOffset, SEEK_SET);
u32 num = fread(&data, 1, 4, fROM);
lastReadPos = (pos + num);
return LE_TO_LOCAL_32(data);
}
else
{
if(pos + 4 > romsize)
{
printf("Panic! GameInfo reading out of buffer!\n");
exit(-1);
}
return LE_TO_LOCAL_32(*(u32*)(romdata + pos));
}
}
bool GameInfo::isDSiEnhanced()
{
return _isDSiEnhanced;
}
bool GameInfo::isHomebrew()
{
return ((header.ARM9src < 0x4000) && (T1ReadLong(header.logo, 0) != 0x51AEFF24) && (T1ReadLong(header.logo, 4) != 0x699AA221));
}
static int rom_init_path(const char *filename, const char *physicalName, const char *logicalFilename)
{
u32 type = ROM_NDS;
path.init(logicalFilename? logicalFilename : filename);
if ( path.isdsgba(path.path)) {
type = ROM_DSGBA;
gameInfo.loadROM(path.path, type);
}
else if ( !strcasecmp(path.extension().c_str(), "nds")) {
type = ROM_NDS;
gameInfo.loadROM(physicalName ? physicalName : path.path, type); //n.b. this does nothing if the file can't be found (i.e. if it was an extracted tempfile)...
//...but since the data was extracted to gameInfo then it is ok
}
//ds.gba in archives, it's already been loaded into memory at this point
else if (logicalFilename && path.isdsgba(std::string(logicalFilename))) {
type = ROM_DSGBA;
} else {
//well, try to load it as an nds rom anyway
type = ROM_NDS;
gameInfo.loadROM(physicalName ? physicalName : path.path, type);
}
//check that size is at least the size of the header
if (gameInfo.romsize < 352) {
return -1;
}
gameInfo.romType = type;
return 1;
}
int NDS_LoadROM(const char *filename, const char *physicalName, const char *logicalFilename)
{
int ret;
char buf[PATH_MAX_LENGTH];
if (filename == NULL)
return -1;
ret = rom_init_path(filename, physicalName, logicalFilename);
if (ret < 1)
return ret;
//check whether this rom is any kind of valid
if(!CheckValidRom((u8*)&gameInfo.header, gameInfo.secureArea))
{
printf("Specified file is not a valid rom\n");
return -1;
}
gameInfo.populate();
if (CommonSettings.loadToMemory)
gameInfo.crc = crc32(0, (u8*)gameInfo.romdata, gameInfo.romsize);
else
gameInfo.crc = 0;
gameInfo.chipID = 0xC2; // The Manufacturer ID is defined by JEDEC (C2h = Macronix)
if (!gameInfo.isHomebrew())
{
gameInfo.chipID |= ((((128 << gameInfo.header.cardSize) / 1024) - 1) << 8); // Chip size in megabytes minus 1
// (07h = 8MB, 0Fh = 16MB, 1Fh = 32MB, 3Fh = 64MB, 7Fh = 128MB)
// flags
// 0: Unknown
// 1: Unknown
// 2: Unknown
// 3: Unknown
// 4: Unknown
// 5: DSi? (if set to 1 then DSi Enhanced games send command D6h to Slot1)
// 6: Unknown
// 7: ROM speed (Secure Area Block transfer mode (trasfer 8x200h or 1000h bytes)
// TODO:
//if (gameInfo.isDSiEnhanced())
// gameInfo.chipID |= (0x40 << 24);
gameInfo.chipID |= (0x00 << 24);
}
#ifdef DEBUG
INFO("\nROM game code: %c%c%c%c\n", gameInfo.header.gameCode[0], gameInfo.header.gameCode[1], gameInfo.header.gameCode[2], gameInfo.header.gameCode[3]);
if (gameInfo.crc)
INFO("ROM crc: %08X\n", gameInfo.crc);
if (!gameInfo.isHomebrew())
{
INFO("ROM serial: %s\n", gameInfo.ROMserial);
INFO("ROM chipID: %08X\n", gameInfo.chipID);
INFO("ROM internal name: %s\n", gameInfo.ROMname);
if (gameInfo.isDSiEnhanced()) INFO("ROM DSi Enhanced\n");
}
INFO("ROM developer: %s\n", ((gameInfo.header.makerCode == 0) && gameInfo.isHomebrew())
? "Homebrew" :
getDeveloperNameByID(gameInfo.header.makerCode));
#endif
buf[0] = gameInfo.header.gameCode[0];
buf[1] = gameInfo.header.gameCode[1];
buf[2] = gameInfo.header.gameCode[2];
buf[3] = gameInfo.header.gameCode[3];
buf[4] = 0;
if (advsc.checkDB(buf, gameInfo.crc))
{
u8 sv = advsc.getSaveType();
printf("Found in game database by %s:\n", advsc.getIdMethod());
printf("\t* ROM serial:\t\t%s\n", advsc.getSerial());
printf("\t* ROM save type:\t");
if (sv == 0xFF)
printf("Unknown");
else
if (sv == 0xFE)
printf("None");
else
{
printf("%s", save_types[sv + 1].descr);
if (CommonSettings.autodetectBackupMethod == 1)
backup_setManualBackupType(sv + 1);
}
printf("\n\t* ROM crc:\t\t%08X\n", advsc.getCRC32());
}
printf("\n");
//for homebrew, try auto-patching DLDI. should be benign if there is no DLDI or if it fails
if(gameInfo.isHomebrew())
{
if(!CommonSettings.loadToMemory)
msgbox->warn("Sorry.. right now, you can't use the default (stream rom from disk) with homebrew due to a bug with DLDI-autopatching");
if (slot1_GetCurrentType() == NDS_SLOT1_R4)
DLDI_tryPatch((void*)gameInfo.romdata, gameInfo.romsize, 1);
else
if (slot2_GetCurrentType() == NDS_SLOT2_CFLASH)
DLDI_tryPatch((void*)gameInfo.romdata, gameInfo.romsize, 0);
}
if (cheats != NULL)
{
memset(buf, 0, sizeof(buf));
path.getpathnoext(path.CHEATS, buf);
strcat(buf, ".dct"); // DeSmuME cheat :)
cheats->init(buf);
}
NDS_Reset();
return ret;
}
void NDS_FreeROM(void)
{
gameInfo.closeROM();
}
void NDS_Sleep() { nds.sleeping = TRUE; }
void NDS_TriggerCardEjectIRQ()
{
NDS_makeIrq(ARMCPU_ARM7, IRQ_BIT_GC_IREQ_MC);
NDS_makeIrq(ARMCPU_ARM9, IRQ_BIT_GC_IREQ_MC); //zero added on 11-aug-2013 with no proof of accuracy
}
class FrameSkipper
{
public:
void RequestSkip()
{
nextSkip = true;
}
void OmitSkip(bool force, bool forceEvenIfCapturing=false)
{
nextSkip = false;
if((force && consecutiveNonCaptures > 30) || forceEvenIfCapturing)
{
SkipCur2DFrame = false;
SkipCur3DFrame = false;
SkipNext2DFrame = false;
if(forceEvenIfCapturing)
consecutiveNonCaptures = 0;
}
}
void Advance()
{
const GPUEngineA *mainEngine = GPU->GetEngineMain();
bool capturing = (mainEngine->dispCapCnt.enabled || (mainEngine->dispCapCnt.val & 0x80000000));
if(capturing && consecutiveNonCaptures > 30)
{
// the worst-looking graphics corruption problems from frameskip
// are the result of skipping the capture on first frame it turns on.
// so we do this to handle the capture immediately,
// despite the risk of 1 frame of 2d/3d mismatch or wrong screen display.
SkipNext2DFrame = false;
nextSkip = false;
}
else if((lastDisplayTarget != mainEngine->GetDisplayByID()) && lastSkip && !skipped)
{
// if we're switching from not skipping to skipping
// and the screens are also switching around this frame,
// go for 1 extra frame without skipping.
// this avoids the scenario where we only draw one of the two screens
// when a game is switching screens every frame.
nextSkip = false;
}
if(capturing)
consecutiveNonCaptures = 0;
else if(!(consecutiveNonCaptures > 9000)) // arbitrary cap to avoid eventual wrap
consecutiveNonCaptures++;
lastDisplayTarget = mainEngine->GetDisplayByID();
lastSkip = skipped;
skipped = nextSkip;
nextSkip = false;
SkipCur2DFrame = SkipNext2DFrame;
SkipCur3DFrame = skipped;
SkipNext2DFrame = skipped;
}
FORCEINLINE bool ShouldSkip2D()
{
return SkipCur2DFrame;
}
FORCEINLINE bool ShouldSkip3D()
{
return SkipCur3DFrame;
}
FrameSkipper()
{
nextSkip = false;
skipped = false;
lastSkip = false;
lastDisplayTarget = NDSDisplayID_Main;
SkipCur2DFrame = false;
SkipCur3DFrame = false;
SkipNext2DFrame = false;
consecutiveNonCaptures = 0;
}
private:
bool nextSkip;
bool skipped;
bool lastSkip;
NDSDisplayID lastDisplayTarget;
int consecutiveNonCaptures;
bool SkipCur2DFrame;
bool SkipCur3DFrame;
bool SkipNext2DFrame;
};
static FrameSkipper frameSkipper;
void NDS_SkipNextFrame() {
frameSkipper.RequestSkip();
}
void NDS_OmitFrameSkip(int force) {
frameSkipper.OmitSkip(force > 0, force > 1);
}
#define INDEX(i) ((((i)>>16)&0xFF0)|(((i)>>4)&0xF))
enum ESI_DISPCNT
{
ESI_DISPCNT_HStart, ESI_DISPCNT_HStartIRQ, ESI_DISPCNT_HDraw, ESI_DISPCNT_HBlank
};
u64 nds_timer;
u64 nds_arm9_timer, nds_arm7_timer;
static const u64 kNever = 0xFFFFFFFFFFFFFFFFULL;
struct TSequenceItem
{
u64 timestamp;
u32 param;
bool enabled;
virtual void save(EMUFILE* os)
{
write64le(timestamp,os);
write32le(param,os);
writebool(enabled,os);
}
virtual bool load(EMUFILE* is)
{
if(read64le(×tamp,is) != 1) return false;
if(read32le(¶m,is) != 1) return false;
if(readbool(&enabled,is) != 1) return false;
return true;
}
FORCEINLINE bool isTriggered()
{
return enabled && nds_timer >= timestamp;
}
FORCEINLINE u64 next()
{
return timestamp;
}
};
struct TSequenceItem_GXFIFO : public TSequenceItem
{
FORCEINLINE bool isTriggered()
{
return enabled && nds_timer >= MMU.gfx3dCycles;
}
FORCEINLINE void exec()
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[4]++);
#endif
while(isTriggered()) {
enabled = false;
gfx3d_execute3D();
}
}
FORCEINLINE u64 next()
{
if(enabled) return MMU.gfx3dCycles;
else return kNever;
}
};
template struct TSequenceItem_Timer : public TSequenceItem
{
FORCEINLINE bool isTriggered()
{
return enabled && nds_timer >= nds.timerCycle[procnum][num];
}
FORCEINLINE void schedule()
{
enabled = MMU.timerON[procnum][num] && MMU.timerMODE[procnum][num] != 0xFFFF;
}
FORCEINLINE u64 next()
{
return nds.timerCycle[procnum][num];
}
FORCEINLINE void exec()
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[13+procnum*4+num]++);
#endif
u8* regs = procnum==0?MMU.ARM9_REG:MMU.ARM7_REG;
bool first = true;
//we'll need to check chained timers..
for(int i=num;i<4;i++)
{
bool over = false;
//maybe too many checks if this is here, but we need it here for now
if(!MMU.timerON[procnum][i]) return;
if(MMU.timerMODE[procnum][i] == 0xFFFF)
{
++(MMU.timer[procnum][i]);
over = !MMU.timer[procnum][i];
}
else
{
if(!first) break; //this timer isn't chained. break the chain
first = false;
over = true;
int remain = 65536 - MMU.timerReload[procnum][i];
int ctr=0;
while(nds.timerCycle[procnum][i] <= nds_timer) {
nds.timerCycle[procnum][i] += (remain << MMU.timerMODE[procnum][i]);
ctr++;
}
#ifndef NDEBUG
if(ctr>1) {
printf("yikes!!!!! please report!\n");
}
#endif
}
if(over)
{
MMU.timer[procnum][i] = MMU.timerReload[procnum][i];
if(T1ReadWord(regs, 0x102 + i*4) & 0x40)
{
NDS_makeIrq(procnum, IRQ_BIT_TIMER_0 + i);
}
}
else
break; //no more chained timers to trigger. we're done here
}
}
};
template struct TSequenceItem_DMA : public TSequenceItem
{
DmaController* controller;
FORCEINLINE bool isTriggered()
{
return (controller->dmaCheck && nds_timer>= controller->nextEvent);
}
FORCEINLINE bool isEnabled() {
return controller->dmaCheck?TRUE:FALSE;
}
FORCEINLINE u64 next()
{
return controller->nextEvent;
}
FORCEINLINE void exec()
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[5+procnum*4+chan]++);
#endif
//if (nds.freezeBus) return;
//printf("exec from TSequenceItem_DMA: %d %d\n",procnum,chan);
controller->exec();
// //give gxfifo dmas a chance to re-trigger
// if(MMU.DMAStartTime[procnum][chan] == EDMAMode_GXFifo) {
// MMU.DMAing[procnum][chan] = FALSE;
// if (gxFIFO.size <= 127)
// {
// execHardware_doDma(procnum,chan,EDMAMode_GXFifo);
// if (MMU.DMACompleted[procnum][chan])
// goto docomplete;
// else return;
// }
// }
//
//docomplete:
// if (MMU.DMACompleted[procnum][chan])
// {
// u8* regs = procnum==0?MMU.ARM9_REG:MMU.ARM7_REG;
//
// //disable the channel
// if(MMU.DMAStartTime[procnum][chan] != EDMAMode_GXFifo) {
// T1WriteLong(regs, 0xB8 + (0xC*chan), T1ReadLong(regs, 0xB8 + (0xC*chan)) & 0x7FFFFFFF);
// MMU.DMACrt[procnum][chan] &= 0x7FFFFFFF; //blehhh i hate this shit being mirrored in memory
// }
//
// if((MMU.DMACrt[procnum][chan])&(1<<30)) {
// if(procnum==0) NDS_makeARM9Int(8+chan);
// else NDS_makeARM7Int(8+chan);
// }
//
// MMU.DMAing[procnum][chan] = FALSE;
// }
}
};
struct TSequenceItem_divider : public TSequenceItem
{
FORCEINLINE bool isTriggered()
{
return MMU.divRunning && nds_timer >= MMU.divCycles;
}
bool isEnabled() { return MMU.divRunning!=0; }
FORCEINLINE u64 next()
{
return MMU.divCycles;
}
void exec()
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[2]++);
#endif
MMU_new.div.busy = 0;
#ifdef HOST_64
T1WriteQuad(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A0, MMU.divResult);
T1WriteQuad(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A8, MMU.divMod);
#else
T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A0, (u32)MMU.divResult);
T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A4, (u32)(MMU.divResult >> 32));
T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A8, (u32)MMU.divMod);
T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2AC, (u32)(MMU.divMod >> 32));
#endif
MMU.divRunning = FALSE;
}
};
struct TSequenceItem_sqrtunit : public TSequenceItem
{
FORCEINLINE bool isTriggered()
{
return MMU.sqrtRunning && nds_timer >= MMU.sqrtCycles;
}
bool isEnabled() { return MMU.sqrtRunning!=0; }
FORCEINLINE u64 next()
{
return MMU.sqrtCycles;
}
FORCEINLINE void exec()
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[3]++);
#endif
MMU_new.sqrt.busy = 0;
T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2B4, MMU.sqrtResult);
MMU.sqrtRunning = FALSE;
}
};
struct Sequencer
{
bool nds_vblankEnded;
bool reschedule;
TSequenceItem dispcnt;
TSequenceItem wifi;
TSequenceItem_divider divider;
TSequenceItem_sqrtunit sqrtunit;
TSequenceItem_GXFIFO gxfifo;
TSequenceItem_DMA<0,0> dma_0_0; TSequenceItem_DMA<0,1> dma_0_1;
TSequenceItem_DMA<0,2> dma_0_2; TSequenceItem_DMA<0,3> dma_0_3;
TSequenceItem_DMA<1,0> dma_1_0; TSequenceItem_DMA<1,1> dma_1_1;
TSequenceItem_DMA<1,2> dma_1_2; TSequenceItem_DMA<1,3> dma_1_3;
TSequenceItem_Timer<0,0> timer_0_0; TSequenceItem_Timer<0,1> timer_0_1;
TSequenceItem_Timer<0,2> timer_0_2; TSequenceItem_Timer<0,3> timer_0_3;
TSequenceItem_Timer<1,0> timer_1_0; TSequenceItem_Timer<1,1> timer_1_1;
TSequenceItem_Timer<1,2> timer_1_2; TSequenceItem_Timer<1,3> timer_1_3;
void init();
void execHardware();
u64 findNext();
void save(EMUFILE* os)
{
write64le(nds_timer,os);
write64le(nds_arm9_timer,os);
write64le(nds_arm7_timer,os);
dispcnt.save(os);
divider.save(os);
sqrtunit.save(os);
gxfifo.save(os);
wifi.save(os);
#define SAVE(I,X,Y) I##_##X##_##Y .save(os);
SAVE(timer,0,0); SAVE(timer,0,1); SAVE(timer,0,2); SAVE(timer,0,3);
SAVE(timer,1,0); SAVE(timer,1,1); SAVE(timer,1,2); SAVE(timer,1,3);
SAVE(dma,0,0); SAVE(dma,0,1); SAVE(dma,0,2); SAVE(dma,0,3);
SAVE(dma,1,0); SAVE(dma,1,1); SAVE(dma,1,2); SAVE(dma,1,3);
#undef SAVE
}
bool load(EMUFILE* is, int version)
{
if(read64le(&nds_timer,is) != 1) return false;
if(read64le(&nds_arm9_timer,is) != 1) return false;
if(read64le(&nds_arm7_timer,is) != 1) return false;
if(!dispcnt.load(is)) return false;
if(!divider.load(is)) return false;
if(!sqrtunit.load(is)) return false;
if(!gxfifo.load(is)) return false;
if(version >= 1) if(!wifi.load(is)) return false;
#define LOAD(I,X,Y) if(!I##_##X##_##Y .load(is)) return false;
LOAD(timer,0,0); LOAD(timer,0,1); LOAD(timer,0,2); LOAD(timer,0,3);
LOAD(timer,1,0); LOAD(timer,1,1); LOAD(timer,1,2); LOAD(timer,1,3);
LOAD(dma,0,0); LOAD(dma,0,1); LOAD(dma,0,2); LOAD(dma,0,3);
LOAD(dma,1,0); LOAD(dma,1,1); LOAD(dma,1,2); LOAD(dma,1,3);
#undef LOAD
return true;
}
} sequencer;
void NDS_RescheduleGXFIFO(u32 cost)
{
if(!sequencer.gxfifo.enabled) {
MMU.gfx3dCycles = nds_timer;
sequencer.gxfifo.enabled = true;
}
MMU.gfx3dCycles += cost;
NDS_Reschedule();
}
void NDS_RescheduleTimers()
{
#define check(X,Y) sequencer.timer_##X##_##Y .schedule();
check(0,0); check(0,1); check(0,2); check(0,3);
check(1,0); check(1,1); check(1,2); check(1,3);
#undef check
NDS_Reschedule();
}
void NDS_RescheduleDMA()
{
//TBD
NDS_Reschedule();
}
static void initSchedule()
{
sequencer.init();
//begin at the very end of the last scanline
//so that at t=0 we can increment to scanline=0
nds.VCount = 262;
sequencer.nds_vblankEnded = false;
}
// 2196372 ~= (ARM7_CLOCK << 16) / 1000000
// This value makes more sense to me, because:
// ARM7_CLOCK = 33.51 mhz
// = 33513982 cycles per second
// = 33.513982 cycles per microsecond
const u64 kWifiCycles = 67;//34*2;
//(this isn't very precise. I don't think it needs to be)
void Sequencer::init()
{
NDS_RescheduleTimers();
NDS_RescheduleDMA();
reschedule = false;
nds_timer = 0;
nds_arm9_timer = 0;
nds_arm7_timer = 0;
dispcnt.enabled = true;
dispcnt.param = ESI_DISPCNT_HStart;
dispcnt.timestamp = 0;
gxfifo.enabled = false;
dma_0_0.controller = &MMU_new.dma[0][0];
dma_0_1.controller = &MMU_new.dma[0][1];
dma_0_2.controller = &MMU_new.dma[0][2];
dma_0_3.controller = &MMU_new.dma[0][3];
dma_1_0.controller = &MMU_new.dma[1][0];
dma_1_1.controller = &MMU_new.dma[1][1];
dma_1_2.controller = &MMU_new.dma[1][2];
dma_1_3.controller = &MMU_new.dma[1][3];
#ifdef EXPERIMENTAL_WIFI_COMM
wifi.enabled = true;
wifi.timestamp = kWifiCycles;
#else
wifi.enabled = false;
#endif
}
static void execHardware_hblank()
{
//this logic keeps moving around.
//now, we try and give the game as much time as possible to finish doing its work for the scanline,
//by drawing scanline N at the end of drawing time (but before subsequent interrupt or hdma-driven events happen)
//don't try to do this at the end of the scanline, because some games (sonic classics) may use hblank IRQ to set
//scroll regs for the next scanline
if(nds.VCount<192)
{
GPU->RenderLine(nds.VCount, frameSkipper.ShouldSkip2D());
//trigger hblank dmas
//but notice, we do that just after we finished drawing the line
//(values copied by this hdma should not be used until the next scanline)
triggerDma(EDMAMode_HBlank);
}
#if 0
if(nds.VCount==262)
{
//we need to trigger one last hblank dma since
//a. we're sort of lagged behind by one scanline
//b. i think that 193 hblanks actually fire (one for the hblank in scanline 262)
//this is demonstrated by NSMB splot-parallaxing clouds
//for some reason the game will setup two hdma scroll register buffers
//to be run consecutively, and unless we do this, the second buffer will be offset by one scanline
//causing a glitch in the 0th scanline
//triggerDma(EDMAMode_HBlank);
//BUT! this was removed in order to make glitches in megaman zero collection (mmz 4 1st level) work.
//and, it seems that it is no longer necessary in nsmb. perhaps something else fixed it
}
#endif
//turn on hblank status bit
T1WriteWord(MMU.ARM9_REG, 4, T1ReadWord(MMU.ARM9_REG, 4) | 2);
T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) | 2);
//fire hblank interrupts if necessary
if(T1ReadWord(MMU.ARM9_REG, 4) & 0x10) NDS_makeIrq(ARMCPU_ARM9,IRQ_BIT_LCD_HBLANK);
if(T1ReadWord(MMU.ARM7_REG, 4) & 0x10) NDS_makeIrq(ARMCPU_ARM7,IRQ_BIT_LCD_HBLANK);
//emulation housekeeping. for some reason we always do this at hblank,
//even though it sounds more reasonable to do it at hstart
SPU_Emulate_core();
}
static void execHardware_hstart_vblankEnd()
{
sequencer.nds_vblankEnded = true;
sequencer.reschedule = true;
//turn off vblank status bit
T1WriteWord(MMU.ARM9_REG, 4, T1ReadWord(MMU.ARM9_REG, 4) & ~1);
T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & ~1);
//some emulation housekeeping
frameSkipper.Advance();
if (GPU->GetWillAutoBlitNativeToCustomBuffer())
{
GPU->GetEngineMain()->BlitNativeToCustomFramebuffer();
GPU->GetEngineSub()->BlitNativeToCustomFramebuffer();
}
}
static void execHardware_hstart_vblankStart()
{
//printf("--------VBLANK!!!--------\n");
//fire vblank interrupts if necessary
for(int i=0;i<2;i++)
if(MMU.reg_IF_pending[i] & (1<>8)|((vmatch<<1)&(1<<8)));
return vmatch;
}
static void execHardware_hstart_vcount_irq()
{
//trigger pending VMATCH irqs
if(MMU.reg_IF_pending[ARMCPU_ARM9] & (1<>8)|((vmatch<<1)&(1<<8)));
if(nds.VCount==vmatch)
{
//arm7 vmatch
T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) | 4);
if(T1ReadWord(MMU.ARM7_REG, 4) & 32)
MMU.reg_IF_pending[ARMCPU_ARM7] |= (1<> (32-1)) & (c^d)) ^ d);
}
FORCEINLINE u64 _fast_min(u64 a, u64 b)
{
//you might find that this is faster on a 64bit system; someone should try it
//http://aggregate.org/MAGIC/#Integer%20Selection
//u64 ret = (((((s64)(a-b)) >> (64-1)) & (a^b)) ^ b);
//assert(ret==min(a,b));
//return ret;
//but this ends up being the fastest on 32bits
return a>= 63; //no 64bit shra in sse2, what a disappointment
//temp = _mm_and_si128(temp,xorval);
//temp = _mm_xor_si128(temp,__b);
//return temp.m128i_u64[0];
}
u64 Sequencer::findNext()
{
//this one is always enabled so dont bother to check it
u64 next = dispcnt.next();
if(divider.isEnabled()) next = _fast_min(next,divider.next());
if(sqrtunit.isEnabled()) next = _fast_min(next,sqrtunit.next());
if(gxfifo.enabled) next = _fast_min(next,gxfifo.next());
#ifdef EXPERIMENTAL_WIFI_COMM
next = _fast_min(next,wifi.next());
#endif
#define test(X,Y) if(dma_##X##_##Y .isEnabled()) next = _fast_min(next,dma_##X##_##Y .next());
test(0,0); test(0,1); test(0,2); test(0,3);
test(1,0); test(1,1); test(1,2); test(1,3);
#undef test
#define test(X,Y) if(timer_##X##_##Y .enabled) next = _fast_min(next,timer_##X##_##Y .next());
test(0,0); test(0,1); test(0,2); test(0,3);
test(1,0); test(1,1); test(1,2); test(1,3);
#undef test
return next;
}
void Sequencer::execHardware()
{
if(dispcnt.isTriggered())
{
#ifndef NDEBUG
IF_DEVELOPER(DEBUG_statistics.sequencerExecutionCounters[1]++);
#endif
switch(dispcnt.param)
{
case ESI_DISPCNT_HStart:
execHardware_hstart();
//(used to be 3168)
//hstart is actually 8 dots before the visible drawing begins
//we're going to run 1 here and then run 7 in the next case
dispcnt.timestamp += 1*6*2;
dispcnt.param = ESI_DISPCNT_HStartIRQ;
break;
case ESI_DISPCNT_HStartIRQ:
execHardware_hstart_irq();
dispcnt.timestamp += 7*6*2;
dispcnt.param = ESI_DISPCNT_HDraw;
break;
case ESI_DISPCNT_HDraw:
//execHardware_hdraw();
//duration of non-blanking period is ~1606 clocks (gbatek agrees) [but says its different on arm7]
//im gonna call this 267 dots = 267*6=1602
//so, this event lasts 267 dots minus the 8 dot preroll
dispcnt.timestamp += (267-8)*6*2;
dispcnt.param = ESI_DISPCNT_HBlank;
break;
case ESI_DISPCNT_HBlank:
execHardware_hblank();
//(once this was 1092 or 1092/12=91 dots.)
//there are surely 355 dots per scanline, less 267 for non-blanking period. the rest is hblank and then after that is hstart
dispcnt.timestamp += (355-267)*6*2;
dispcnt.param = ESI_DISPCNT_HStart;
break;
}
}
#ifdef EXPERIMENTAL_WIFI_COMM
if(wifi.isTriggered())
{
WIFI_usTrigger();
wifi.timestamp += kWifiCycles;
}
#endif
if(divider.isTriggered()) divider.exec();
if(sqrtunit.isTriggered()) sqrtunit.exec();
if(gxfifo.isTriggered()) gxfifo.exec();
#define test(X,Y) if(dma_##X##_##Y .isTriggered()) dma_##X##_##Y .exec();
test(0,0); test(0,1); test(0,2); test(0,3);
test(1,0); test(1,1); test(1,2); test(1,3);
#undef test
#define test(X,Y) if(timer_##X##_##Y .enabled) if(timer_##X##_##Y .isTriggered()) timer_##X##_##Y .exec();
test(0,0); test(0,1); test(0,2); test(0,3);
test(1,0); test(1,1); test(1,2); test(1,3);
#undef test
}
void execHardware_interrupts();
static void saveUserInput(EMUFILE* os);
static bool loadUserInput(EMUFILE* is, int version);
void nds_savestate(EMUFILE* os)
{
//version
write32le(3,os);
sequencer.save(os);
saveUserInput(os);
write32le(LidClosed,os);
write8le(countLid,os);
}
bool nds_loadstate(EMUFILE* is, int size)
{
// this isn't part of the savestate loading logic, but
// don't skip the next frame after loading a savestate
frameSkipper.OmitSkip(true, true);
//read version
u32 version;
if(read32le(&version,is) != 1) return false;
if(version > 3) return false;
bool temp = true;
temp &= sequencer.load(is, version);
if(version <= 1 || !temp) return temp;
temp &= loadUserInput(is, version);
if(version < 3) return temp;
read32le(&LidClosed,is);
read8le(&countLid,is);
return temp;
}
#ifdef LOG_ARM9
FORCEINLINE void arm9log()
{
if(dolog)
{
char dasmbuf[4096];
if(NDS_ARM9.CPSR.bits.T)
des_thumb_instructions_set[((NDS_ARM9.instruction)>>6)&1023](NDS_ARM9.instruct_adr, NDS_ARM9.instruction, dasmbuf);
else
des_arm_instructions_set[INDEX(NDS_ARM9.instruction)](NDS_ARM9.instruct_adr, NDS_ARM9.instruction, dasmbuf);
#ifdef LOG_TO_FILE
if (!fp_dis9) return;
#ifdef LOG_TO_FILE_REGS
fprintf(fp_dis9, "\t\t;%05d:%03d %12lld\n\t\t;R0:%08X R1:%08X R2:%08X R3:%08X R4:%08X R5:%08X R6:%08X R7:%08X R8:%08X R9:%08X\n\t\t;R10:%08X R11:%08X R12:%08X R13:%08X R14:%08X R15:%08X| next %08X, N:%i Z:%i C:%i V:%i\n",
currFrameCounter, nds.VCount, nds_timer,
NDS_ARM9.R[0], NDS_ARM9.R[1], NDS_ARM9.R[2], NDS_ARM9.R[3], NDS_ARM9.R[4], NDS_ARM9.R[5], NDS_ARM9.R[6], NDS_ARM9.R[7],
NDS_ARM9.R[8], NDS_ARM9.R[9], NDS_ARM9.R[10], NDS_ARM9.R[11], NDS_ARM9.R[12], NDS_ARM9.R[13], NDS_ARM9.R[14], NDS_ARM9.R[15],
NDS_ARM9.next_instruction, NDS_ARM9.CPSR.bits.N, NDS_ARM9.CPSR.bits.Z, NDS_ARM9.CPSR.bits.C, NDS_ARM9.CPSR.bits.V);
#endif
fprintf(fp_dis9, "%s %08X\t%08X \t%s\n", NDS_ARM9.CPSR.bits.T?"THUMB":"ARM", NDS_ARM9.instruct_adr, NDS_ARM9.instruction, dasmbuf);
/*if (NDS_ARM9.instruction == 0)
{
dolog = false;
INFO("Disassembler is stopped\n");
}*/
#else
printf("%05d:%03d %12lld 9:%08X %08X %-30s R00:%08X R01:%08X R02:%08X R03:%08X R04:%08X R05:%08X R06:%08X R07:%08X R08:%08X R09:%08X R10:%08X R11:%08X R12:%08X R13:%08X R14:%08X R15:%08X\n",
currFrameCounter, nds.VCount, nds_timer,
NDS_ARM9.instruct_adr,NDS_ARM9.instruction, dasmbuf,
NDS_ARM9.R[0], NDS_ARM9.R[1], NDS_ARM9.R[2], NDS_ARM9.R[3], NDS_ARM9.R[4], NDS_ARM9.R[5], NDS_ARM9.R[6], NDS_ARM9.R[7],
NDS_ARM9.R[8], NDS_ARM9.R[9], NDS_ARM9.R[10], NDS_ARM9.R[11], NDS_ARM9.R[12], NDS_ARM9.R[13], NDS_ARM9.R[14], NDS_ARM9.R[15]);
#endif
}
}
#endif
#ifdef LOG_ARM7
FORCEINLINE void arm7log()
{
if(dolog)
{
char dasmbuf[4096];
if(NDS_ARM7.CPSR.bits.T)
des_thumb_instructions_set[((NDS_ARM7.instruction)>>6)&1023](NDS_ARM7.instruct_adr, NDS_ARM7.instruction, dasmbuf);
else
des_arm_instructions_set[INDEX(NDS_ARM7.instruction)](NDS_ARM7.instruct_adr, NDS_ARM7.instruction, dasmbuf);
#ifdef LOG_TO_FILE
if (!fp_dis7) return;
#ifdef LOG_TO_FILE_REGS
fprintf(fp_dis7, "\t\t;%05d:%03d %12lld\n\t\t;R0:%08X R1:%08X R2:%08X R3:%08X R4:%08X R5:%08X R6:%08X R7:%08X R8:%08X R9:%08X\n\t\t;R10:%08X R11:%08X R12:%08X R13:%08X R14:%08X R15:%08X| next %08X, N:%i Z:%i C:%i V:%i\n",
currFrameCounter, nds.VCount, nds_timer,
NDS_ARM7.R[0], NDS_ARM7.R[1], NDS_ARM7.R[2], NDS_ARM7.R[3], NDS_ARM7.R[4], NDS_ARM7.R[5], NDS_ARM7.R[6], NDS_ARM7.R[7],
NDS_ARM7.R[8], NDS_ARM7.R[9], NDS_ARM7.R[10], NDS_ARM7.R[11], NDS_ARM7.R[12], NDS_ARM7.R[13], NDS_ARM7.R[14], NDS_ARM7.R[15],
NDS_ARM7.next_instruction, NDS_ARM7.CPSR.bits.N, NDS_ARM7.CPSR.bits.Z, NDS_ARM7.CPSR.bits.C, NDS_ARM7.CPSR.bits.V);
#endif
fprintf(fp_dis7, "%s %08X\t%08X \t%s\n", NDS_ARM7.CPSR.bits.T?"THUMB":"ARM", NDS_ARM7.instruct_adr, NDS_ARM7.instruction, dasmbuf);
/*if (NDS_ARM7.instruction == 0)
{
dolog = false;
INFO("Disassembler is stopped\n");
}*/
#else
printf("%05d:%03d %12lld 7:%08X %08X %-30s R00:%08X R01:%08X R02:%08X R03:%08X R04:%08X R05:%08X R06:%08X R07:%08X R08:%08X R09:%08X R10:%08X R11:%08X R12:%08X R13:%08X R14:%08X R15:%08X\n",
currFrameCounter, nds.VCount, nds_timer,
NDS_ARM7.instruct_adr,NDS_ARM7.instruction, dasmbuf,
NDS_ARM7.R[0], NDS_ARM7.R[1], NDS_ARM7.R[2], NDS_ARM7.R[3], NDS_ARM7.R[4], NDS_ARM7.R[5], NDS_ARM7.R[6], NDS_ARM7.R[7],
NDS_ARM7.R[8], NDS_ARM7.R[9], NDS_ARM7.R[10], NDS_ARM7.R[11], NDS_ARM7.R[12], NDS_ARM7.R[13], NDS_ARM7.R[14], NDS_ARM7.R[15]);
#endif
}
}
#endif
//these have not been tuned very well yet.
static const int kMaxWork = 4000;
static const int kIrqWait = 4000;
template
static FORCEINLINE s32 minarmtime(s32 arm9, s32 arm7)
{
if(doarm9)
{
if(doarm7)
return min(arm9,arm7);
return arm9;
}
return arm7;
}
#ifdef HAVE_JIT
template
#else
template
#endif
static /*donotinline*/ std::pair armInnerLoop(
const u64 nds_timer_base, const s32 s32next, s32 arm9, s32 arm7)
{
s32 timer = minarmtime(arm9,arm7);
while(timer < s32next && !sequencer.reschedule && execute)
{
if(doarm9 && (!doarm7 || arm9 <= timer))
{
if(!NDS_ARM9.waitIRQ&&!nds.freezeBus)
{
#ifdef LOG_ARM9
arm9log();
#endif
#ifdef DEBUG
debug();
#endif
#ifdef HAVE_JIT
arm9 += armcpu_exec();
#else
arm9 += armcpu_exec();
#endif
#ifdef DEVELOPER
nds_debug_continuing[0] = false;
#endif
}
else
{
s32 temp = arm9;
arm9 = min(s32next, arm9 + kIrqWait);
nds.idleCycles[0] += arm9-temp;
if (gxFIFO.size < 255) nds.freezeBus &= ~1;
}
}
if(doarm7 && (!doarm9 || arm7 <= timer))
{
if(!NDS_ARM7.waitIRQ&&!nds.freezeBus)
{
#ifdef LOG_ARM7
arm7log();
#endif
#ifdef HAVE_JIT
arm7 += (armcpu_exec()<<1);
#else
arm7 += (armcpu_exec()<<1);
#endif
#ifdef DEVELOPER
nds_debug_continuing[1] = false;
#endif
}
else
{
s32 temp = arm7;
arm7 = min(s32next, arm7 + kIrqWait);
nds.idleCycles[1] += arm7-temp;
if(arm7 == s32next)
{
nds_timer = nds_timer_base + minarmtime(arm9,arm7);
#ifdef HAVE_JIT
return armInnerLoop(nds_timer_base, s32next, arm9, arm7);
#else
return armInnerLoop(nds_timer_base, s32next, arm9, arm7);
#endif
}
}
}
timer = minarmtime(arm9,arm7);
nds_timer = nds_timer_base + timer;
}
return std::make_pair(arm9, arm7);
}
void NDS_debug_break()
{
NDS_ARM9.stalled = NDS_ARM7.stalled = 1;
//triggers an immediate exit from the cpu loop
NDS_Reschedule();
}
void NDS_debug_continue()
{
NDS_ARM9.stalled = NDS_ARM7.stalled = 0;
}
void NDS_debug_step()
{
NDS_debug_continue();
singleStep = true;
}
void NDS_exec(s32 nb)
{
sequencer.nds_vblankEnded = false;
nds.cpuloopIterationCount = 0;
#ifndef NDEBUG
IF_DEVELOPER(for(int i=0;i<32;i++) DEBUG_statistics.sequencerExecutionCounters[i] = 0);
#endif
if(nds.sleeping)
{
//speculative code: if ANY irq happens, wake up the arm7.
//I think the arm7 program analyzes the system and may decide not to wake up
//if it is dissatisfied with the conditions
if((MMU.reg_IE[1] & MMU.gen_IF<1>()))
nds.sleeping = FALSE;
}
else
{
for(;;)
{
//trap the debug-stalled condition
#ifdef DEVELOPER
singleStep = false;
//(gdb stub doesnt yet know how to trigger these immediately by calling reschedule)
if ((NDS_ARM9.stalled || NDS_ARM7.stalled) && execute)
{
driver->EMU_DebugIdleEnter();
while((NDS_ARM9.stalled || NDS_ARM7.stalled) && execute)
{
driver->EMU_DebugIdleUpdate();
nds_debug_continuing[0] = nds_debug_continuing[1] = true;
}
driver->EMU_DebugIdleWakeUp();
}
#endif
nds.cpuloopIterationCount++;
sequencer.execHardware();
//break out once per frame
if(sequencer.nds_vblankEnded) break;
//it should be benign to execute execHardware in the next frame,
//since there won't be anything for it to do (everything should be scheduled in the future)
//bail in case the system halted
if(!execute) break;
execHardware_interrupts();
//find next work unit:
u64 next = sequencer.findNext();
next = min(next,nds_timer+kMaxWork); //lets set an upper limit for now
//printf("%d\n",(next-nds_timer));
sequencer.reschedule = false;
//cast these down to 32bits so that things run faster on 32bit procs
u64 nds_timer_base = nds_timer;
s32 arm9 = (s32)(nds_arm9_timer-nds_timer);
s32 arm7 = (s32)(nds_arm7_timer-nds_timer);
s32 s32next = (s32)(next-nds_timer);
#ifdef DEVELOPER
if(singleStep)
{
s32next = 1;
}
#endif
#ifdef HAVE_JIT
std::pair arm9arm7 = CommonSettings.use_jit
? armInnerLoop(nds_timer_base,s32next,arm9,arm7)
: armInnerLoop(nds_timer_base,s32next,arm9,arm7);
#else
std::pair arm9arm7 = armInnerLoop(nds_timer_base,s32next,arm9,arm7);
#endif
#ifdef DEVELOPER
if(singleStep)
{
NDS_ARM9.stalled = NDS_ARM7.stalled = 1;
}
#endif
arm9 = arm9arm7.first;
arm7 = arm9arm7.second;
nds_arm7_timer = nds_timer_base+arm7;
nds_arm9_timer = nds_timer_base+arm9;
#ifndef NDEBUG
//what we find here is dependent on the timing constants above
//if(nds_timer>next && (nds_timer-next)>22)
// printf("curious. please report: over by %d\n",(int)(nds_timer-next));
#endif
//if we were waiting for an irq, don't wait too long:
//let's re-analyze it after this hardware event (this rolls back a big burst of irq waiting which may have been interrupted by a resynch)
if(NDS_ARM9.waitIRQ)
{
nds.idleCycles[0] -= (s32)(nds_arm9_timer-nds_timer);
nds_arm9_timer = nds_timer;
}
if(NDS_ARM7.waitIRQ)
{
nds.idleCycles[1] -= (s32)(nds_arm7_timer-nds_timer);
nds_arm7_timer = nds_timer;
}
}
}
currFrameCounter++;
#ifndef NDEBUG
DEBUG_Notify.NextFrame();
#endif
if (cheats)
cheats->process();
}
template static void execHardware_interrupts_core()
{
u32 IF = MMU.gen_IF();
u32 IE = MMU.reg_IE[PROCNUM];
u32 masked = IF & IE;
if(ARMPROC.halt_IE_and_IF && masked)
{
ARMPROC.halt_IE_and_IF = FALSE;
ARMPROC.waitIRQ = FALSE;
}
if(masked && MMU.reg_IME[PROCNUM] && !ARMPROC.CPSR.bits.I)
{
//printf("Executing IRQ on procnum %d with IF = %08X and IE = %08X\n",PROCNUM,IF,IE);
armcpu_irqException(&ARMPROC);
}
}
void execHardware_interrupts()
{
execHardware_interrupts_core();
execHardware_interrupts_core();
}
static void resetUserInput();
static void PrepareBiosARM7()
{
//begin with the bios unloaded
NDS_ARM7.BIOS_loaded = false;
memset(MMU.ARM7_BIOS, 0, sizeof(MMU.ARM7_BIOS));
if(CommonSettings.UseExtBIOS == true)
{
//read arm7 bios from inputfile and flag it if it succeeds
FILE *arm7inf = fopen(CommonSettings.ARM7BIOS,"rb");
if (arm7inf)
{
if (fread(MMU.ARM7_BIOS,1,16384,arm7inf) == 16384)
NDS_ARM7.BIOS_loaded = true;
fclose(arm7inf);
}
}
//choose to use SWI emulation or routines from bios
if((CommonSettings.SWIFromBIOS) && (NDS_ARM7.BIOS_loaded))
{
NDS_ARM7.swi_tab = 0;
//if we used routines from bios, apply patches
if (CommonSettings.PatchSWI3)
{
//[3801] SUB R0, #1 -> [4770] BX LR
T1WriteWord(MMU.ARM7_BIOS,0x2F08, 0x4770);
}
}
else
NDS_ARM7.swi_tab = ARM_swi_tab[ARMCPU_ARM7];
if(NDS_ARM7.BIOS_loaded)
{
#ifdef DEBUG
INFO("ARM7 BIOS load: %s.\n", NDS_ARM7.BIOS_loaded?"OK":"FAILED");
#endif
}
else
{
//fake bios content, critical to normal operations, since we dont have a real bios.
T1WriteLong(MMU.ARM7_BIOS, 0x0000, 0xEAFFFFFE); //B 00000000 (reset: infinite loop) (originally: 0xE25EF002 - SUBS PC, LR, #2
T1WriteLong(MMU.ARM7_BIOS, 0x0004, 0xEAFFFFFE); //B 00000004 (undefined instruction: infinite loop)
T1WriteLong(MMU.ARM7_BIOS, 0x0008, 0xEAFFFFFE); //B 00000280 (SWI: infinite loop [since we will be HLEing the SWI routines])
T1WriteLong(MMU.ARM7_BIOS, 0x000C, 0xEAFFFFFE); //B 0000000C (prefetch abort: infinite loop)
T1WriteLong(MMU.ARM7_BIOS, 0x0010, 0xEAFFFFFE); //B 00000010 (data abort: infinite loop)
T1WriteLong(MMU.ARM7_BIOS, 0x0018, 0xEA000000); //B 00000020 (IRQ: branch to handler)
T1WriteLong(MMU.ARM7_BIOS, 0x001C, 0xEAFFFFFE); //B 0000001C (FIQ vector: infinite loop)
//IRQ handler
T1WriteLong(MMU.ARM7_BIOS, 0x0020, 0xE92D500F); //STMDB SP!, {R0-R3,R12,LR}
T1WriteLong(MMU.ARM7_BIOS, 0x0024, 0xE3A00301); //MOV R0, #4000000
T1WriteLong(MMU.ARM7_BIOS, 0x0028, 0xE28FE000); //ADD LR, PC, #0
T1WriteLong(MMU.ARM7_BIOS, 0x002C, 0xE510F004); //LDR PC, [R0, -#4]
T1WriteLong(MMU.ARM7_BIOS, 0x0030, 0xE8BD500F); //LDMIA SP!, {R0-R3,R12,LR}
T1WriteLong(MMU.ARM7_BIOS, 0x0034, 0xE25EF004); //SUBS PC, LR, #4
}
}
static void PrepareBiosARM9()
{
//begin with the bios unloaded
memset(MMU.ARM9_BIOS, 0, sizeof(MMU.ARM9_BIOS));
NDS_ARM9.BIOS_loaded = false;
if(CommonSettings.UseExtBIOS == true)
{
//read arm9 bios from inputfile and flag it if it succeeds
FILE* arm9inf = fopen(CommonSettings.ARM9BIOS,"rb");
if (arm9inf)
{
if (fread(MMU.ARM9_BIOS,1,4096,arm9inf) == 4096)
NDS_ARM9.BIOS_loaded = true;
fclose(arm9inf);
}
}
//choose to use SWI emulation or routines from bios
if((CommonSettings.SWIFromBIOS) && (NDS_ARM9.BIOS_loaded))
{
NDS_ARM9.swi_tab = 0;
//if we used routines from bios, apply patches
//[3801] SUB R0, #1 -> [4770] BX LR
if (CommonSettings.PatchSWI3)
T1WriteWord(MMU.ARM9_BIOS, 0x07CC, 0x4770);
}
else NDS_ARM9.swi_tab = ARM_swi_tab[ARMCPU_ARM9];
if(NDS_ARM9.BIOS_loaded)
{
#ifdef DEBUG
INFO("ARM9 BIOS load: %s.\n", NDS_ARM9.BIOS_loaded?"OK":"FAILED");
#endif
}
else
{
//fake bios content, critical to normal operations, since we dont have a real bios.
//it'd be cool if we could write this in some kind of assembly language, inline or otherwise, without some bulky dependencies
//perhaps we could build it with devkitarm? but thats bulky (offline) dependencies, to be sure..
//reminder: bios chains data abort to fast irq
//exception vectors:
T1WriteLong(MMU.ARM9_BIOS, 0x0000, 0xEAFFFFFE); // (infinite loop for) Reset !!!
//T1WriteLong(MMU.ARM9_BIOS, 0x0004, 0xEAFFFFFE); // (infinite loop for) Undefined instruction
T1WriteLong(MMU.ARM9_BIOS, 0x0004, 0xEA000004); // Undefined instruction -> Fast IRQ (just guessing)
T1WriteLong(MMU.ARM9_BIOS, 0x0008, 0xEA00009C); // SWI -> ?????
T1WriteLong(MMU.ARM9_BIOS, 0x000C, 0xEAFFFFFE); // (infinite loop for) Prefetch Abort
T1WriteLong(MMU.ARM9_BIOS, 0x0010, 0xEA000001); // Data Abort -> Fast IRQ
T1WriteLong(MMU.ARM9_BIOS, 0x0014, 0x00000000); // Reserved
T1WriteLong(MMU.ARM9_BIOS, 0x0018, 0xEA000095); // Normal IRQ -> 0x0274
T1WriteLong(MMU.ARM9_BIOS, 0x001C, 0xEA00009D); // Fast IRQ -> 0x0298
//copy the logo content into the bios - Pokemon Platinum uses this in Pal Park trade
//it compares the logo from the arm9 bios to the logo in the GBA header.
//NOTE: in the unlikely event that the valid header is missing from the gameInfo, we'd be doing wrong here.
// however, its nice not to have the logo embedded here.
// TODO - take a CRC of the logo, check vs logoCRC16, and a hardcoded value, to make sure all is in order--report error if not
memcpy(&MMU.ARM9_BIOS[0x20], &gameInfo.header.logo[0], 0x9C);
T1WriteWord(MMU.ARM9_BIOS, 0x20 + 0x9C, gameInfo.header.logoCRC16);
//... and with that we are at 0xBC:
//(now what goes in this gap?? nothing we need, i guess)
//IRQ handler: get dtcm address and jump to a vector in it
T1WriteLong(MMU.ARM9_BIOS, 0x0274, 0xE92D500F); //STMDB SP!, {R0-R3,R12,LR}
T1WriteLong(MMU.ARM9_BIOS, 0x0278, 0xEE190F11); //MRC CP15, 0, R0, CR9, CR1, 0
T1WriteLong(MMU.ARM9_BIOS, 0x027C, 0xE1A00620); //MOV R0, R0, LSR #C
T1WriteLong(MMU.ARM9_BIOS, 0x0280, 0xE1A00600); //MOV R0, R0, LSL #C
T1WriteLong(MMU.ARM9_BIOS, 0x0284, 0xE2800C40); //ADD R0, R0, #4000
T1WriteLong(MMU.ARM9_BIOS, 0x0288, 0xE28FE000); //ADD LR, PC, #0
T1WriteLong(MMU.ARM9_BIOS, 0x028C, 0xE510F004); //LDR PC, [R0, -#4]
//????
T1WriteLong(MMU.ARM9_BIOS, 0x0290, 0xE8BD500F); //LDMIA SP!, {R0-R3,R12,LR}
T1WriteLong(MMU.ARM9_BIOS, 0x0294, 0xE25EF004); //SUBS PC, LR, #4
//-------
//FIQ and abort exception handler
//TODO - this code is copied from the bios. refactor it
//friendly reminder: to calculate an immediate offset: encoded = (desired_address-cur_address-8)
T1WriteLong(MMU.ARM9_BIOS, 0x0298, 0xE10FD000); //MRS SP, CPSR
T1WriteLong(MMU.ARM9_BIOS, 0x029C, 0xE38DD0C0); //ORR SP, SP, #C0
T1WriteLong(MMU.ARM9_BIOS, 0x02A0, 0xE12FF00D); //MSR CPSR_fsxc, SP
T1WriteLong(MMU.ARM9_BIOS, 0x02A4, 0xE59FD000 | (0x2D4-0x2A4-8)); //LDR SP, [FFFF02D4]
T1WriteLong(MMU.ARM9_BIOS, 0x02A8, 0xE28DD001); //ADD SP, SP, #1
T1WriteLong(MMU.ARM9_BIOS, 0x02AC, 0xE92D5000); //STMDB SP!, {R12,LR}
T1WriteLong(MMU.ARM9_BIOS, 0x02B0, 0xE14FE000); //MRS LR, SPSR
T1WriteLong(MMU.ARM9_BIOS, 0x02B4, 0xEE11CF10); //MRC CP15, 0, R12, CR1, CR0, 0
T1WriteLong(MMU.ARM9_BIOS, 0x02B8, 0xE92D5000); //STMDB SP!, {R12,LR}
T1WriteLong(MMU.ARM9_BIOS, 0x02BC, 0xE3CCC001); //BIC R12, R12, #1
T1WriteLong(MMU.ARM9_BIOS, 0x02C0, 0xEE01CF10); //MCR CP15, 0, R12, CR1, CR0, 0
T1WriteLong(MMU.ARM9_BIOS, 0x02C4, 0xE3CDC001); //BIC R12, SP, #1
T1WriteLong(MMU.ARM9_BIOS, 0x02C8, 0xE59CC010); //LDR R12, [R12, #10]
T1WriteLong(MMU.ARM9_BIOS, 0x02CC, 0xE35C0000); //CMP R12, #0
T1WriteLong(MMU.ARM9_BIOS, 0x02D0, 0x112FFF3C); //BLXNE R12
T1WriteLong(MMU.ARM9_BIOS, 0x02D4, 0x027FFD9C); //0x027FFD9C
//---------
}
}
static void JumbleMemory() { }
static void PrepareLogfiles()
{
#ifdef LOG_ARM7
if (fp_dis7 != NULL)
{
fclose(fp_dis7);
fp_dis7 = NULL;
}
fp_dis7 = fopen("D:\\desmume_dis7.asm", "w");
#endif
#ifdef LOG_ARM9
if (fp_dis9 != NULL)
{
fclose(fp_dis9);
fp_dis9 = NULL;
}
fp_dis9 = fopen("D:\\desmume_dis9.asm", "w");
#endif
}
bool NDS_LegitBoot()
{
#ifdef HAVE_JIT
//hack for firmware boot in JIT mode.
//we know that it takes certain jit parameters to successfully boot the firmware.
//CRAZYMAX: is it safe to accept anything smaller than 12?
CommonSettings.jit_max_block_size = std::min(CommonSettings.jit_max_block_size,12U);
#endif
//partially clobber the loaded firmware with the user settings from DFC
firmware->loadSettings();
//since firmware only boots encrypted roms, we have to make sure it's encrypted first
//this has not been validated on big endian systems. it almost positively doesn't work.
if (gameInfo.header.CRC16 != 0)
EncryptSecureArea((u8*)&gameInfo.header, (u8*)gameInfo.secureArea);
//boot processors from their bios entrypoints
armcpu_init(&NDS_ARM7, 0x00000000);
armcpu_init(&NDS_ARM9, 0xFFFF0000);
return true;
}
//the fake firmware boot-up process
bool NDS_FakeBoot()
{
NDS_header * header = NDS_getROMHeader();
#ifndef NDEBUG
DEBUG_reset();
#endif
if (!header) return false;
nds.isFakeBooted = true;
//since we're bypassing the code to decrypt the secure area, we need to make sure its decrypted first
//this has not been validated on big endian systems. it almost positively doesn't work.
if (gameInfo.header.CRC16 != 0)
{
bool okRom = DecryptSecureArea((u8*)&gameInfo.header, (u8*)gameInfo.secureArea);
if(!okRom) {
printf("Specified file is not a valid rom\n");
return false;
}
}
//bios (or firmware) sets this default, which is generally not important for retail games but some homebrews are depending on
_MMU_write08(REG_WRAMCNT,3);
//EDIT - whats this firmware and how is it relating to the dummy firmware below
//how do these even get used? what is the purpose of unpack and why is it not used by the firmware boot process?
if (CommonSettings.UseExtFirmware && firmware->loaded())
{
firmware->unpack();
firmware->loadSettings();
}
// Create the dummy firmware
//EDIT - whats dummy firmware and how is relating to the above?
//it seems to be emplacing basic firmware data into MMU.fw.data
NDS_CreateDummyFirmware(&CommonSettings.fw_config);
//firmware loads the game card arm9 and arm7 programs as specified in rom header
{
bool hasSecureArea = ((gameInfo.romType == ROM_NDS) && (gameInfo.header.CRC16 != 0));
//copy the arm9 program to the address specified by rom header
u32 src = header->ARM9src;
u32 dst = header->ARM9cpy;
for(u32 i = 0; i < header->ARM9binSize; i+=4)
{
u32 tmp = (hasSecureArea && ((src >= 0x4000) && (src < 0x8000)))?LE_TO_LOCAL_32(*(u32*)(gameInfo.secureArea + (src - 0x4000))):gameInfo.readROM(src);
_MMU_write32(dst, tmp);
dst += 4;
src += 4;
}
//copy the arm7 program to the address specified by rom header
src = header->ARM7src;
dst = header->ARM7cpy;
for(u32 i = 0; i < header->ARM7binSize; i+=4)
{
_MMU_write32(dst, gameInfo.readROM(src));
dst += 4;
src += 4;
}
}
//bios does this (thats weird, though. shouldnt it get changed when the card is swapped in the firmware menu?
//right now our firmware menu isnt detecting any change to the card.
//are some games depending on it being written here? please document.
//_MMU_write16(0x027FF808, T1ReadWord(MMU.CART_ROM, 0x15E));
//firmware sets up a copy of the firmware user settings in memory.
//TBD - this code is really clunky
//it seems to be copying the MMU.fw.data data into RAM in the user memory stash locations
u8 temp_buffer[NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT];
if ( copy_firmware_user_data( temp_buffer, MMU.fw.data)) {
for ( int fw_index = 0; fw_index < NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT; fw_index++)
_MMU_write08(0x027FFC80 + fw_index, temp_buffer[fw_index]);
}
//something copies the whole header to Main RAM 0x27FFE00 on startup. (http://nocash.emubase.de/gbatek.htm#dscartridgeheader)
//once upon a time this copied 0x90 more. this was thought to be wrong, and changed.
if(nds.Is_DSI())
{
//dsi needs this copied later in memory. there are probably a number of things that get copied to a later location in memory.. thats where the NDS consoles tend to stash stuff.
for (int i = 0; i < (0x170); i+=4)
_MMU_write32(0x027FFE00 + i, gameInfo.readROM(i));
}
else
{
for (int i = 0; i < (0x170); i+=4)
_MMU_write32(0x027FFE00 + i, gameInfo.readROM(i));
}
//the firmware will be booting to these entrypoint addresses via BX (well, the arm9 at least; is unverified for the arm7)
armcpu_init(&NDS_ARM7, header->ARM7exe);
armcpu_init(&NDS_ARM9, header->ARM9exe);
//firmware sets REG_POSTFLG to the value indicating post-firmware status
MMU.ARM9_REG[0x300] = 1;
MMU.ARM7_REG[0x300] = 1;
//firmware makes system think it's booted from card -- EXTREMELY IMPORTANT!!! This is actually checked by some things. (which things?) Thanks to cReDiAr
_MMU_write08(0x02FFFC40,0x1); // removed redundant write to ARM9, this is going to shared main memory. But one has to wonder why r3478 was made which corrected a typo resulting in only ARMCPU_ARM7 getting used.
//the chipId is read several times
//for some reason, each of those reads get stored here.
_MMU_write32(0x027FF800, gameInfo.chipID); //1st chipId
_MMU_write32(0x027FF804, gameInfo.chipID); //2nd (secure) chipId
_MMU_write32(0x027FFC00, gameInfo.chipID); //3rd (secure) chipId
// Write the header checksum to memory
_MMU_write16(0x027FF808, gameInfo.header.headerCRC16);
//bitbox 4k demo is so stripped down it relies on default stack values
//otherwise the arm7 will crash before making a sound
//(these according to gbatek softreset bios docs)
NDS_ARM7.R13_svc = 0x0380FFDC;
NDS_ARM7.R13_irq = 0x0380FFB0;
NDS_ARM7.R13_usr = 0x0380FF00;
NDS_ARM7.R[13] = NDS_ARM7.R13_usr;
//and let's set these for the arm9 while we're at it, though we have no proof
NDS_ARM9.R13_svc = 0x00803FC0;
NDS_ARM9.R13_irq = 0x00803FA0;
NDS_ARM9.R13_usr = 0x00803EC0;
NDS_ARM9.R13_abt = NDS_ARM9.R13_usr; //?????
//I think it is wrong to take gbatek's "SYS" and put it in USR--maybe USR doesnt matter.
//i think SYS is all the misc modes. please verify by setting nonsensical stack values for USR here
NDS_ARM9.R[13] = NDS_ARM9.R13_usr;
//n.b.: im not sure about all these, I dont know enough about arm9 svc/irq/etc modes
//and how theyre named in desmume to match them up correctly. i just guessed.
//--------------------------------
//setup the homebrew argv
//this is useful for nitrofs apps which are emulating themselves via cflash
//struct __argv {
// int argvMagic; //!< argv magic number, set to 0x5f617267 ('_arg') if valid
// char *commandLine; //!< base address of command line, set of null terminated strings
// int length; //!< total length of command line
// int argc; //!< internal use, number of arguments
// char **argv; //!< internal use, argv pointer
//};
std::string rompath = "fat:/" + path.RomName;
const u32 kCommandline = 0x027E0000;
//const u32 kCommandline = 0x027FFF84;
//homebrew-related stuff.
//its safe to put things in this position.. apparently nothing important is here.
//however, some games could be checking them as an anti-desmume measure, so we might have to control it with slot-1 settings to suggest booting a homebrew app
//perhaps we could automatically boot homebrew to an R4-like device.
_MMU_write32(0x02FFFE70, 0x5f617267);
_MMU_write32(0x02FFFE74, kCommandline); //(commandline starts here)
_MMU_write32(0x02FFFE78, rompath.size()+1);
//0x027FFF7C (argc)
//0x027FFF80 (argv)
for(size_t i=0;i(kCommandline+i, rompath[i]);
_MMU_write08(kCommandline+rompath.size(), 0);
//--------------------------------
//Call the card post_fakeboot hook to perform additional initialization
slot1_device->post_fakeboot(ARMCPU_ARM9);
slot1_device->post_fakeboot(ARMCPU_ARM7);
delete header;
return true;
}
void NDS_Reset()
{
PrepareLogfiles();
currFrameCounter = 0;
resetUserInput();
singleStep = false;
nds_debug_continuing[0] = nds_debug_continuing[1] = false;
nds.sleeping = FALSE;
nds.cardEjected = FALSE;
nds.freezeBus = 0;
nds.power1.lcd = nds.power1.gpuMain = nds.power1.gfx3d_render = nds.power1.gfx3d_geometry = nds.power1.gpuSub = nds.power1.dispswap = 1;
nds.power2.speakers = 1;
nds.power2.wifi = 0;
nds.wifiCycle = 0;
memset(nds.timerCycle, 0, sizeof(u64) * 2 * 4);
nds.old = 0;
nds.scr_touchX = nds.scr_touchY = nds.adc_touchX = nds.adc_touchY = 0;
nds.isTouch = 0;
nds.isFakeBooted = false;
nds.paddle = 0;
nds.ConsoleType = CommonSettings.ConsoleType;
nds._DebugConsole = CommonSettings.DebugConsole;
nds.ensataEmulation = CommonSettings.EnsataEmulation;
nds.stylusJitter = CommonSettings.StylusJitter;
nds.ensataHandshake = ENSATA_HANDSHAKE_none;
nds.ensataIpcSyncCounter = 0;
nds_timer = 0;
nds_arm9_timer = 0;
nds_arm7_timer = 0;
LidClosed = FALSE;
countLid = 0;
MMU_Reset();
SetupMMU(nds.Is_DebugConsole(),nds.Is_DSI());
JumbleMemory();
#ifdef HAVE_JIT
arm_jit_reset(CommonSettings.use_jit);
#endif
//initialize CP15 specially for this platform
//TODO - how much of this is necessary for firmware boot?
//(only ARM9 has CP15)
reconstruct(&cp15);
MMU.ARM9_RW_MODE = BIT7(cp15.ctrl);
NDS_ARM9.intVector = 0xFFFF0000 * (BIT13(cp15.ctrl));
NDS_ARM9.LDTBit = !BIT15(cp15.ctrl); //TBit
PrepareBiosARM7();
PrepareBiosARM9();
if (firmware)
{
delete firmware;
firmware = NULL;
}
firmware = new CFIRMWARE();
firmware->load();
//we will allow a proper firmware boot, if:
//1. we have the ARM7 and ARM9 bioses (its doubtful that our HLE bios implement the necessary functions)
//2. firmware is available
//3. user has requested booting from firmware
bool canBootFromFirmware = (NDS_ARM7.BIOS_loaded && NDS_ARM9.BIOS_loaded && CommonSettings.BootFromFirmware && firmware->loaded());
bool bootResult = false;
if(canBootFromFirmware)
bootResult = NDS_LegitBoot();
else
bootResult = NDS_FakeBoot();
// Init calibration info
memcpy(&TSCal, firmware->getTouchCalibrate(), sizeof(TSCalInfo));
GPU->Reset();
WIFI_Reset();
memcpy(FW_Mac, (MMU.fw.data + 0x36), 6);
SPU_DeInit();
SPU_ReInit(!canBootFromFirmware && bootResult);
//this needs to happen last, pretty much, since it establishes the correct scheduling state based on all of the above initialization
initSchedule();
}
//convert a 12.4 screen coordinate to an ADC value.
//the desmume host system will track the screen coordinate, but the hardware should be receiving the raw ADC values.
//so we'll need to use this to simulate the ADC values corresponding to the desired screen coords, based on the current TSC calibrations
u16 NDS_getADCTouchPosX(int scrX_lsl4)
{
scrX_lsl4 >>= 4;
int rv = ((scrX_lsl4 - TSCal.scr.x1 + 1) * TSCal.adc.width) / TSCal.scr.width + TSCal.adc.x1;
rv = min(0xFFF, max(0, rv));
return (u16)(rv);
}
u16 NDS_getADCTouchPosY(int scrY_lsl4)
{
scrY_lsl4 >>= 4;
int rv = ((scrY_lsl4 - TSCal.scr.y1 + 1) * TSCal.adc.height) / TSCal.scr.height + TSCal.adc.y1;
rv = min(0xFFF, max(0, rv));
return (u16)(rv);
}
static UserInput rawUserInput = {}; // requested input, generally what the user is physically pressing
static UserInput finalUserInput = {}; // what gets sent to the game and possibly recorded
const UserInput& NDS_getRawUserInput()
{
return rawUserInput;
}
UserInput& NDS_getProcessingUserInput()
{
return rawUserInput;
}
const UserInput& NDS_getFinalUserInput()
{
return finalUserInput;
}
static void saveUserInput(EMUFILE* os, UserInput& input)
{
os->fwrite((const char*)input.buttons.array, 14);
writebool(input.touch.isTouch, os);
write16le(input.touch.touchX, os);
write16le(input.touch.touchY, os);
write32le(input.mic.micButtonPressed, os);
}
static bool loadUserInput(EMUFILE* is, UserInput& input, int version)
{
is->fread((char*)input.buttons.array, 14);
readbool(&input.touch.isTouch, is);
read16le(&input.touch.touchX, is);
read16le(&input.touch.touchY, is);
read32le(&input.mic.micButtonPressed, is);
return true;
}
static void resetUserInput(UserInput& input)
{
memset(&input, 0, sizeof(UserInput));
}
// (userinput is kind of a misnomer, e.g. finalUserInput has to mirror nds.pad, nds.touchX, etc.)
static void saveUserInput(EMUFILE* os)
{
saveUserInput(os, finalUserInput);
saveUserInput(os, rawUserInput); // saved in case a savestate is made during input processing (which Lua could do if nothing else)
bool validToProcessInput = true;
writebool(validToProcessInput, os);
for(int i = 0; i < 14; i++)
write32le(0, os); // saved to make autofire more tolerable to use with re-recording
}
static bool loadUserInput(EMUFILE* is, int version)
{
bool rv = true;
bool validToProcessInput = true;
rv &= loadUserInput(is, finalUserInput, version);
rv &= loadUserInput(is, rawUserInput, version);
readbool(&validToProcessInput, is);
u32 j = 0;
for(int i = 0; i < 14; i++)
read32le((u32*)&j, is);
return rv;
}
static void resetUserInput()
{
resetUserInput(finalUserInput);
}
void NDS_setPad(bool R,bool L,bool D,bool U,bool T,bool S,bool B,bool A,bool Y,bool X,bool W,bool E,bool G, bool F)
{
UserButtons& rawButtons = rawUserInput.buttons;
rawButtons.R = R;
rawButtons.L = L;
rawButtons.D = D;
rawButtons.U = U;
rawButtons.T = T;
rawButtons.S = S;
rawButtons.B = B;
rawButtons.A = A;
rawButtons.Y = Y;
rawButtons.X = X;
rawButtons.W = W;
rawButtons.E = E;
rawButtons.G = G;
rawButtons.F = F;
}
void NDS_setTouchPos(u16 x, u16 y, u16 scale)
{
rawUserInput.touch.touchX = (x/scale)<<4;
rawUserInput.touch.touchY = (y/scale)<<4;
rawUserInput.touch.isTouch = true;
nds.stylusJitter = CommonSettings.StylusJitter;
}
void NDS_releaseTouch(void)
{
rawUserInput.touch.touchX = 0;
rawUserInput.touch.touchY = 0;
rawUserInput.touch.isTouch = false;
}
void NDS_setMic(bool pressed)
{
rawUserInput.mic.micButtonPressed = (pressed ? TRUE : FALSE);
}
static void NDS_applyFinalInput()
{
const UserInput& input = NDS_getFinalUserInput();
u16 pad = (0 |
((input.buttons.A ? 0 : 0x80) >> 7) |
((input.buttons.B ? 0 : 0x80) >> 6) |
((input.buttons.T ? 0 : 0x80) >> 5) |
((input.buttons.S ? 0 : 0x80) >> 4) |
((input.buttons.R ? 0 : 0x80) >> 3) |
((input.buttons.L ? 0 : 0x80) >> 2) |
((input.buttons.U ? 0 : 0x80) >> 1) |
((input.buttons.D ? 0 : 0x80) ) |
((input.buttons.E ? 0 : 0x80) << 1) |
((input.buttons.W ? 0 : 0x80) << 2)) ;
pad = LOCAL_TO_LE_16(pad);
((u16 *)MMU.ARM9_REG)[0x130>>1] = (u16)pad;
((u16 *)MMU.ARM7_REG)[0x130>>1] = (u16)pad;
u16 k_cnt = ((u16 *)MMU.ARM9_REG)[0x132>>1];
if ( k_cnt & (1<<14))
{
//INFO("ARM9: KeyPad IRQ (pad 0x%04X, cnt 0x%04X (condition %s))\n", pad, k_cnt, k_cnt&(1<<15)?"AND":"OR");
u16 k_cnt_selected = (k_cnt & 0x3F);
if (k_cnt&(1<<15)) // AND
{
if ((~pad & k_cnt_selected) == k_cnt_selected) NDS_makeIrq(ARMCPU_ARM9,IRQ_BIT_KEYPAD);
}
else // OR
{
if (~pad & k_cnt_selected) NDS_makeIrq(ARMCPU_ARM9,IRQ_BIT_KEYPAD);
}
}
k_cnt = ((u16 *)MMU.ARM7_REG)[0x132>>1];
if ( k_cnt & (1<<14))
{
//INFO("ARM7: KeyPad IRQ (pad 0x%04X, cnt 0x%04X (condition %s))\n", pad, k_cnt, k_cnt&(1<<15)?"AND":"OR");
u16 k_cnt_selected = (k_cnt & 0x3F);
if (k_cnt&(1<<15)) // AND
{
if ((~pad & k_cnt_selected) == k_cnt_selected) NDS_makeIrq(ARMCPU_ARM7,IRQ_BIT_KEYPAD);
}
else // OR
{
if (~pad & k_cnt_selected) NDS_makeIrq(ARMCPU_ARM7,IRQ_BIT_KEYPAD);
}
}
if(input.touch.isTouch)
{
u16 adc_x = NDS_getADCTouchPosX(input.touch.touchX);
u16 adc_y = NDS_getADCTouchPosY(input.touch.touchY);
nds.adc_touchX = adc_x;
nds.adc_touchY = adc_y;
nds.adc_jitterctr = 0;
nds.scr_touchX = input.touch.touchX;
nds.scr_touchY = input.touch.touchY;
nds.isTouch = 1;
}
else
{
nds.adc_touchX = 0;
nds.adc_touchY = 0;
nds.scr_touchX = 0;
nds.scr_touchY = 0;
nds.isTouch = 0;
}
if (input.buttons.F && !countLid)
{
LidClosed = (!LidClosed) & 0x01;
if (!LidClosed)
{
NDS_makeIrq(ARMCPU_ARM7,IRQ_BIT_ARM7_FOLD);
}
countLid = 30;
}
else
{
if (countLid > 0)
countLid--;
}
u16 padExt = ((input.buttons.X ? 0 : 0x80) >> 7) |
((input.buttons.Y ? 0 : 0x80) >> 6) |
((input.buttons.G ? 0 : 0x80) >> 4) |
((LidClosed) << 7) |
0x0034;
padExt = LOCAL_TO_LE_16(padExt);
padExt |= (((u16 *)MMU.ARM7_REG)[0x136>>1] & 0x0070);
((u16 *)MMU.ARM7_REG)[0x136>>1] = (u16)padExt;
//put into the format we want for the movie system
//fRLDUTSBAYXWEg
//we don't really need nds.pad anymore, but removing it would be a pain
nds.pad =
((input.buttons.R ? 1 : 0) << 12)|
((input.buttons.L ? 1 : 0) << 11)|
((input.buttons.D ? 1 : 0) << 10)|
((input.buttons.U ? 1 : 0) << 9)|
((input.buttons.T ? 1 : 0) << 8)|
((input.buttons.S ? 1 : 0) << 7)|
((input.buttons.B ? 1 : 0) << 6)|
((input.buttons.A ? 1 : 0) << 5)|
((input.buttons.Y ? 1 : 0) << 4)|
((input.buttons.X ? 1 : 0) << 3)|
((input.buttons.W ? 1 : 0) << 2)|
((input.buttons.E ? 1 : 0) << 1);
}
void NDS_beginProcessingInput()
{
}
void NDS_endProcessingInput()
{
// transfer the processed input
finalUserInput = rawUserInput;
// use the final input for a few things right away
NDS_applyFinalInput();
}
void NDS_suspendProcessingInput(bool suspend)
{
static int suspendCount = 0;
if(suspend)
suspendCount++;
else if(suspendCount)
suspendCount--;
}
void NDS_swapScreen()
{
if (GPU->GetDisplayMain()->GetEngineID() == GPUEngineID_Main)
{
GPU->GetDisplayMain()->SetEngineByID(GPUEngineID_Sub);
GPU->GetDisplayTouch()->SetEngineByID(GPUEngineID_Main);
}
else
{
GPU->GetDisplayMain()->SetEngineByID(GPUEngineID_Main);
GPU->GetDisplayTouch()->SetEngineByID(GPUEngineID_Sub);
}
}
#if defined(LOG_ARM9) || defined(LOG_ARM7)
void emu_halt()
{
//printf("halting emu: ARM9 PC=%08X/%08X, ARM7 PC=%08X/%08X\n", NDS_ARM9.R[15], NDS_ARM9.instruct_adr, NDS_ARM7.R[15], NDS_ARM7.instruct_adr);
execute = false;
#ifdef LOG_ARM9
if (fp_dis9)
{
char buf[256] = { 0 };
sprintf(buf, "halting emu: ARM9 PC=%08X/%08X\n", NDS_ARM9.R[15], NDS_ARM9.instruct_adr);
fwrite(buf, 1, strlen(buf), fp_dis9);
INFO("ARM9 halted\n");
}
#endif
#ifdef LOG_ARM7
if (fp_dis7)
{
char buf[256] = { 0 };
sprintf(buf, "halting emu: ARM7 PC=%08X/%08X\n", NDS_ARM7.R[15], NDS_ARM7.instruct_adr);
fwrite(buf, 1, strlen(buf), fp_dis7);
INFO("ARM7 halted\n");
}
#endif
}
#endif
//returns true if exmemcnt specifies satisfactory parameters for the device, which calls this function
bool ValidateSlot2Access(u32 procnum, u32 demandSRAMSpeed, u32 demand1stROMSpeed, u32 demand2ndROMSpeed, int clockbits)
{
static const u32 _sramSpeeds[] = {10,8,6,18};
static const u32 _rom1Speeds[] = {10,8,6,18};
static const u32 _rom2Speeds[] = {6,4};
u16 exmemcnt = T1ReadWord(MMU.MMU_MEM[procnum][0x40], 0x204);
u16 exmemcnt9 = T1ReadWord(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x204);
u32 arm7access = (exmemcnt9 & EXMEMCNT_MASK_SLOT2_ARM7);
u32 sramSpeed = _sramSpeeds[(exmemcnt & EXMEMCNT_MASK_SLOT2_SRAM_TIME)];
u32 romSpeed1 = _rom1Speeds[(exmemcnt & EXMEMCNT_MASK_SLOT2_ROM_1ST_TIME)>>2];
u32 romSpeed2 = _rom2Speeds[(exmemcnt & EXMEMCNT_MASK_SLOT2_ROM_2ND_TIME)>>4];
u32 curclockbits = (exmemcnt & EXMEMCNT_MASK_SLOT2_CLOCKRATE)>>5;
if(procnum==ARMCPU_ARM9 && arm7access) return false;
if(procnum==ARMCPU_ARM7 && !arm7access) return false;
//what we're interested in here is whether the rom/ram are too low -> too fast. then accesses won't have enough time to work.
//i'm not sure if this gives us enough flexibility, but it is good enough for now.
//should make the arguments to this function bitmasks later if we need better.
if(sramSpeed < demandSRAMSpeed) return false;
if(romSpeed1 < demand1stROMSpeed) return false;
if(romSpeed2 < demand2ndROMSpeed) return false;
if(clockbits != -1 && clockbits != (int)curclockbits) return false;
return true;
}
desmume/src/libretro-common/include/features/ 000700 001750 001750 00000000000 12756420131 022466 5 ustar 00sergio sergio 000000 000000 desmume/src/utils/AsmJit/x86/x86util.cpp 000664 001750 001750 00000005500 12755534123 021142 0 ustar 00sergio sergio 000000 000000 // [AsmJit]
// Complete JIT Assembler for C++ Language.
//
// [License]
// Zlib - See COPYING file in this package.
#define ASMJIT_EXPORTS
// [Dependencies - AsmJit]
#include "../x86/x86defs.h"
// [Api-Begin]
#include "../core/apibegin.h"
namespace AsmJit {
// ============================================================================
// [AsmJit::_x86UtilJccFromCond]
// ============================================================================
const uint32_t _x86UtilJccFromCond[20] =
{
kX86InstJO,
kX86InstJNO,
kX86InstJB,
kX86InstJAE,
kX86InstJE,
kX86InstJNE,
kX86InstJBE,
kX86InstJA,
kX86InstJS,
kX86InstJNS,
kX86InstJPE,
kX86InstJPO,
kX86InstJL,
kX86InstJGE,
kX86InstJLE,
kX86InstJG,
kInstNone,
kInstNone,
kInstNone,
kInstNone
};
// ============================================================================
// [AsmJit::_x86UtilMovccFromCond]
// ============================================================================
const uint32_t _x86UtilMovccFromCond[20] =
{
kX86InstCMovO,
kX86InstCMovNO,
kX86InstCMovB,
kX86InstCMovAE,
kX86InstCMovE,
kX86InstCMovNE,
kX86InstCMovBE,
kX86InstCMovA,
kX86InstCMovS,
kX86InstCMovNS,
kX86InstCMovPE,
kX86InstCMovPO,
kX86InstCMovL,
kX86InstCMovGE,
kX86InstCMovLE,
kX86InstCMovG,
kInstNone,
kInstNone,
kInstNone,
kInstNone
};
// ============================================================================
// [AsmJit::_x86UtilSetccFromCond]
// ============================================================================
const uint32_t _x86UtilSetccFromCond[20] =
{
kX86InstSetO,
kX86InstSetNO,
kX86InstSetB,
kX86InstSetAE,
kX86InstSetE,
kX86InstSetNE,
kX86InstSetBE,
kX86InstSetA,
kX86InstSetS,
kX86InstSetNS,
kX86InstSetPE,
kX86InstSetPO,
kX86InstSetL,
kX86InstSetGE,
kX86InstSetLE,
kX86InstSetG,
kInstNone,
kInstNone,
kInstNone,
kInstNone
};
// ============================================================================
// [AsmJit::_x86UtilReversedCond]
// ============================================================================
const uint32_t _x86UtilReversedCond[20] =
{
/* x86CondO -> */ kX86CondO,
/* x86CondNO -> */ kX86CondNO,
/* x86CondB -> */ kX86CondA,
/* x86CondAE -> */ kX86CondBE,
/* x86CondE -> */ kX86CondE,
/* x86CondNE -> */ kX86CondNE,
/* x86CondBE -> */ kX86CondAE,
/* x86CondA -> */ kX86CondB,
/* x86CondS -> */ kX86CondS,
/* x86CondNS -> */ kX86CondNS,
/* x86CondPE -> */ kX86CondPE,
/* x86CondPO -> */ kX86CondPO,
/* x86CondL -> */ kX86CondG,
/* x86CondGE -> */ kX86CondLE,
/* x86CondLE -> */ kX86CondGE,
/* x86CondG -> */ kX86CondL,
/* kX86CondFpuUnordered -> */ kX86CondFpuUnordered,
/* kX86CondFpuNotUnordered -> */ kX86CondFpuNotUnordered,
0x12,
0x13
};
} // AsmJit namespace
#include "../core/apiend.h"
desmume/src/libretro-common/include/rthreads/rsemaphore.h 000664 001750 001750 00000003634 12755534123 025032 0 ustar 00sergio sergio 000000 000000 /* Copyright (C) 2010-2015 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rsemaphore.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_SEMAPHORE_H
#define __LIBRETRO_SDK_SEMAPHORE_H
#include
RETRO_BEGIN_DECLS
typedef struct ssem ssem_t;
/**
* ssem_create:
* @value : initial value for the semaphore
*
* Create a new semaphore.
*
* Returns: pointer to new semaphore if successful, otherwise NULL.
*/
ssem_t *ssem_new(int value);
void ssem_free(ssem_t *semaphore);
int ssem_get(ssem_t *semaphore);
void ssem_wait(ssem_t *semaphore);
void ssem_signal(ssem_t *semaphore);
RETRO_END_DECLS
#endif /* __LIBRETRO_SDK_SEMAPHORE_H */
desmume/src/libretro-common/conversion/ 000700 001750 001750 00000000000 12756420131 021412 5 ustar 00sergio sergio 000000 000000 desmume/src/utils/libfat/common.h 000664 001750 001750 00000005425 12755534123 020207 0 ustar 00sergio sergio 000000 000000 /*
common.h
Common definitions and included files for the FATlib
Copyright (c) 2006 Michael "Chishm" Chisholm
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _COMMON_H
#define _COMMON_H
#define BYTES_PER_READ 512
#include
#ifndef _MSC_VER
#include
#endif
// When compiling for NDS, make sure NDS is defined
#ifndef NDS
#if defined ARM9 || defined ARM7
#define NDS
#endif
#endif
// Platform specific includes
#if defined(__gamecube__) || defined (__wii__)
#include
#include
#include
#elif defined(NDS)
#include
#include
#include
#elif defined(GBA)
#include
#include
#endif
#include
// Platform specific options
#if defined (__wii__)
#define DEFAULT_CACHE_PAGES 4
#define DEFAULT_SECTORS_PAGE 64
#define USE_LWP_LOCK
#define USE_RTC_TIME
#elif defined (__gamecube__)
#define DEFAULT_CACHE_PAGES 4
#define DEFAULT_SECTORS_PAGE 64
#define USE_LWP_LOCK
#define USE_RTC_TIME
#elif defined (NDS)
#define DEFAULT_CACHE_PAGES 4
#define DEFAULT_SECTORS_PAGE 8
#define USE_RTC_TIME
#elif defined (GBA)
#define DEFAULT_CACHE_PAGES 2
#define DEFAULT_SECTORS_PAGE 8
#define LIMIT_SECTORS 128
#else
#define LIBFAT_PC 1
//any particular reason for this?
#define DEFAULT_CACHE_PAGES 4
#define DEFAULT_SECTORS_PAGE 8
#endif
#include "libfat_pc.h"
#include "fat.h"
#endif // _COMMON_H
desmume/src/utils/AsmJit/core/assert.h 000664 001750 001750 00000003012 12755534123 021064 0 ustar 00sergio sergio 000000 000000 // [AsmJit]
// Complete JIT Assembler for C++ Language.
//
// [License]
// Zlib - See COPYING file in this package.
// [Guard]
#ifndef _ASMJIT_CORE_ASSERT_H
#define _ASMJIT_CORE_ASSERT_H
// [Dependencies - AsmJit]
#include "../core/build.h"
// [Api-Begin]
#include "../core/apibegin.h"
namespace AsmJit {
//! @addtogroup AsmJit_Core
//! @{
// ============================================================================
// [AsmJit::Assert]
// ============================================================================
//! @brief Called in debug build on assertion failure.
//! @param file Source file name where it happened.
//! @param line Line in the source file.
//! @param exp Expression what failed.
//!
//! If you have problems with assertions simply put a breakpoint into
//! AsmJit::assertionFailure() method (AsmJit/Core/Assert.cpp file) and examine
//! call stack.
ASMJIT_API void assertionFailure(const char* file, int line, const char* exp);
// ============================================================================
// [ASMJIT_ASSERT]
// ============================================================================
#if defined(ASMJIT_DEBUG)
#if !defined(ASMJIT_ASSERT)
#define ASMJIT_ASSERT(exp) \
do { \
if (!(exp)) ::AsmJit::assertionFailure(__FILE__, __LINE__, #exp); \
} while(0)
#endif
#else
#if !defined(ASMJIT_ASSERT)
#define ASMJIT_ASSERT(exp) ASMJIT_NOP()
#endif
#endif // DEBUG
//! @}
} // AsmJit namespace
// [Api-End]
#include "../core/apiend.h"
// [Guard]
#endif // _ASMJIT_CORE_ASSERT_H
desmume/src/libretro-common/include/compat/msvc/ 000700 001750 001750 00000000000 12756420131 023103 5 ustar 00sergio sergio 000000 000000 desmume/src/libretro-common/gfx/scaler/ 000700 001750 001750 00000000000 12756420131 021262 5 ustar 00sergio sergio 000000 000000 desmume/src/libretro-common/formats/jpeg/ 000700 001750 001750 00000000000 12756420131 021625 5 ustar 00sergio sergio 000000 000000 desmume/src/libretro-common/file/nbio/test/nbio_test 000755 001750 001750 00000036450 12755534123 024016 0 ustar 00sergio sergio 000000 000000 ELF > `@ @ (4 @ 8 @ $ ! @ @ @ @ @ À À @ @ @ @ Ô Ô Ø Ø` Ø` Ø à ð ð` ð` Ð Ð @ @ D D Påtd @ @ „ „ Qåtd /lib64/ld-linux-x86-64.so.2 GNU GNU ³çÙƒž4Ñá:Z¤ßðuñ8ö5<¢ k $ S > * Y 7 p E 1 L libc.so.6 fopen ftell puts realloc abort memset fseek memcmp fclose malloc fwrite fread __libc_start_main free __gmon_start__ GLIBC_2.2.5 ui À`
à` è` ð` ø` ` ` ` ` ` (` 0` 8`
@` H` HƒìH‹} H…Àtè HƒÄà ÿ5j ÿ%l @ ÿ%j h éàÿÿÿÿ%b h éÐÿÿÿÿ%Z h éÀÿÿÿÿ%R h é°ÿÿÿÿ%J h é ÿÿÿÿ%B h éÿÿÿÿ%: h é€ÿÿÿÿ%2 h épÿÿÿÿ%* h é`ÿÿÿÿ%" h éPÿÿÿÿ% h
é@ÿÿÿÿ% h é0ÿÿÿÿ%
h é ÿÿÿÿ% h
éÿÿÿÿ%j f 1íI‰Ñ^H‰âHƒäðPTIÇÀ @ HÇÁ°@ HÇÇ"
@ èGÿÿÿôfD ¸·` UH-°` HƒøH‰åv¸ H…Àt]¿°` ÿàf„ ]Ã@ f.„ ¾°` UHî°` HÁþH‰åH‰ðHÁè?HÆHÑþt¸ H…Àt]¿°` ÿà ]ÃfD €=™ uUH‰åènÿÿÿ]Ɔ óÃ@ ¿è` Hƒ? uë“ ¸ H…ÀtñUH‰åÿÐ]ézÿÿÿUH‰åHƒì ÆEÿ HÇEð ¾ ¿8@ è¾ H‰EèH‹Eè¾ H‰Çè‡ HUàH‹EèH‰ÖH‰Çè H‰EðH‹EàH= t
¿A@ èÕýÿÿH‹E𺠾B H‰ÇèïýÿÿH‹EèH‰Çè- ëÆEÿH‹EèH‰Çè{ ƒð„Àué¶Eÿƒð„Àt
¿W@ è…ýÿÿH‹EèH‰Çè ÉÃUH‰åHƒì ÆEÿ ¾ ¿8@ è H‰EðHUàH‹EðH‰ÖH‰Çèk H‰EèH‹EàH= t
¿s@ è*ýÿÿHƒ}è t
¿@ èýÿÿH‹EðH‰Çè' ëÆEÿH‹EðH‰ÇèÕ ƒð„Àué¶Eÿƒð„Àt
¿¼@ èßüÿÿHUàH‹EðH‰ÖH‰Çèó H‰EèH‹EàH= t
¿×@ è²üÿÿH‹Eè¶