diff options
-rw-r--r-- | src/DSi.cpp | 8 | ||||
-rw-r--r-- | src/GPU.cpp | 18 | ||||
-rw-r--r-- | src/GPU.h | 1 | ||||
-rw-r--r-- | src/NDS.cpp | 310 | ||||
-rw-r--r-- | src/NDS.h | 29 | ||||
-rw-r--r-- | src/Platform.h | 3 | ||||
-rw-r--r-- | src/RTC.cpp | 847 | ||||
-rw-r--r-- | src/RTC.h | 29 | ||||
-rw-r--r-- | src/SPI_Firmware.h | 2 | ||||
-rw-r--r-- | src/SPU.cpp | 2 | ||||
-rw-r--r-- | src/Savestate.h | 2 | ||||
-rw-r--r-- | src/frontend/qt_sdl/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/frontend/qt_sdl/Config.cpp | 8 | ||||
-rw-r--r-- | src/frontend/qt_sdl/Config.h | 6 | ||||
-rw-r--r-- | src/frontend/qt_sdl/DateTimeDialog.cpp | 91 | ||||
-rw-r--r-- | src/frontend/qt_sdl/DateTimeDialog.h | 70 | ||||
-rw-r--r-- | src/frontend/qt_sdl/DateTimeDialog.ui | 148 | ||||
-rw-r--r-- | src/frontend/qt_sdl/Platform.cpp | 10 | ||||
-rw-r--r-- | src/frontend/qt_sdl/ROMManager.cpp | 15 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.cpp | 42 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.h | 6 |
21 files changed, 1452 insertions, 196 deletions
diff --git a/src/DSi.cpp b/src/DSi.cpp index 15160b2..17bfb8f 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -959,10 +959,10 @@ void RunNDMAs(u32 cpu) { if (NDS::ARM9Timestamp >= NDS::ARM9Target) return; - if (!(NDS::CPUStop & 0x80000000)) NDMAs[0]->Run(); - if (!(NDS::CPUStop & 0x80000000)) NDMAs[1]->Run(); - if (!(NDS::CPUStop & 0x80000000)) NDMAs[2]->Run(); - if (!(NDS::CPUStop & 0x80000000)) NDMAs[3]->Run(); + if (!(NDS::CPUStop & NDS::CPUStop_GXStall)) NDMAs[0]->Run(); + if (!(NDS::CPUStop & NDS::CPUStop_GXStall)) NDMAs[1]->Run(); + if (!(NDS::CPUStop & NDS::CPUStop_GXStall)) NDMAs[2]->Run(); + if (!(NDS::CPUStop & NDS::CPUStop_GXStall)) NDMAs[3]->Run(); } else { diff --git a/src/GPU.cpp b/src/GPU.cpp index 07748be..630e88d 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -1096,6 +1096,24 @@ void FinishFrame(u32 lines) } } +void BlankFrame() +{ + int backbuf = FrontBuffer ? 0 : 1; + int fbsize; + if (GPU3D::CurrentRenderer->Accelerated) + fbsize = (256*3 + 1) * 192; + else + fbsize = 256 * 192; + + memset(Framebuffer[backbuf][0], 0, fbsize*4); + memset(Framebuffer[backbuf][1], 0, fbsize*4); + + FrontBuffer = backbuf; + AssignFramebuffers(); + + TotalScanlines = 263; +} + void StartScanline(u32 line) { if (line == 0) @@ -607,6 +607,7 @@ void SetPowerCnt(u32 val); void StartFrame(); void FinishFrame(u32 lines); +void BlankFrame(); void StartScanline(u32 line); void StartHBlank(u32 line); diff --git a/src/NDS.cpp b/src/NDS.cpp index ae45d3d..832661a 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -167,7 +167,7 @@ u32 SqrtVal[2]; u32 SqrtRes; u32 KeyInput; -u16 KeyCnt; +u16 KeyCnt[2]; u16 RCnt; bool Running; @@ -612,7 +612,8 @@ void Reset() SchedListMask = 0; KeyInput = 0x007F03FF; - KeyCnt = 0; + KeyCnt[0] = 0; + KeyCnt[1] = 0; RCnt = 0; NDSCart::Reset(); @@ -715,6 +716,7 @@ bool DoSavestate_Scheduler(Savestate* file) GPU::StartScanline, GPU::StartHBlank, GPU::FinishFrame, SPU::Mix, Wifi::USTimer, + RTC::ClockTimer, GPU::DisplayFIFO, NDSCart::ROMPrepareData, NDSCart::ROMEndTransfer, @@ -887,7 +889,7 @@ bool DoSavestate(Savestate* file) file->Bool32(&LagFrameFlag); // TODO: save KeyInput???? - file->Var16(&KeyCnt); + file->VarArray(KeyCnt, 2*sizeof(u16)); file->Var16(&RCnt); file->Var8(&WRAMCnt); @@ -1059,93 +1061,192 @@ void RunSystem(u64 timestamp) } } -template <bool EnableJIT, int ConsoleType> -u32 RunFrame() +u64 NextTargetSleep() { - FrameStartTimestamp = SysTimestamp; + u64 minEvent = UINT64_MAX; - LagFrameFlag = true; - bool runFrame = Running && !(CPUStop & 0x40000000); - if (runFrame) + u32 mask = SchedListMask; + for (int i = 0; i < Event_MAX; i++) { - ARM9->CheckGdbIncoming(); - ARM7->CheckGdbIncoming(); + if (!mask) break; + if (i == Event_SPU || i == Event_RTC) + { + if (mask & 0x1) + { + if (SchedList[i].Timestamp < minEvent) + minEvent = SchedList[i].Timestamp; + } + } - GPU::StartFrame(); + mask >>= 1; + } - while (Running && GPU::TotalScanlines==0) - { - u64 target = NextTarget(); - ARM9Target = target << ARM9ClockShift; - CurCPU = 0; + return minEvent; +} + +void RunSystemSleep(u64 timestamp) +{ + u64 offset = timestamp - SysTimestamp; + SysTimestamp = timestamp; - if (CPUStop & 0x80000000) + u32 mask = SchedListMask; + for (int i = 0; i < Event_MAX; i++) + { + if (!mask) break; + if (i == Event_SPU || i == Event_RTC) + { + if (mask & 0x1) { - // GXFIFO stall - s32 cycles = GPU3D::CyclesToRunFor(); + if (SchedList[i].Timestamp <= SysTimestamp) + { + SchedListMask &= ~(1<<i); - ARM9Timestamp = std::min(ARM9Target, ARM9Timestamp+(cycles<<ARM9ClockShift)); + u32 param; + if (i == Event_SPU) + param = 1; + else + param = SchedList[i].Param; + + SchedList[i].Func(param); + } } - else if (CPUStop & 0x0FFF) + } + else if (mask & 0x1) + { + if (SchedList[i].Timestamp <= SysTimestamp) { - DMAs[0]->Run<ConsoleType>(); - if (!(CPUStop & 0x80000000)) DMAs[1]->Run<ConsoleType>(); - if (!(CPUStop & 0x80000000)) DMAs[2]->Run<ConsoleType>(); - if (!(CPUStop & 0x80000000)) DMAs[3]->Run<ConsoleType>(); - if (ConsoleType == 1) DSi::RunNDMAs(0); + SchedList[i].Timestamp += offset; } - else + } + + mask >>= 1; + } +} + +template <bool EnableJIT, int ConsoleType> +u32 RunFrame() +{ + FrameStartTimestamp = SysTimestamp; + + GPU::TotalScanlines = 0; + + LagFrameFlag = true; + bool runFrame = Running && !(CPUStop & CPUStop_Sleep); + while (Running) + { + u64 frametarget = SysTimestamp + 560190; + + if (CPUStop & CPUStop_Sleep) + { + // we are running in sleep mode + // we still need to run the RTC during this mode + // we also keep outputting audio, so that frontends using audio sync don't skyrocket to 1000+FPS + + while (Running && (SysTimestamp < frametarget)) { -#ifdef JIT_ENABLED - if (EnableJIT) - ARM9->ExecuteJIT(); - else -#endif - ARM9->Execute(); + u64 target = NextTargetSleep(); + if (target > frametarget) + target = frametarget; + + ARM9Timestamp = target << ARM9ClockShift; + ARM7Timestamp = target; + TimerTimestamp[0] = target; + TimerTimestamp[1] = target; + GPU3D::Timestamp = target; + RunSystemSleep(target); + + if (!(CPUStop & CPUStop_Sleep)) + break; } - RunTimers(0); - GPU3D::Run(); + if (SysTimestamp >= frametarget) + GPU::BlankFrame(); + } + else + { + ARM9->CheckGdbIncoming(); + ARM7->CheckGdbIncoming(); - target = ARM9Timestamp >> ARM9ClockShift; - CurCPU = 1; + if (!(CPUStop & CPUStop_Wakeup)) + { + GPU::StartFrame(); + } + CPUStop &= ~CPUStop_Wakeup; - while (ARM7Timestamp < target) + while (Running && GPU::TotalScanlines==0) { - ARM7Target = target; // might be changed by a reschedule + u64 target = NextTarget(); + ARM9Target = target << ARM9ClockShift; + CurCPU = 0; - if (CPUStop & 0x0FFF0000) + if (CPUStop & CPUStop_GXStall) { - DMAs[4]->Run<ConsoleType>(); - DMAs[5]->Run<ConsoleType>(); - DMAs[6]->Run<ConsoleType>(); - DMAs[7]->Run<ConsoleType>(); - if (ConsoleType == 1) DSi::RunNDMAs(1); + // GXFIFO stall + s32 cycles = GPU3D::CyclesToRunFor(); + + ARM9Timestamp = std::min(ARM9Target, ARM9Timestamp+(cycles<<ARM9ClockShift)); + } + else if (CPUStop & CPUStop_DMA9) + { + DMAs[0]->Run<ConsoleType>(); + if (!(CPUStop & CPUStop_GXStall)) DMAs[1]->Run<ConsoleType>(); + if (!(CPUStop & CPUStop_GXStall)) DMAs[2]->Run<ConsoleType>(); + if (!(CPUStop & CPUStop_GXStall)) DMAs[3]->Run<ConsoleType>(); + if (ConsoleType == 1) DSi::RunNDMAs(0); } else { #ifdef JIT_ENABLED if (EnableJIT) - ARM7->ExecuteJIT(); + ARM9->ExecuteJIT(); else #endif - ARM7->Execute(); + ARM9->Execute(); } - RunTimers(1); - } + RunTimers(0); + GPU3D::Run(); - RunSystem(target); + target = ARM9Timestamp >> ARM9ClockShift; + CurCPU = 1; - if (CPUStop & 0x40000000) - { - // checkme: when is sleep mode effective? - CancelEvent(Event_LCD); - GPU::TotalScanlines = 263; - break; + while (ARM7Timestamp < target) + { + ARM7Target = target; // might be changed by a reschedule + + if (CPUStop & CPUStop_DMA7) + { + DMAs[4]->Run<ConsoleType>(); + DMAs[5]->Run<ConsoleType>(); + DMAs[6]->Run<ConsoleType>(); + DMAs[7]->Run<ConsoleType>(); + if (ConsoleType == 1) DSi::RunNDMAs(1); + } + else + { +#ifdef JIT_ENABLED + if (EnableJIT) + ARM7->ExecuteJIT(); + else +#endif + ARM7->Execute(); + } + + RunTimers(1); + } + + RunSystem(target); + + if (CPUStop & CPUStop_Sleep) + { + break; + } } } + if (GPU::TotalScanlines == 0) + continue; + #ifdef DEBUG_CHECK_DESYNC Log(LogLevel::Debug, "[%08X%08X] ARM9=%ld, ARM7=%ld, GPU=%ld\n", (u32)(SysTimestamp>>32), (u32)SysTimestamp, @@ -1154,6 +1255,7 @@ u32 RunFrame() GPU3D::Timestamp-SysTimestamp); #endif SPU::TransferOutput(); + break; } // In the context of TASes, frame count is traditionally the primary measure of emulated time, @@ -1162,7 +1264,7 @@ u32 RunFrame() if (LagFrameFlag) NumLagFrames++; - if (runFrame) + if (Running) return GPU::TotalScanlines; else return 263; @@ -1276,13 +1378,47 @@ void ReleaseScreen() } +void CheckKeyIRQ(u32 cpu, u32 oldkey, u32 newkey) +{ + u16 cnt = KeyCnt[cpu]; + if (!(cnt & (1<<14))) // IRQ disabled + return; + + u32 mask = (cnt & 0x03FF); + oldkey &= mask; + newkey &= mask; + + bool oldmatch, newmatch; + if (cnt & (1<<15)) + { + // logical AND + + oldmatch = (oldkey == 0); + newmatch = (newkey == 0); + } + else + { + // logical OR + + oldmatch = (oldkey != mask); + newmatch = (newkey != mask); + } + + if ((!oldmatch) && newmatch) + SetIRQ(cpu, IRQ_Keypad); +} + void SetKeyMask(u32 mask) { u32 key_lo = mask & 0x3FF; u32 key_hi = (mask >> 10) & 0x3; + u32 oldkey = KeyInput; KeyInput &= 0xFFFCFC00; KeyInput |= key_lo | (key_hi << 16); + + CheckKeyIRQ(0, oldkey, KeyInput); + CheckKeyIRQ(1, oldkey, KeyInput); } bool IsLidClosed() @@ -1301,8 +1437,6 @@ void SetLidClosed(bool closed) { KeyInput &= ~(1<<23); SetIRQ(1, IRQ_LidOpen); - CPUStop &= ~0x40000000; - GPU3D::RestartFrame(); } } @@ -1467,6 +1601,16 @@ void SetIRQ(u32 cpu, u32 irq) { IF[cpu] |= (1 << irq); UpdateIRQ(cpu); + + if ((cpu == 1) && (CPUStop & CPUStop_Sleep)) + { + if (IE[1] & (1 << irq)) + { + CPUStop &= ~CPUStop_Sleep; + CPUStop |= CPUStop_Wakeup; + GPU3D::RestartFrame(); + } + } } void ClearIRQ(u32 cpu, u32 irq) @@ -1526,9 +1670,9 @@ void ResumeCPU(u32 cpu, u32 mask) void GXFIFOStall() { - if (CPUStop & 0x80000000) return; + if (CPUStop & CPUStop_GXStall) return; - CPUStop |= 0x80000000; + CPUStop |= CPUStop_GXStall; if (CurCPU == 1) ARM9->Halt(2); else @@ -1543,14 +1687,14 @@ void GXFIFOStall() void GXFIFOUnstall() { - CPUStop &= ~0x80000000; + CPUStop &= ~CPUStop_GXStall; } void EnterSleepMode() { - if (CPUStop & 0x40000000) return; + if (CPUStop & CPUStop_Sleep) return; - CPUStop |= 0x40000000; + CPUStop |= CPUStop_Sleep; ARM7->Halt(2); } @@ -2017,7 +2161,7 @@ void debug(u32 param) // printf("VRAM %c: %02X\n", 'A'+i, GPU::VRAMCNT[i]); FILE* - shit = fopen("debug/crayon.bin", "wb"); + shit = fopen("debug/DSfirmware.bin", "wb"); fwrite(ARM9->ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -2942,8 +3086,8 @@ u8 ARM9IORead8(u32 addr) { case 0x04000130: LagFrameFlag = false; return KeyInput & 0xFF; case 0x04000131: LagFrameFlag = false; return (KeyInput >> 8) & 0xFF; - case 0x04000132: return KeyCnt & 0xFF; - case 0x04000133: return KeyCnt >> 8; + case 0x04000132: return KeyCnt[0] & 0xFF; + case 0x04000133: return KeyCnt[0] >> 8; case 0x040001A2: if (!(ExMemCnt[0] & (1<<11))) @@ -3079,7 +3223,7 @@ u16 ARM9IORead16(u32 addr) case 0x0400010E: return Timers[3].Cnt; case 0x04000130: LagFrameFlag = false; return KeyInput & 0xFFFF; - case 0x04000132: return KeyCnt; + case 0x04000132: return KeyCnt[0]; case 0x04000180: return IPCSync9; case 0x04000184: @@ -3221,7 +3365,7 @@ u32 ARM9IORead32(u32 addr) case 0x04000108: return TimerGetCounter(2) | (Timers[2].Cnt << 16); case 0x0400010C: return TimerGetCounter(3) | (Timers[3].Cnt << 16); - case 0x04000130: LagFrameFlag = false; return (KeyInput & 0xFFFF) | (KeyCnt << 16); + case 0x04000130: LagFrameFlag = false; return (KeyInput & 0xFFFF) | (KeyCnt[0] << 16); case 0x04000180: return IPCSync9; case 0x04000184: return ARM9IORead16(addr); @@ -3341,10 +3485,10 @@ void ARM9IOWrite8(u32 addr, u8 val) case 0x0400106D: GPU::GPU2D_B.Write8(addr, val); return; case 0x04000132: - KeyCnt = (KeyCnt & 0xFF00) | val; + KeyCnt[0] = (KeyCnt[0] & 0xFF00) | val; return; case 0x04000133: - KeyCnt = (KeyCnt & 0x00FF) | (val << 8); + KeyCnt[0] = (KeyCnt[0] & 0x00FF) | (val << 8); return; case 0x04000188: @@ -3454,7 +3598,7 @@ void ARM9IOWrite16(u32 addr, u16 val) case 0x0400010E: TimerStart(3, val); return; case 0x04000132: - KeyCnt = val; + KeyCnt[0] = val; return; case 0x04000180: @@ -3647,7 +3791,7 @@ void ARM9IOWrite32(u32 addr, u32 val) return; case 0x04000130: - KeyCnt = val >> 16; + KeyCnt[0] = val >> 16; return; case 0x04000180: @@ -3800,8 +3944,8 @@ u8 ARM7IORead8(u32 addr) { case 0x04000130: return KeyInput & 0xFF; case 0x04000131: return (KeyInput >> 8) & 0xFF; - case 0x04000132: return KeyCnt & 0xFF; - case 0x04000133: return KeyCnt >> 8; + case 0x04000132: return KeyCnt[1] & 0xFF; + case 0x04000133: return KeyCnt[1] >> 8; case 0x04000134: return RCnt & 0xFF; case 0x04000135: return RCnt >> 8; case 0x04000136: return (KeyInput >> 16) & 0xFF; @@ -3894,7 +4038,7 @@ u16 ARM7IORead16(u32 addr) case 0x0400010E: return Timers[7].Cnt; case 0x04000130: return KeyInput & 0xFFFF; - case 0x04000132: return KeyCnt; + case 0x04000132: return KeyCnt[1]; case 0x04000134: return RCnt; case 0x04000136: return KeyInput >> 16; @@ -3986,8 +4130,8 @@ u32 ARM7IORead32(u32 addr) case 0x04000108: return TimerGetCounter(6) | (Timers[6].Cnt << 16); case 0x0400010C: return TimerGetCounter(7) | (Timers[7].Cnt << 16); - case 0x04000130: return (KeyInput & 0xFFFF) | (KeyCnt << 16); - case 0x04000134: return RCnt | (KeyCnt & 0xFFFF0000); + case 0x04000130: return (KeyInput & 0xFFFF) | (KeyCnt[1] << 16); + case 0x04000134: return RCnt | (KeyInput & 0xFFFF0000); case 0x04000138: return RTC::Read(); case 0x04000180: return IPCSync7; @@ -4068,10 +4212,10 @@ void ARM7IOWrite8(u32 addr, u8 val) switch (addr) { case 0x04000132: - KeyCnt = (KeyCnt & 0xFF00) | val; + KeyCnt[1] = (KeyCnt[1] & 0xFF00) | val; return; case 0x04000133: - KeyCnt = (KeyCnt & 0x00FF) | (val << 8); + KeyCnt[1] = (KeyCnt[1] & 0x00FF) | (val << 8); return; case 0x04000134: RCnt = (RCnt & 0xFF00) | val; @@ -4165,7 +4309,7 @@ void ARM7IOWrite16(u32 addr, u16 val) case 0x0400010C: Timers[7].Reload = val; return; case 0x0400010E: TimerStart(7, val); return; - case 0x04000132: KeyCnt = val; return; + case 0x04000132: KeyCnt[1] = val; return; case 0x04000134: RCnt = val; return; case 0x04000138: RTC::Write(val, false); return; @@ -4334,7 +4478,7 @@ void ARM7IOWrite32(u32 addr, u32 val) TimerStart(7, val>>16); return; - case 0x04000130: KeyCnt = val >> 16; return; + case 0x04000130: KeyCnt[1] = val >> 16; return; case 0x04000134: RCnt = val & 0xFFFF; return; case 0x04000138: RTC::Write(val & 0xFFFF, false); return; @@ -37,6 +37,7 @@ enum Event_LCD = 0, Event_SPU, Event_Wifi, + Event_RTC, Event_DisplayFIFO, Event_ROMTransfer, @@ -122,6 +123,33 @@ enum IRQ2_DSi_MicExt }; +enum +{ + CPUStop_DMA9_0 = (1<<0), + CPUStop_DMA9_1 = (1<<1), + CPUStop_DMA9_2 = (1<<2), + CPUStop_DMA9_3 = (1<<3), + CPUStop_NDMA9_0 = (1<<4), + CPUStop_NDMA9_1 = (1<<5), + CPUStop_NDMA9_2 = (1<<6), + CPUStop_NDMA9_3 = (1<<7), + CPUStop_DMA9 = 0xFFF, + + CPUStop_DMA7_0 = (1<<16), + CPUStop_DMA7_1 = (1<<17), + CPUStop_DMA7_2 = (1<<18), + CPUStop_DMA7_3 = (1<<19), + CPUStop_NDMA7_0 = (1<<20), + CPUStop_NDMA7_1 = (1<<21), + CPUStop_NDMA7_2 = (1<<22), + CPUStop_NDMA7_3 = (1<<23), + CPUStop_DMA7 = (0xFFF<<16), + + CPUStop_Wakeup = (1<<29), + CPUStop_Sleep = (1<<30), + CPUStop_GXStall = (1<<31), +}; + struct Timer { u16 Reload; @@ -219,6 +247,7 @@ extern MemRegion SWRAM_ARM9; extern MemRegion SWRAM_ARM7; extern u32 KeyInput; +extern u16 RCnt; const u32 ARM7WRAMSize = 0x10000; extern u8* ARM7WRAM; diff --git a/src/Platform.h b/src/Platform.h index a379d85..144fce1 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -337,6 +337,9 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen /// @param writelen The number of bytes that were written to firmware. void WriteFirmware(const SPI_Firmware::Firmware& firmware, u32 writeoffset, u32 writelen); +// called when the RTC date/time is changed and the frontend might need to take it into account +void WriteDateTime(int year, int month, int day, int hour, int minute, int second); + // local multiplayer comm interface // packet type: DS-style TX header (12 bytes) + original 802.11 frame diff --git a/src/RTC.cpp b/src/RTC.cpp index 94d7ae7..021cde7 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -20,7 +20,7 @@ #define _POSIX_THREAD_SAFE_FUNCTIONS #include <string.h> -#include <time.h> +#include "NDS.h" #include "RTC.h" #include "Platform.h" @@ -45,16 +45,23 @@ u32 OutputPos; u8 CurCmd; -u8 StatusReg1; -u8 StatusReg2; -u8 Alarm1[3]; -u8 Alarm2[3]; -u8 ClockAdjust; -u8 FreeReg; +StateData State; + +s32 TimerError; +u32 ClockCount; + + +void WriteDateTime(int num, u8 val); bool Init() { + ResetState(); + + // indicate the power was off + // this will be changed if a previously saved RTC state is loaded + State.StatusReg1 = 0x80; + return true; } @@ -73,12 +80,8 @@ void Reset() CurCmd = 0; - StatusReg1 = 0; - StatusReg2 = 0; - memset(Alarm1, 0, sizeof(Alarm1)); - memset(Alarm2, 0, sizeof(Alarm2)); - ClockAdjust = 0; - FreeReg = 0; + ClockCount = 0; + ScheduleTimer(true); } void DoSavestate(Savestate* file) @@ -97,12 +100,10 @@ void DoSavestate(Savestate* file) file->Var8(&CurCmd); - file->Var8(&StatusReg1); - file->Var8(&StatusReg2); - file->VarArray(Alarm1, sizeof(Alarm1)); - file->VarArray(Alarm2, sizeof(Alarm2)); - file->Var8(&ClockAdjust); - file->Var8(&FreeReg); + file->VarArray(&State, sizeof(State)); + + file->Var32((u32*)&TimerError); + file->Var32(&ClockCount); } @@ -111,120 +112,772 @@ u8 BCD(u8 val) return (val % 10) | ((val / 10) << 4); } +u8 BCDIncrement(u8 val) +{ + val++; + if ((val & 0x0F) >= 0x0A) + val += 0x06; + if ((val & 0xF0) >= 0xA0) + val += 0x60; + return val; +} -void ByteIn(u8 val) +u8 BCDSanitize(u8 val, u8 vmin, u8 vmax) { - if (InputPos == 0) + if (val < vmin || val > vmax) + val = vmin; + else if ((val & 0x0F) >= 0x0A) + val = vmin; + else if ((val & 0xF0) >= 0xA0) + val = vmin; + + return val; +} + + +void GetState(StateData& state) +{ + memcpy(&state, &State, sizeof(State)); +} + +void SetState(StateData& state) +{ + memcpy(&State, &state, sizeof(State)); + + // sanitize the input state + + for (int i = 0; i < 7; i++) + WriteDateTime(i+1, State.DateTime[i]); +} + +void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) +{ + int val; + + val = State.DateTime[0]; + year = (val & 0xF) + ((val >> 4) * 10); + year += 2000; + + val = State.DateTime[1] & 0x3F; + month = (val & 0xF) + ((val >> 4) * 10); + + val = State.DateTime[2] & 0x3F; + day = (val & 0xF) + ((val >> 4) * 10); + + val = State.DateTime[4] & 0x3F; + hour = (val & 0xF) + ((val >> 4) * 10); + + if (!(State.StatusReg1 & (1<<1))) { - if ((val & 0xF0) == 0x60) + // 12-hour mode + + if (State.DateTime[4] & 0x40) + hour += 12; + } + + val = State.DateTime[5] & 0x7F; + minute = (val & 0xF) + ((val >> 4) * 10); + + val = State.DateTime[6] & 0x7F; + second = (val & 0xF) + ((val >> 4) * 10); +} + +void SetDateTime(int year, int month, int day, int hour, int minute, int second) +{ + int monthdays[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + // the year range of the DS RTC is limited to 2000-2099 + year %= 100; + if (year < 0) year = 0; + + if (!(year & 3)) monthdays[2] = 29; + + if (month < 1 || month > 12) month = 1; + if (day < 1 || day > monthdays[month]) day = 1; + if (hour < 0 || hour > 23) hour = 0; + if (minute < 0 || minute > 59) minute = 0; + if (second < 0 || second > 59) second = 0; + + // note on day-of-week value + // that RTC register is a simple incrementing counter and the assignation is defined by software + // DS/DSi firmware counts from 0=Sunday + + int numdays = (year * 365) + ((year+3) / 4); // account for leap years + + for (int m = 1; m < month; m++) + { + numdays += monthdays[m]; + } + numdays += (day-1); + + // 01/01/2000 is a Saturday, so the starting value is 6 + int dayofweek = (6 + numdays) % 7; + + int pm = (hour >= 12) ? 0x40 : 0; + if (!(State.StatusReg1 & (1<<1))) + { + // 12-hour mode + + if (pm) hour -= 12; + } + + State.DateTime[0] = BCD(year); + State.DateTime[1] = BCD(month); + State.DateTime[2] = BCD(day); + State.DateTime[3] = dayofweek; + State.DateTime[4] = BCD(hour) | pm; + State.DateTime[5] = BCD(minute); + State.DateTime[6] = BCD(second); + + State.StatusReg1 &= ~0x80; +} + +void ResetState() +{ + memset(&State, 0, sizeof(State)); + State.DateTime[1] = 1; + State.DateTime[2] = 1; +} + + +void SetIRQ(u8 irq) +{ + u8 oldstat = State.IRQFlag; + State.IRQFlag |= irq; + State.StatusReg1 |= irq; + + if ((!(oldstat & 0x30)) && (State.IRQFlag & 0x30)) + { + if ((NDS::RCnt & 0xC100) == 0x8100) { - u8 rev[16] = {0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6}; - CurCmd = rev[val & 0xF]; + // CHECKME: is the IRQ status readable in RCNT? + NDS::SetIRQ(1, NDS::IRQ_RTC); } - else - CurCmd = val; + } +} - if (CurCmd & 0x80) +void ClearIRQ(u8 irq) +{ + State.IRQFlag &= ~irq; +} + +void ProcessIRQ(int type) // 0=minute carry 1=periodic 2=status reg write +{ + // INT1 + + switch (State.StatusReg2 & 0x0F) + { + case 0b0000: // none + if (type == 2) { - switch (CurCmd & 0x70) - { - case 0x00: Output[0] = StatusReg1; break; - case 0x40: Output[0] = StatusReg2; break; + ClearIRQ(0x10); + } + break; - case 0x20: - { - time_t timestamp = time(NULL); - struct tm timedata; - localtime_r(×tamp, &timedata); - - Output[0] = BCD(timedata.tm_year - 100); - Output[1] = BCD(timedata.tm_mon + 1); - Output[2] = BCD(timedata.tm_mday); - Output[3] = BCD(timedata.tm_wday); - Output[4] = BCD(timedata.tm_hour); - Output[5] = BCD(timedata.tm_min); - Output[6] = BCD(timedata.tm_sec); - } - break; + case 0b0001: + case 0b0101: // selected frequency steady interrupt + if ((type == 1 && (!(ClockCount & 0x3FF))) || (type == 2)) + { + u32 mask = 0; + if (State.Alarm1[2] & (1<<0)) mask |= 0x4000; + if (State.Alarm1[2] & (1<<1)) mask |= 0x2000; + if (State.Alarm1[2] & (1<<2)) mask |= 0x1000; + if (State.Alarm1[2] & (1<<3)) mask |= 0x0800; + if (State.Alarm1[2] & (1<<4)) mask |= 0x0400; + + if (mask && ((ClockCount & mask) != mask)) + SetIRQ(0x10); + else + ClearIRQ(0x10); + } + break; - case 0x60: - { - time_t timestamp = time(NULL); - struct tm timedata; - localtime_r(×tamp, &timedata); + case 0b0010: + case 0b0110: // per-minute edge interrupt + if ((type == 0) || (type == 2 && (State.IRQFlag & 0x01))) + { + SetIRQ(0x10); + } + break; - Output[0] = BCD(timedata.tm_hour); - Output[1] = BCD(timedata.tm_min); - Output[2] = BCD(timedata.tm_sec); - } - break; + case 0b0011: // per-minute steady interrupt 1 (duty 30s) + if ((type == 0) || (type == 2 && (State.IRQFlag & 0x01))) + { + SetIRQ(0x10); + } + else if ((type == 1) && (State.DateTime[6] == 0x30)) + { + ClearIRQ(0x10); + } + break; - case 0x10: - if (StatusReg2 & 0x04) - { - Output[0] = Alarm1[0]; - Output[1] = Alarm1[1]; - Output[2] = Alarm1[2]; - } - else - Output[0] = Alarm1[2]; - break; + case 0b0111: // per-minute steady interrupt 2 (duty 256 cycles) + if ((type == 0) || (type == 2 && (State.IRQFlag & 0x01))) + { + SetIRQ(0x10); + } + else if ((type == 1) && (State.DateTime[6] == 0x00) && ((ClockCount & 0x7FFF) == 256)) + { + ClearIRQ(0x10); + } + break; - case 0x50: - Output[0] = Alarm2[0]; - Output[1] = Alarm2[1]; - Output[2] = Alarm2[2]; - break; + case 0b0100: // alarm interrupt + if (type == 0) + { + bool cond = true; + if (State.Alarm1[0] & (1<<7)) + cond = cond && ((State.Alarm1[0] & 0x07) == State.DateTime[3]); + if (State.Alarm1[1] & (1<<7)) + cond = cond && ((State.Alarm1[1] & 0x7F) == State.DateTime[4]); + if (State.Alarm1[2] & (1<<7)) + cond = cond && ((State.Alarm1[2] & 0x7F) == State.DateTime[5]); + + if (cond) + SetIRQ(0x10); + else + ClearIRQ(0x10); + } + break; - case 0x30: Output[0] = ClockAdjust; break; - case 0x70: Output[0] = FreeReg; break; - } + default: // 32KHz output + if (type == 1) + { + SetIRQ(0x10); + ClearIRQ(0x10); } - return; + break; } - switch (CurCmd & 0x70) + // INT2 + + if (State.StatusReg2 & (1<<6)) { - case 0x00: - if (InputPos == 1) StatusReg1 = val & 0x0E; - break; + // alarm interrupt + + if (type == 0) + { + bool cond = true; + if (State.Alarm2[0] & (1<<7)) + cond = cond && ((State.Alarm2[0] & 0x07) == State.DateTime[3]); + if (State.Alarm2[1] & (1<<7)) + cond = cond && ((State.Alarm2[1] & 0x7F) == State.DateTime[4]); + if (State.Alarm2[2] & (1<<7)) + cond = cond && ((State.Alarm2[2] & 0x7F) == State.DateTime[5]); + + if (cond) + SetIRQ(0x20); + else + ClearIRQ(0x20); + } + } + else + { + if (type == 2) + { + ClearIRQ(0x20); + } + } +} - case 0x40: - if (InputPos == 1) StatusReg2 = val; - if (StatusReg2 & 0x4F) Log(LogLevel::Debug, "RTC INTERRUPT ON: %02X\n", StatusReg2); + +u8 DaysInMonth() +{ + u8 numdays; + + switch (State.DateTime[1]) + { + case 0x01: // Jan + case 0x03: // Mar + case 0x05: // May + case 0x07: // Jul + case 0x08: // Aug + case 0x10: // Oct + case 0x12: // Dec + numdays = 0x31; break; - case 0x20: - // TODO: set time somehow?? + case 0x04: // Apr + case 0x06: // Jun + case 0x09: // Sep + case 0x11: // Nov + numdays = 0x30; break; - case 0x60: - // same shit + case 0x02: // Feb + { + numdays = 0x28; + + // leap year: if year divisible by 4 and not divisible by 100 unless divisible by 400 + // the limited year range (2000-2099) simplifies this + int year = State.DateTime[0]; + year = (year & 0xF) + ((year >> 4) * 10); + if (!(year & 3)) + numdays = 0x29; + } break; - case 0x10: - if (StatusReg2 & 0x04) + default: // ??? + return 0; + } + + return numdays; +} + +void CountYear() +{ + State.DateTime[0] = BCDIncrement(State.DateTime[0]); +} + +void CountMonth() +{ + State.DateTime[1] = BCDIncrement(State.DateTime[1]); + if (State.DateTime[1] > 0x12) + { + State.DateTime[1] = 1; + CountYear(); + } +} + +void CheckEndOfMonth() +{ + if (State.DateTime[2] > DaysInMonth()) + { + State.DateTime[2] = 1; + CountMonth(); + } +} + +void CountDay() +{ + // day-of-week counter + State.DateTime[3]++; + if (State.DateTime[3] >= 7) + State.DateTime[3] = 0; + + // day counter + State.DateTime[2] = BCDIncrement(State.DateTime[2]); + CheckEndOfMonth(); +} + +void CountHour() +{ + u8 hour = BCDIncrement(State.DateTime[4] & 0x3F); + u8 pm = State.DateTime[4] & 0x40; + + if (State.StatusReg1 & (1<<1)) + { + // 24-hour mode + + if (hour >= 0x24) { - if (InputPos <= 3) Alarm1[InputPos-1] = val; + hour = 0; + CountDay(); } - else + + pm = (hour >= 0x12) ? 0x40 : 0; + } + else + { + // 12-hour mode + + if (hour >= 0x12) { - if (InputPos == 1) Alarm1[2] = val; + hour = 0; + if (pm) CountDay(); + pm ^= 0x40; } + } + + State.DateTime[4] = hour | pm; +} + +void CountMinute() +{ + State.MinuteCount++; + State.DateTime[5] = BCDIncrement(State.DateTime[5]); + if (State.DateTime[5] >= 0x60) + { + State.DateTime[5] = 0; + CountHour(); + } + + State.IRQFlag |= 0x01; // store minute carry flag + ProcessIRQ(0); +} + +void CountSecond() +{ + State.DateTime[6] = BCDIncrement(State.DateTime[6]); + if (State.DateTime[6] >= 0x60) + { + State.DateTime[6] = 0; + CountMinute(); + } +} + + +void ScheduleTimer(bool first) +{ + if (first) TimerError = 0; + + // the RTC clock runs at 32768Hz + // cycles = 33513982 / 32768 + s32 sysclock = 33513982 + TimerError; + s32 delay = sysclock >> 15; + TimerError = sysclock & 0x7FFF; + + NDS::ScheduleEvent(NDS::Event_RTC, !first, delay, ClockTimer, 0); +} + +void ClockTimer(u32 param) +{ + ClockCount++; + + if (!(ClockCount & 0x7FFF)) + { + // count up one second + CountSecond(); + } + else if ((ClockCount & 0x7FFF) == 4) + { + // minute-carry flag lasts 4 cycles + State.IRQFlag &= ~0x01; + } + + ProcessIRQ(1); + + ScheduleTimer(false); +} + + +void WriteDateTime(int num, u8 val) +{ + switch (num) + { + case 1: // year + State.DateTime[0] = BCDSanitize(val, 0x00, 0x99); break; - case 0x50: - if (InputPos <= 3) Alarm2[InputPos-1] = val; + case 2: // month + State.DateTime[1] = BCDSanitize(val & 0x1F, 0x01, 0x12); break; - case 0x30: - if (InputPos == 1) ClockAdjust = val; + case 3: // day + State.DateTime[2] = BCDSanitize(val & 0x3F, 0x01, 0x31); + CheckEndOfMonth(); break; - case 0x70: - if (InputPos == 1) FreeReg = val; + case 4: // day of week + State.DateTime[3] = BCDSanitize(val & 0x07, 0x00, 0x06); break; + + case 5: // hour + { + u8 hour = val & 0x3F; + u8 pm = val & 0x40; + + if (State.StatusReg1 & (1<<1)) + { + // 24-hour mode + + hour = BCDSanitize(hour, 0x00, 0x23); + pm = (hour >= 0x12) ? 0x40 : 0; + } + else + { + // 12-hour mode + + hour = BCDSanitize(hour, 0x00, 0x11); + } + + State.DateTime[4] = hour | pm; + } + break; + + case 6: // minute + State.DateTime[5] = BCDSanitize(val & 0x7F, 0x00, 0x59); + break; + + case 7: // second + State.DateTime[6] = BCDSanitize(val & 0x7F, 0x00, 0x59); + break; + } +} + +void SaveDateTime() +{ + int y, m, d, h, i, s; + GetDateTime(y, m, d, h, i, s); + Platform::WriteDateTime(y, m, d, h, i, s); +} + +void CmdRead() +{ + if ((CurCmd & 0x0F) == 0x06) + { + switch (CurCmd & 0x70) + { + case 0x00: + Output[0] = State.StatusReg1; + State.StatusReg1 &= 0x0F; // clear auto-clearing bit4-7 + break; + + case 0x40: + Output[0] = State.StatusReg2; + break; + + case 0x20: + memcpy(Output, &State.DateTime[0], 7); + break; + + case 0x60: + memcpy(Output, &State.DateTime[4], 3); + break; + + case 0x10: + if (State.StatusReg2 & 0x04) + memcpy(Output, &State.Alarm1[0], 3); + else + Output[0] = State.Alarm1[2]; + break; + + case 0x50: + memcpy(Output, &State.Alarm2[0], 3); + break; + + case 0x30: Output[0] = State.ClockAdjust; break; + case 0x70: Output[0] = State.FreeReg; break; + } + + return; + } + else if ((CurCmd & 0x0F) == 0x0E) + { + if (NDS::ConsoleType != 1) + { + Log(LogLevel::Debug, "RTC: unknown read command %02X\n", CurCmd); + return; + } + + switch (CurCmd & 0x70) + { + case 0x00: + Output[0] = (State.MinuteCount >> 16) & 0xFF; + Output[1] = (State.MinuteCount >> 8) & 0xFF; + Output[2] = State.MinuteCount & 0xFF; + break; + + case 0x40: Output[0] = State.FOUT1; break; + case 0x20: Output[0] = State.FOUT2; break; + + case 0x10: + memcpy(Output, &State.AlarmDate1[0], 3); + break; + + case 0x50: + memcpy(Output, &State.AlarmDate2[0], 3); + break; + + default: + Log(LogLevel::Debug, "RTC: unknown read command %02X\n", CurCmd); + break; + } + + return; + } + + Log(LogLevel::Debug, "RTC: unknown read command %02X\n", CurCmd); +} + +void CmdWrite(u8 val) +{ + if ((CurCmd & 0x0F) == 0x06) + { + switch (CurCmd & 0x70) + { + case 0x00: + if (InputPos == 1) + { + u8 oldval = State.StatusReg1; + + if (val & (1<<0)) // reset + ResetState(); + + State.StatusReg1 = (State.StatusReg1 & 0xF0) | (val & 0x0E); + + if ((State.StatusReg1 ^ oldval) & (1<<1)) + { + // AM/PM changed + + u8 hour = State.DateTime[4] & 0x3F; + u8 pm = State.DateTime[4] & 0x40; + + if (State.StatusReg1 & (1<<1)) + { + // 24-hour mode + + if (pm) + { + hour += 0x12; + if ((hour & 0x0F) >= 0x0A) + hour += 0x06; + } + + hour = BCDSanitize(hour, 0x00, 0x23); + } + else + { + // 12-hour mode + + if (hour >= 0x12) + { + pm = 0x40; + + hour -= 0x12; + if ((hour & 0x0F) >= 0x0A) + hour -= 0x06; + } + else + pm = 0; + + hour = BCDSanitize(hour, 0x00, 0x11); + } + + State.DateTime[4] = hour | pm; + } + } + break; + + case 0x40: + if (InputPos == 1) + { + State.StatusReg2 = val; + ProcessIRQ(2); + } + break; + + case 0x20: + if (InputPos <= 7) + WriteDateTime(InputPos, val); + if (InputPos == 7) + SaveDateTime(); + break; + + case 0x60: + if (InputPos <= 3) + WriteDateTime(InputPos+4, val); + if (InputPos == 3) + SaveDateTime(); + break; + + case 0x10: + if (State.StatusReg2 & 0x04) + { + if (InputPos <= 3) + State.Alarm1[InputPos-1] = val; + } + else + { + if (InputPos == 1) + State.Alarm1[2] = val; + } + break; + + case 0x50: + if (InputPos <= 3) + State.Alarm2[InputPos-1] = val; + break; + + case 0x30: + if (InputPos == 1) + State.ClockAdjust = val; + break; + + case 0x70: + if (InputPos == 1) + State.FreeReg = val; + break; + } + + return; + } + else if ((CurCmd & 0x0F) == 0x0E) + { + if (NDS::ConsoleType != 1) + { + Log(LogLevel::Debug, "RTC: unknown write command %02X\n", CurCmd); + return; + } + + switch (CurCmd & 0x70) + { + case 0x00: + Log(LogLevel::Debug, "RTC: trying to write read-only minute counter\n"); + break; + + case 0x40: + if (InputPos == 1) + State.FOUT1 = val; + break; + + case 0x20: + if (InputPos == 1) + State.FOUT2 = val; + break; + + case 0x10: + if (InputPos <= 3) + State.AlarmDate1[InputPos-1] = val; + break; + + case 0x50: + if (InputPos <= 3) + State.AlarmDate2[InputPos-1] = val; + break; + + default: + Log(LogLevel::Debug, "RTC: unknown write command %02X\n", CurCmd); + break; + } + + return; } + + Log(LogLevel::Debug, "RTC: unknown write command %02X\n", CurCmd); +} + +void ByteIn(u8 val) +{ + if (InputPos == 0) + { + if ((val & 0xF0) == 0x60) + { + u8 rev[16] = {0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6}; + CurCmd = rev[val & 0xF]; + } + else + CurCmd = val; + + if (NDS::ConsoleType == 1) + { + // for DSi: handle extra commands + + if (((CurCmd & 0xF0) == 0x70) && ((CurCmd & 0xFE) != 0x76)) + { + u8 rev[16] = {0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE}; + CurCmd = rev[CurCmd & 0xF]; + } + } + + if (CurCmd & 0x80) + { + CmdRead(); + } + return; + } + + CmdWrite(val); } @@ -25,11 +25,40 @@ namespace RTC { +struct StateData +{ + u8 StatusReg1; + u8 StatusReg2; + u8 DateTime[7]; + u8 Alarm1[3]; + u8 Alarm2[3]; + u8 ClockAdjust; + u8 FreeReg; + + u8 IRQFlag; + + // DSi registers + u32 MinuteCount; + u8 FOUT1; + u8 FOUT2; + u8 AlarmDate1[3]; + u8 AlarmDate2[3]; +}; + bool Init(); void DeInit(); void Reset(); void DoSavestate(Savestate* file); +void GetState(StateData& state); +void SetState(StateData& state); +void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second); +void SetDateTime(int year, int month, int day, int hour, int minute, int second); +void ResetState(); + +void ScheduleTimer(bool first); +void ClockTimer(u32 param); + u16 Read(); void Write(u16 val, bool byte); diff --git a/src/SPI_Firmware.h b/src/SPI_Firmware.h index 14771b2..8557604 100644 --- a/src/SPI_Firmware.h +++ b/src/SPI_Firmware.h @@ -396,7 +396,7 @@ union UserData u8 TouchCalibrationPixel2[2]; u16 Settings; u8 Year; - u8 Unknown1; + u8 RTCClockAdjust; u32 RTCOffset; u8 Unused2[4]; u16 UpdateCounter; diff --git a/src/SPU.cpp b/src/SPU.cpp index 3939aef..8148bac 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -726,7 +726,7 @@ void Mix(u32 dummy) s32 left = 0, right = 0; s32 leftoutput = 0, rightoutput = 0; - if (Cnt & (1<<15)) + if ((Cnt & (1<<15)) && (!dummy)) { s32 ch0 = Channels[0]->DoRun(); s32 ch1 = Channels[1]->DoRun(); diff --git a/src/Savestate.h b/src/Savestate.h index 235d1fb..6707f9f 100644 --- a/src/Savestate.h +++ b/src/Savestate.h @@ -24,7 +24,7 @@ #include <stdio.h> #include "types.h" -#define SAVESTATE_MAJOR 10 +#define SAVESTATE_MAJOR 11 #define SAVESTATE_MINOR 1 class Savestate diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 3923f37..cdc96dd 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES_QT_SDL main_shaders.h CheatsDialog.cpp Config.cpp + DateTimeDialog.cpp EmuSettingsDialog.cpp PowerManagement/PowerManagementDialog.cpp PowerManagement/resources/battery.qrc diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index da08c28..c28f63a 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -19,6 +19,7 @@ #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <inttypes.h> #include "Platform.h" #include "Config.h" @@ -140,6 +141,8 @@ int MouseHideSeconds; bool PauseLostFocus; +int64_t RTCOffset; + bool DSBatteryLevelOkay; int DSiBatteryLevel; bool DSiBatteryCharging; @@ -339,6 +342,8 @@ ConfigEntry ConfigFile[] = {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, + {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, @@ -406,6 +411,7 @@ void LoadFile(int inst) case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break; case 2: *(std::string*)entry->Value = entryval; break; + case 3: *(int64_t*)entry->Value = strtoll(entryval, NULL, 10); break; } break; @@ -426,6 +432,7 @@ void Load() case 0: *(int*)entry->Value = std::get<int>(entry->Default); break; case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break; case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break; + case 3: *(int64_t*)entry->Value = std::get<int64_t>(entry->Default); break; } } @@ -462,6 +469,7 @@ void Save() case 0: Platform::FileWriteFormatted(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value); break; case 1: Platform::FileWriteFormatted(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break; case 2: Platform::FileWriteFormatted(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; + case 3: Platform::FileWriteFormatted(f, "%s=%" PRId64 "\r\n", entry->Name, *(int64_t*)entry->Value); break; } } diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index b1d9532..fba9bfb 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -57,9 +57,9 @@ namespace Config struct ConfigEntry { char Name[32]; - int Type; // 0=int 1=bool 2=string + int Type; // 0=int 1=bool 2=string 3=64bit int void* Value; // pointer to the value variable - std::variant<int, bool, std::string> Default; + std::variant<int, bool, std::string, int64_t> Default; bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer }; @@ -185,6 +185,8 @@ extern bool MouseHide; extern int MouseHideSeconds; extern bool PauseLostFocus; +extern int64_t RTCOffset; + extern bool DSBatteryLevelOkay; extern int DSiBatteryLevel; extern bool DSiBatteryCharging; diff --git a/src/frontend/qt_sdl/DateTimeDialog.cpp b/src/frontend/qt_sdl/DateTimeDialog.cpp new file mode 100644 index 0000000..2ef9688 --- /dev/null +++ b/src/frontend/qt_sdl/DateTimeDialog.cpp @@ -0,0 +1,91 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> + +#include "types.h" +#include "Config.h" + +#include "DateTimeDialog.h" +#include "ui_DateTimeDialog.h" + +DateTimeDialog* DateTimeDialog::currentDlg = nullptr; + + +DateTimeDialog::DateTimeDialog(QWidget* parent) : QDialog(parent), ui(new Ui::DateTimeDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + QDateTime now = QDateTime::currentDateTime(); + customTime = now.addSecs(Config::RTCOffset); + + ui->chkChangeTime->setChecked(false); + ui->chkResetTime->setChecked(false); + + ui->lblCustomTime->setText(customTime.toString(ui->txtNewCustomTime->displayFormat())); + startTimer(1000); + + ui->txtNewCustomTime->setEnabled(ui->chkChangeTime->isChecked()); +} + +DateTimeDialog::~DateTimeDialog() +{ + delete ui; +} + +void DateTimeDialog::timerEvent(QTimerEvent* event) +{ + customTime = customTime.addSecs(1); + ui->lblCustomTime->setText(customTime.toString(ui->txtNewCustomTime->displayFormat())); +} + +void DateTimeDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + if (ui->chkChangeTime->isChecked()) + { + QDateTime now = QDateTime::currentDateTime(); + Config::RTCOffset = now.secsTo(ui->txtNewCustomTime->dateTime()); + } + else if (ui->chkResetTime->isChecked()) + Config::RTCOffset = 0; + + Config::Save(); + } + + QDialog::done(r); + + closeDlg(); +} + +void DateTimeDialog::on_chkChangeTime_clicked(bool checked) +{ + if (checked) ui->chkResetTime->setChecked(false); + ui->txtNewCustomTime->setEnabled(checked); +} + +void DateTimeDialog::on_chkResetTime_clicked(bool checked) +{ + if (checked) + { + ui->chkChangeTime->setChecked(false); + ui->txtNewCustomTime->setEnabled(false); + } +} diff --git a/src/frontend/qt_sdl/DateTimeDialog.h b/src/frontend/qt_sdl/DateTimeDialog.h new file mode 100644 index 0000000..f13dbd0 --- /dev/null +++ b/src/frontend/qt_sdl/DateTimeDialog.h @@ -0,0 +1,70 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef DATETIMEDIALOG_H +#define DATETIMEDIALOG_H + +#include <QDialog> +#include <QButtonGroup> +#include <QDateTime> + +namespace Ui {class DateTimeDialog; } +class DateTimeDialog; + +class DateTimeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DateTimeDialog(QWidget* parent); + ~DateTimeDialog(); + + static DateTimeDialog* currentDlg; + static DateTimeDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new DateTimeDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +protected: + void timerEvent(QTimerEvent* event) override; + +private slots: + void done(int r); + + void on_chkChangeTime_clicked(bool checked); + void on_chkResetTime_clicked(bool checked); + +private: + Ui::DateTimeDialog* ui; + + QDateTime customTime; +}; + +#endif // DATETIMEDIALOG_H diff --git a/src/frontend/qt_sdl/DateTimeDialog.ui b/src/frontend/qt_sdl/DateTimeDialog.ui new file mode 100644 index 0000000..59eb677 --- /dev/null +++ b/src/frontend/qt_sdl/DateTimeDialog.ui @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>DateTimeDialog</class> + <widget class="QDialog" name="DateTimeDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>357</width> + <height>161</height> + </rect> + </property> + <property name="windowTitle"> + <string>Date and time - melonDS</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Date and time</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Current value:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="lblCustomTime"> + <property name="text"> + <string>[placeholder]</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="chkChangeTime"> + <property name="text"> + <string>Change to:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDateTimeEdit" name="txtNewCustomTime"> + <property name="dateTime"> + <datetime> + <hour>0</hour> + <minute>0</minute> + <second>0</second> + <year>2000</year> + <month>1</month> + <day>1</day> + </datetime> + </property> + <property name="maximumDateTime"> + <datetime> + <hour>23</hour> + <minute>59</minute> + <second>59</second> + <year>2099</year> + <month>12</month> + <day>31</day> + </datetime> + </property> + <property name="minimumDateTime"> + <datetime> + <hour>0</hour> + <minute>0</minute> + <second>0</second> + <year>2000</year> + <month>1</month> + <day>1</day> + </datetime> + </property> + <property name="displayFormat"> + <string>dd/MM/yyyy HH:mm:ss</string> + </property> + <property name="calendarPopup"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QCheckBox" name="chkResetTime"> + <property name="text"> + <string>Reset to system date and time</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>DateTimeDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>DateTimeDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 0cb9574..252594c 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -23,6 +23,7 @@ #include <string> #include <QStandardPaths> #include <QString> +#include <QDateTime> #include <QDir> #include <QThread> #include <QSemaphore> @@ -610,6 +611,15 @@ void WriteFirmware(const SPI_Firmware::Firmware& firmware, u32 writeoffset, u32 } +void WriteDateTime(int year, int month, int day, int hour, int minute, int second) +{ + QDateTime hosttime = QDateTime::currentDateTime(); + QDateTime time = QDateTime(QDate(year, month, day), QTime(hour, minute, second)); + + Config::RTCOffset = hosttime.secsTo(time); + Config::Save(); +} + bool MP_Init() { return LocalMP::Init(); diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index cc65dfd..648ab67 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -28,6 +28,8 @@ #include <utility> #include <fstream> +#include <QDateTime> + #include <zstd.h> #ifdef ARCHIVE_SUPPORT_ENABLED #include "ArchiveUtil.h" @@ -39,6 +41,7 @@ #include "NDS.h" #include "DSi.h" #include "SPI.h" +#include "RTC.h" #include "DSi_I2C.h" #include "FreeBIOS.h" @@ -589,6 +592,15 @@ void SetBatteryLevels() } } +void SetDateTime() +{ + QDateTime hosttime = QDateTime::currentDateTime(); + QDateTime time = hosttime.addSecs(Config::RTCOffset); + + RTC::SetDateTime(time.date().year(), time.date().month(), time.date().day(), + time.time().hour(), time.time().minute(), time.time().second()); +} + void Reset() { NDS::SetConsoleType(Config::ConsoleType); @@ -602,6 +614,7 @@ void Reset() } NDS::Reset(); SetBatteryLevels(); + SetDateTime(); if ((CartType != -1) && NDSSave) { @@ -678,6 +691,7 @@ bool LoadBIOS() NDS::Reset(); SetBatteryLevels(); + SetDateTime(); return true; } @@ -1204,6 +1218,7 @@ bool LoadROM(QStringList filepath, bool reset) NDS::Reset(); SetBatteryLevels(); + SetDateTime(); } u32 savelen = 0; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index a6cc248..f670477 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -58,6 +58,7 @@ #include "main.h" #include "Input.h" #include "CheatsDialog.h" +#include "DateTimeDialog.h" #include "EmuSettingsDialog.h" #include "InputConfig/InputConfigDialog.h" #include "VideoSettingsDialog.h" @@ -90,6 +91,7 @@ #include "LocalMP.h" #include "Config.h" #include "DSi_I2C.h" +#include "RTC.h" #include "Savestate.h" @@ -313,6 +315,7 @@ void EmuThread::deinitOpenGL() void EmuThread::run() { u32 mainScreenPos[3]; + Platform::FileHandle* file; NDS::Init(); @@ -352,6 +355,15 @@ void EmuThread::run() u32 winUpdateCount = 0, winUpdateFreq = 1; u8 dsiVolumeLevel = 0x1F; + file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); + if (file) + { + RTC::StateData state; + Platform::FileRead(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + RTC::SetState(state); + } + char melontitle[100]; while (EmuRunning != emuStatus_Exit) @@ -650,6 +662,15 @@ void EmuThread::run() } } + file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); + if (file) + { + RTC::StateData state; + RTC::GetState(state); + Platform::FileWrite(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + } + EmuStatus = emuStatus_Exit; GPU::DeInitRenderer(); @@ -1525,6 +1546,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actPowerManagement = menu->addAction("Power management"); connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + menu->addSeparator(); actEnableCheats = menu->addAction("Enable cheats"); @@ -1787,6 +1811,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actStop->setEnabled(false); actFrameStep->setEnabled(false); + actDateTime->setEnabled(true); actPowerManagement->setEnabled(false); actSetupCheats->setEnabled(false); @@ -2737,6 +2762,16 @@ void MainWindow::onFrameStep() emuThread->emuFrameStep(); } +void MainWindow::onOpenDateTime() +{ + DateTimeDialog* dlg = DateTimeDialog::openDlg(this); +} + +void MainWindow::onOpenPowerManagement() +{ + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this); +} + void MainWindow::onEnableCheats(bool checked) { Config::EnableCheats = checked?1:0; @@ -2824,11 +2859,6 @@ void MainWindow::onEmuSettingsDialogFinished(int res) actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); } -void MainWindow::onOpenPowerManagement() -{ - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this); -} - void MainWindow::onOpenInputConfig() { emuThread->emuPause(); @@ -3148,6 +3178,7 @@ void MainWindow::onEmuStart() actStop->setEnabled(true); actFrameStep->setEnabled(true); + actDateTime->setEnabled(false); actPowerManagement->setEnabled(true); actTitleManager->setEnabled(false); @@ -3169,6 +3200,7 @@ void MainWindow::onEmuStop() actStop->setEnabled(false); actFrameStep->setEnabled(false); + actDateTime->setEnabled(true); actPowerManagement->setEnabled(false); actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 073a4da..5832ed3 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -299,6 +299,8 @@ private slots: void onReset(); void onStop(); void onFrameStep(); + void onOpenPowerManagement(); + void onOpenDateTime(); void onEnableCheats(bool checked); void onSetupCheats(); void onCheatsDialogFinished(int res); @@ -309,7 +311,6 @@ private slots: void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); - void onOpenPowerManagement(); void onOpenInputConfig(); void onInputConfigFinished(int res); void onOpenVideoSettings(); @@ -397,6 +398,8 @@ public: QAction* actReset; QAction* actStop; QAction* actFrameStep; + QAction* actPowerManagement; + QAction* actDateTime; QAction* actEnableCheats; QAction* actSetupCheats; QAction* actROMInfo; @@ -408,7 +411,6 @@ public: #ifdef __APPLE__ QAction* actPreferences; #endif - QAction* actPowerManagement; QAction* actInputConfig; QAction* actVideoSettings; QAction* actCameraSettings; |