/* Copyright (C) 2006 yopyop Copyright (C) 2008-2013 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 #include #include #include #include #include "NDSSystem.h" #include "MMU.h" #include "cp15.h" #include "bios.h" #include "readwrite.h" #include "firmware.h" #include "version.h" #include "slot1.h" // =============================================================== TCommonSettings CommonSettings; GameInfo gameInfo; NDSSystem nds; static std::unique_ptr firmware; namespace DLDI { bool tryPatch(void *data, size_t size); } int NDS_Init() { MMU_Init(); nds.VCount = 0; // got to print this somewhere.. printf("%s\n", EMU_DESMUME_NAME_AND_VERSION()); armcpu_new(&NDS_ARM7, 1); armcpu_new(&NDS_ARM9, 0); if (SPU_Init(SNDCORE_DUMMY, 740)) return -1; return 0; } void NDS_DeInit() { if (MMU.CART_ROM != MMU.UNUSED_RAM) NDS_FreeROM(); SPU_DeInit(); MMU_DeInit(); #ifdef HAVE_JIT arm_jit_close(); #endif } bool NDS_SetROM(uint8_t *rom, uint32_t mask) { MMU_setRom(rom, mask); return true; } std::unique_ptr NDS_getROMHeader() { if (MMU.CART_ROM == MMU.UNUSED_RAM) return std::unique_ptr(); auto header = std::unique_ptr(new NDS_header); memcpy(header->gameTile, MMU.CART_ROM, 12); memcpy(header->gameCode, MMU.CART_ROM + 12, 4); header->makerCode = T1ReadWord(MMU.CART_ROM, 16); header->unitCode = MMU.CART_ROM[18]; header->deviceCode = MMU.CART_ROM[19]; header->cardSize = MMU.CART_ROM[20]; memcpy(header->cardInfo, MMU.CART_ROM + 21, 8); header->flags = MMU.CART_ROM[29]; header->romversion = MMU.CART_ROM[30]; header->ARM9src = T1ReadLong(MMU.CART_ROM, 32); header->ARM9exe = T1ReadLong(MMU.CART_ROM, 36); header->ARM9cpy = T1ReadLong(MMU.CART_ROM, 40); header->ARM9binSize = T1ReadLong(MMU.CART_ROM, 44); header->ARM7src = T1ReadLong(MMU.CART_ROM, 48); header->ARM7exe = T1ReadLong(MMU.CART_ROM, 52); header->ARM7cpy = T1ReadLong(MMU.CART_ROM, 56); header->ARM7binSize = T1ReadLong(MMU.CART_ROM, 60); header->FNameTblOff = T1ReadLong(MMU.CART_ROM, 64); header->FNameTblSize = T1ReadLong(MMU.CART_ROM, 68); header->FATOff = T1ReadLong(MMU.CART_ROM, 72); header->FATSize = T1ReadLong(MMU.CART_ROM, 76); header->ARM9OverlayOff = T1ReadLong(MMU.CART_ROM, 80); header->ARM9OverlaySize = T1ReadLong(MMU.CART_ROM, 84); header->ARM7OverlayOff = T1ReadLong(MMU.CART_ROM, 88); header->ARM7OverlaySize = T1ReadLong(MMU.CART_ROM, 92); header->unknown2a = T1ReadLong(MMU.CART_ROM, 96); header->unknown2b = T1ReadLong(MMU.CART_ROM, 100); header->IconOff = T1ReadLong(MMU.CART_ROM, 104); header->CRC16 = T1ReadWord(MMU.CART_ROM, 108); header->ROMtimeout = T1ReadWord(MMU.CART_ROM, 110); header->ARM9unk = T1ReadLong(MMU.CART_ROM, 112); header->ARM7unk = T1ReadLong(MMU.CART_ROM, 116); memcpy(header->unknown3c, MMU.CART_ROM + 120, 8); header->ROMSize = T1ReadLong(MMU.CART_ROM, 128); header->HeaderSize = T1ReadLong(MMU.CART_ROM, 132); memcpy(header->unknown5, MMU.CART_ROM + 136, 56); memcpy(header->logo, MMU.CART_ROM + 192, 156); header->logoCRC16 = T1ReadWord(MMU.CART_ROM, 348); header->headerCRC16 = T1ReadWord(MMU.CART_ROM, 350); memcpy(header->reserved, MMU.CART_ROM + 352, std::min(160, gameInfo.romsize - 352)); return header; } RomBanner::RomBanner(bool defaultInit) { if (!defaultInit) return; this->version = 1; //Version (0001h) this->crc16 = 0; //CRC16 across entries 020h..83Fh memset(this->reserved, 0, sizeof(this->reserved)); memset(this->bitmap, 0, sizeof(this->bitmap)); memset(this->palette, 0, sizeof(this->palette)); memset(this->titles, 0, sizeof(this->titles)); memset(this->end0xFF, 0, sizeof(this->end0xFF)); } void NDS_FreeROM() { if (MMU.CART_ROM == reinterpret_cast(&gameInfo.romdata[0])) gameInfo.romdata.reset(); if (MMU.CART_ROM != MMU.UNUSED_RAM) delete [] MMU.CART_ROM; MMU_unsetRom(); } void NDS_Sleep() { nds.sleeping = true; } enum ESI_DISPCNT { ESI_DISPCNT_HStart, ESI_DISPCNT_HStartIRQ, ESI_DISPCNT_HDraw, ESI_DISPCNT_HBlank }; uint64_t nds_timer; static uint64_t nds_arm9_timer, nds_arm7_timer; struct TSequenceItem { uint64_t timestamp; uint32_t param; bool enabled; virtual ~TSequenceItem() { } virtual bool isTriggered() const { return this->enabled && nds_timer >= this->timestamp; } virtual uint64_t next() const { return this->timestamp; } }; template struct TSequenceItem_Timer : public TSequenceItem { bool isTriggered() const { return this->enabled && nds_timer >= nds.timerCycle[procnum][num]; } void schedule() { this->enabled = MMU.timerON[procnum][num] && MMU.timerMODE[procnum][num] != 0xFFFF; } uint64_t next() const { return nds.timerCycle[procnum][num]; } void exec() { uint8_t *regs = !procnum ? MMU.ARM9_REG : MMU.ARM7_REG; bool first = true, over; // we'll need to check chained timers.. for (int i = num; i < 4; ++i) { // 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; bool isTriggered() const { return this->controller->dmaCheck && nds_timer>= this->controller->nextEvent; } bool isEnabled() const { return this->controller->dmaCheck; } uint64_t next() const { return this->controller->nextEvent; } void exec() { //printf("exec from TSequenceItem_DMA: %d %d\n",procnum,chan); this->controller->exec(); } }; struct TSequenceItem_divider : public TSequenceItem { bool isTriggered() const { return MMU.divRunning && nds_timer >= MMU.divCycles; } bool isEnabled() { return MMU.divRunning; } uint64_t next() const { return MMU.divCycles; } void exec() { MMU_new.div.busy = 0; #ifdef _WIN64 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, MMU.divResult & 0xFFFFFFFF); T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A4, (MMU.divResult >> 32) & 0xFFFFFFFF); T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2A8, MMU.divMod & 0xFFFFFFFF); T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2AC, (MMU.divMod >> 32) & 0xFFFFFFFF); #endif MMU.divRunning = false; } }; struct TSequenceItem_sqrtunit : public TSequenceItem { bool isTriggered() const { return MMU.sqrtRunning && nds_timer >= MMU.sqrtCycles; } bool isEnabled() { return MMU.sqrtRunning; } uint64_t next() const { return MMU.sqrtCycles; } void exec() { MMU_new.sqrt.busy = 0; T1WriteLong(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x2B4, MMU.sqrtResult); MMU.sqrtRunning = false; } }; static 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(); uint64_t findNext(); } sequencer; 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 uint64_t kWifiCycles = 34*2; //(this isn't very precise. I don't think it needs to be) void Sequencer::init() { NDS_RescheduleTimers(); NDS_RescheduleDMA(); this->reschedule = false; nds_timer = 0; nds_arm9_timer = 0; nds_arm7_timer = 0; this->dispcnt.enabled = true; this->dispcnt.param = ESI_DISPCNT_HStart; this->dispcnt.timestamp = 0; this->dma_0_0.controller = &MMU_new.dma[0][0]; this->dma_0_1.controller = &MMU_new.dma[0][1]; this->dma_0_2.controller = &MMU_new.dma[0][2]; this->dma_0_3.controller = &MMU_new.dma[0][3]; this->dma_1_0.controller = &MMU_new.dma[1][0]; this->dma_1_1.controller = &MMU_new.dma[1][1]; this->dma_1_2.controller = &MMU_new.dma[1][2]; this->dma_1_3.controller = &MMU_new.dma[1][3]; } 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) // 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); // 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); } 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 << IRQ_BIT_LCD_VBLANK)) { MMU.reg_IF_pending[i] &= ~(1 << IRQ_BIT_LCD_VBLANK); NDS_makeIrq(i, IRQ_BIT_LCD_VBLANK); } // trigger vblank dmas triggerDma(EDMAMode_VBlank); } static uint16_t execHardware_gen_vmatch_goal() { uint16_t vmatch = T1ReadWord(MMU.ARM9_REG, 4); vmatch = (vmatch >> 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 << IRQ_BIT_LCD_VMATCH)) { MMU.reg_IF_pending[ARMCPU_ARM9] &= ~(1 << IRQ_BIT_LCD_VMATCH); NDS_makeIrq(ARMCPU_ARM9, IRQ_BIT_LCD_VMATCH); } if(MMU.reg_IF_pending[ARMCPU_ARM7] & (1 << IRQ_BIT_LCD_VMATCH)) { MMU.reg_IF_pending[ARMCPU_ARM7] &= ~(1 << IRQ_BIT_LCD_VMATCH); NDS_makeIrq(ARMCPU_ARM7, IRQ_BIT_LCD_VMATCH); } } static void execHardware_hstart_vcount() { uint16_t vmatch = execHardware_gen_vmatch_goal(); if (nds.VCount == vmatch) { // arm9 vmatch T1WriteWord(MMU.ARM9_REG, 4, T1ReadWord(MMU.ARM9_REG, 4) | 4); if (T1ReadWord(MMU.ARM9_REG, 4) & 32) MMU.reg_IF_pending[ARMCPU_ARM9] |= 1 << IRQ_BIT_LCD_VMATCH; } else T1WriteWord(MMU.ARM9_REG, 4, T1ReadWord(MMU.ARM9_REG, 4) & 0xFFFB); vmatch = T1ReadWord(MMU.ARM7_REG, 4); vmatch = (vmatch >> 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 << IRQ_BIT_LCD_VMATCH; } else T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & 0xFFFB); } static void execHardware_hstart_irq() { // this function very soon after the registers get updated to trigger IRQs // this is necessary to fix "egokoro kyoushitsu" which idles waiting for vcount=192, which never happens due to a long vblank irq // 100% accurate emulation would require the read of VCOUNT to be in the pipeline already with the irq coming in behind it, thus // allowing the vcount to register as 192 occasionally (maybe about 1 out of 28 frames) // the actual length of the delay is in execHardware() where the events are scheduled sequencer.reschedule = true; if (nds.VCount == 192) // when the vcount hits 192, vblank begins execHardware_hstart_vblankStart(); execHardware_hstart_vcount_irq(); } static void execHardware_hstart() { ++nds.VCount; if (nds.VCount == 263) // when the vcount hits 263 it rolls over to 0 nds.VCount = 0; if (nds.VCount == 262) // when the vcount hits 262, vblank ends (oam pre-renders by one scanline) execHardware_hstart_vblankEnd(); else if (nds.VCount == 192) { // turn on 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); // check whether we'll need to fire vblank irqs if (T1ReadWord(MMU.ARM9_REG, 4) & 0x8) MMU.reg_IF_pending[ARMCPU_ARM9] |= 1 << IRQ_BIT_LCD_VBLANK; if (T1ReadWord(MMU.ARM7_REG, 4) & 0x8) MMU.reg_IF_pending[ARMCPU_ARM7] |= 1 << IRQ_BIT_LCD_VBLANK; } // write the new vcount T1WriteWord(MMU.ARM9_REG, 6, nds.VCount & 0xFFFF); T1WriteWord(MMU.ARM9_REG, 0x1006, nds.VCount & 0xFFFF); T1WriteWord(MMU.ARM7_REG, 6, nds.VCount & 0xFFFF); T1WriteWord(MMU.ARM7_REG, 0x1006, nds.VCount & 0xFFFF); // turn off hblank status bit T1WriteWord(MMU.ARM9_REG, 4, T1ReadWord(MMU.ARM9_REG, 4) & 0xFFFD); T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & 0xFFFD); // handle vcount status execHardware_hstart_vcount(); // trigger hstart dmas triggerDma(EDMAMode_HStart); if (nds.VCount < 192) // this is hacky. // there is a corresponding hack in doDMA. // it should be driven by a fifo (and generate just in time as the scanline is displayed) // but that isnt even possible until we have some sort of sub-scanline timing. // it may not be necessary. triggerDma(EDMAMode_MemDisplay); } void NDS_Reschedule() { sequencer.reschedule = true; } static inline uint64_t _fast_min(uint64_t a, uint64_t b) { // you might find that this is faster on a 64bit system; someone should try it // http://aggregate.org/MAGIC/#Integer%20Selection //uint64_t 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]; } uint64_t Sequencer::findNext() { // this one is always enabled so dont bother to check it uint64_t next = this->dispcnt.next(); if (this->divider.isEnabled()) next = _fast_min(next, this->divider.next()); if (this->sqrtunit.isEnabled()) next = _fast_min(next, this->sqrtunit.next()); #define test(X, Y) \ if (this->dma_##X##_##Y .isEnabled()) \ next = _fast_min(next, this->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 (this->timer_##X##_##Y .enabled) \ next = _fast_min(next, this->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 (this->dispcnt.isTriggered()) { switch (this->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 this->dispcnt.timestamp += 12; this->dispcnt.param = ESI_DISPCNT_HStartIRQ; break; case ESI_DISPCNT_HStartIRQ: execHardware_hstart_irq(); this->dispcnt.timestamp += 84; this->dispcnt.param = ESI_DISPCNT_HDraw; break; case ESI_DISPCNT_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 this->dispcnt.timestamp += 3108; this->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 this->dispcnt.timestamp += 1056; this->dispcnt.param = ESI_DISPCNT_HStart; } } if (this->divider.isTriggered()) this->divider.exec(); if (this->sqrtunit.isTriggered()) this->sqrtunit.exec(); #define test(X, Y) \ if (this->dma_##X##_##Y .isTriggered()) \ this->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 (this->timer_##X##_##Y .enabled && this->timer_##X##_##Y .isTriggered()) \ this->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(); // these have not been tuned very well yet. static const int kMaxWork = 4000; static const int kIrqWait = 4000; template static inline int32_t minarmtime(int32_t arm9, int32_t arm7) { if (doarm9) { if (doarm7) return std::min(arm9, arm7); else return arm9; } else return arm7; } #ifdef HAVE_JIT template #else template #endif static std::pair armInnerLoop(uint64_t nds_timer_base, int32_t s32next, int32_t arm9, int32_t arm7) { int32_t timer = minarmtime(arm9, arm7); while (timer < s32next && !sequencer.reschedule && execute) { if (doarm9 && (!doarm7 || arm9 <= timer)) { if (!NDS_ARM9.waitIRQ && !nds.freezeBus) { #ifdef HAVE_JIT arm9 += armcpu_exec(); #else arm9 += armcpu_exec(); #endif } else arm9 = std::min(s32next, arm9 + kIrqWait); } if (doarm7 && (!doarm9 || arm7 <= timer)) { if (!NDS_ARM7.waitIRQ && !nds.freezeBus) { #ifdef HAVE_JIT arm7 += armcpu_exec() << 1; #else arm7 += armcpu_exec() << 1; #endif } else { arm7 = std::min(s32next, arm7 + kIrqWait); 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); } template void NDS_exec(int32_t) { sequencer.nds_vblankEnded = false; 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 (;;) { 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: uint64_t next = sequencer.findNext(); next = std::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 uint64_t nds_timer_base = nds_timer; int32_t arm9 = (nds_arm9_timer - nds_timer) & 0xFFFFFFFF; int32_t arm7 = (nds_arm7_timer - nds_timer) & 0xFFFFFFFF; int32_t s32next = (next - nds_timer) & 0xFFFFFFFF; #ifdef HAVE_JIT auto arm9arm7 = CommonSettings.use_jit ? armInnerLoop(nds_timer_base, s32next, arm9, arm7) : armInnerLoop(nds_timer_base, s32next, arm9, arm7); #else auto arm9arm7 = armInnerLoop(nds_timer_base, s32next, arm9, arm7); #endif arm9 = arm9arm7.first; arm7 = arm9arm7.second; nds_arm7_timer = nds_timer_base + arm7; nds_arm9_timer = nds_timer_base + arm9; // 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_arm9_timer = nds_timer; if (NDS_ARM7.waitIRQ) nds_arm7_timer = nds_timer; } } } template static void execHardware_interrupts_core() { uint32_t IF = MMU.gen_IF(); uint32_t IE = MMU.reg_IE[PROCNUM]; uint32_t 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 PrepareBiosARM7() { NDS_ARM7.BIOS_loaded = false; memset(MMU.ARM7_BIOS, 0, sizeof(MMU.ARM7_BIOS)); if (CommonSettings.UseExtBIOS) { // read arm7 bios from inputfile and flag it if it succeeds FILE *arm7inf = fopen(CommonSettings.ARM7BIOS, "rb"); 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) _MMU_write16(0x00002F08, 0x4770); } else NDS_ARM7.swi_tab = ARM_swi_tab[ARMCPU_ARM7]; if (!NDS_ARM7.BIOS_loaded) { // fake bios content, critical to normal operations, since we dont have a real bios. #if 0 // someone please document what is in progress here // TODO T1WriteLong(MMU.ARM7_BIOS, 0x0000, 0xEAFFFFFE); // loop for Reset !!! T1WriteLong(MMU.ARM7_BIOS, 0x0004, 0xEAFFFFFE); // loop for Undef instr expection T1WriteLong(MMU.ARM7_BIOS, 0x0008, 0xEA00009C); // SWI T1WriteLong(MMU.ARM7_BIOS, 0x000C, 0xEAFFFFFE); // loop for Prefetch Abort T1WriteLong(MMU.ARM7_BIOS, 0x0010, 0xEAFFFFFE); // loop for Data Abort T1WriteLong(MMU.ARM7_BIOS, 0x0014, 0x00000000); // Reserved T1WriteLong(MMU.ARM7_BIOS, 0x001C, 0x00000000); // Fast IRQ #endif T1WriteLong(MMU.ARM7_BIOS, 0x0000, 0xE25EF002); T1WriteLong(MMU.ARM7_BIOS, 0x0018, 0xEA000000); T1WriteLong(MMU.ARM7_BIOS, 0x0020, 0xE92D500F); T1WriteLong(MMU.ARM7_BIOS, 0x0024, 0xE3A00301); T1WriteLong(MMU.ARM7_BIOS, 0x0028, 0xE28FE000); T1WriteLong(MMU.ARM7_BIOS, 0x002C, 0xE510F004); T1WriteLong(MMU.ARM7_BIOS, 0x0030, 0xE8BD500F); T1WriteLong(MMU.ARM7_BIOS, 0x0034, 0xE25EF004); } } static void PrepareBiosARM9() { memset(MMU.ARM9_BIOS, 0, sizeof(MMU.ARM9_BIOS)); NDS_ARM9.BIOS_loaded = false; if (CommonSettings.UseExtBIOS) { // read arm9 bios from inputfile and flag it if it succeeds FILE *arm9inf = fopen(CommonSettings.ARM9BIOS, "rb"); 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 if (CommonSettings.PatchSWI3) _MMU_write16(0xFFFF07CC, 0x4770); } else NDS_ARM9.swi_tab = ARM_swi_tab[ARMCPU_ARM9]; if (!NDS_ARM9.BIOS_loaded) { // 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 static const uint8_t logo_data[] = { 0x24, 0xFF, 0xAE, 0x51, 0x69, 0x9A, 0xA2, 0x21, 0x3D, 0x84, 0x82, 0x0A, 0x84, 0xE4, 0x09, 0xAD, 0x11, 0x24, 0x8B, 0x98, 0xC0, 0x81, 0x7F, 0x21, 0xA3, 0x52, 0xBE, 0x19, 0x93, 0x09, 0xCE, 0x20, 0x10, 0x46, 0x4A, 0x4A, 0xF8, 0x27, 0x31, 0xEC, 0x58, 0xC7, 0xE8, 0x33, 0x82, 0xE3, 0xCE, 0xBF, 0x85, 0xF4, 0xDF, 0x94, 0xCE, 0x4B, 0x09, 0xC1, 0x94, 0x56, 0x8A, 0xC0, 0x13, 0x72, 0xA7, 0xFC, 0x9F, 0x84, 0x4D, 0x73, 0xA3, 0xCA, 0x9A, 0x61, 0x58, 0x97, 0xA3, 0x27, 0xFC, 0x03, 0x98, 0x76, 0x23, 0x1D, 0xC7, 0x61, 0x03, 0x04, 0xAE, 0x56, 0xBF, 0x38, 0x84, 0x00, 0x40, 0xA7, 0x0E, 0xFD, 0xFF, 0x52, 0xFE, 0x03, 0x6F, 0x95, 0x30, 0xF1, 0x97, 0xFB, 0xC0, 0x85, 0x60, 0xD6, 0x80, 0x25, 0xA9, 0x63, 0xBE, 0x03, 0x01, 0x4E, 0x38, 0xE2, 0xF9, 0xA2, 0x34, 0xFF, 0xBB, 0x3E, 0x03, 0x44, 0x78, 0x00, 0x90, 0xCB, 0x88, 0x11, 0x3A, 0x94, 0x65, 0xC0, 0x7C, 0x63, 0x87, 0xF0, 0x3C, 0xAF, 0xD6, 0x25, 0xE4, 0x8B, 0x38, 0x0A, 0xAC, 0x72, 0x21, 0xD4, 0xF8, 0x07 }; // logo (do some games fail to boot without this? example?) for (int t = 0; t < 0x9C; ++t) MMU.ARM9_BIOS[t + 0x20] = logo_data[t]; //...0xBC: // (now what goes in this gap??) // 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, 0xE59FD028); // 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 // --------- } } void NDS_Reset() { bool fw_success = false; auto header = NDS_getROMHeader(); if (!header) return; nds.sleeping = false; nds.cardEjected = false; nds.freezeBus = 0; nds_timer = 0; nds_arm9_timer = 0; nds_arm7_timer = 0; SPU_DeInit(); MMU_Reset(); #ifdef HAVE_JIT arm_jit_reset(CommonSettings.use_jit); #endif PrepareBiosARM7(); PrepareBiosARM9(); // according to smea, this is initialized to 3 by the time we get into a user game program. who does this? // well, the firmware load process is about to write a boot program into SIWRAM for the arm7. so we need it setup by now. // but, this is a bit weird.. I would be expecting the bioses to do that. maybe we have some more detail to emulate. // * is this setting the default, or does the bios do it before loading the firmware programs? // at any, it's important that this be done long before the user code ever runs _MMU_write08(REG_WRAMCNT, 3); firmware.reset(new CFIRMWARE()); fw_success = firmware->load(); if (NDS_ARM7.BIOS_loaded && NDS_ARM9.BIOS_loaded && CommonSettings.BootFromFirmware && fw_success) { // Copy secure area to memory if needed. // could we get a comment about what's going on here? // how does this stuff get copied before anything ever even runs? // does it get mapped straight to the rom somehow? // This code could be made more clear too. if (header->ARM9src >= 0x4000 && header->ARM9src < 0x8000) { uint32_t src = header->ARM9src; uint32_t dst = header->ARM9cpy; uint32_t size = (0x8000 - src) >> 2; for (uint32_t i = 0; i < size; ++i) { _MMU_write32(dst, T1ReadLong(MMU.CART_ROM, src)); src += 4; dst += 4; } } // TODO someone describe why here if (firmware->patched) { armcpu_init(&NDS_ARM7, 0x00000008); armcpu_init(&NDS_ARM9, 0xFFFF0008); } else { // set the cpus to an initial state with their respective firmware program entrypoints armcpu_init(&NDS_ARM7, firmware->ARM7bootAddr); armcpu_init(&NDS_ARM9, firmware->ARM9bootAddr); } // set REG_POSTFLG to the value indicating pre-firmware status MMU.ARM9_REG[0x300] = 0; MMU.ARM7_REG[0x300] = 0; } else { // fake firmware boot-up process // copy the arm9 program to the address specified by rom header uint32_t src = header->ARM9src; uint32_t dst = header->ARM9cpy; for (uint32_t i = 0; i < (header->ARM9binSize >> 2); ++i) { _MMU_write32(dst, T1ReadLong(MMU.CART_ROM, src)); dst += 4; src += 4; } // copy the arm7 program to the address specified by rom header src = header->ARM7src; dst = header->ARM7cpy; for (uint32_t i = 0; i < (header->ARM7binSize >> 2); ++i) { _MMU_write32(dst, T1ReadLong(MMU.CART_ROM, src)); dst += 4; src += 4; } // set the cpus to an initial state with their respective programs entrypoints armcpu_init(&NDS_ARM7, header->ARM7exe); armcpu_init(&NDS_ARM9, header->ARM9exe); // set REG_POSTFLG to the value indicating post-firmware status MMU.ARM9_REG[0x300] = 1; MMU.ARM7_REG[0x300] = 1; } // only ARM9 have co-processor reconstruct(&cp15); cp15.reset(&NDS_ARM9); // 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. memset(nds.timerCycle, 0, sizeof(uint64_t) * 8); nds.old = 0; SetupMMU(false, nds.Is_DSI()); _MMU_write16(REG_KEYINPUT, 0x3FF); _MMU_write16(REG_KEYINPUT, 0x3FF); _MMU_write08(REG_EXTKEYIN, 0x43); // Setup a copy of the firmware user settings in memory. // (this is what the DS firmware would do). { uint8_t temp_buffer[NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT]; if (copy_firmware_user_data(temp_buffer, &MMU.fw.data[0])) 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]); } // Copy 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 < 92; ++i) _MMU_write32(0x02FFFE00 + i * 4, LE_TO_LOCAL_32(reinterpret_cast(MMU.CART_ROM)[i])); } else { for (int i = 0; i < 92; ++i) _MMU_write32(0x027FFE00 + i * 4, LE_TO_LOCAL_32(reinterpret_cast(MMU.CART_ROM)[i])); } // Write the header checksum to memory (the firmware needs it to see the cart) _MMU_write16(0x027FF808, T1ReadWord(MMU.CART_ROM, 0x15E)); if (firmware->patched && CommonSettings.UseExtBIOS && CommonSettings.BootFromFirmware && fw_success) { // HACK! for flashme _MMU_write32(0x27FFE24, firmware->ARM9bootAddr); _MMU_write32(0x27FFE34, firmware->ARM7bootAddr); } // make system think it's booted from card -- EXTREMELY IMPORTANT!!! Thanks to cReDiAr _MMU_write08(0x02FFFC40, 0x1); _MMU_write08(0x02FFFC40, 0x1); initSchedule(); SPU_ReInit(); } // these templates needed to be instantiated manually template void NDS_exec(int32_t nb); template void NDS_exec(int32_t nb);