aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/DSi.cpp8
-rw-r--r--src/GPU.cpp18
-rw-r--r--src/GPU.h1
-rw-r--r--src/NDS.cpp310
-rw-r--r--src/NDS.h29
-rw-r--r--src/Platform.h3
-rw-r--r--src/RTC.cpp847
-rw-r--r--src/RTC.h29
-rw-r--r--src/SPI_Firmware.h2
-rw-r--r--src/SPU.cpp2
-rw-r--r--src/Savestate.h2
-rw-r--r--src/frontend/qt_sdl/CMakeLists.txt1
-rw-r--r--src/frontend/qt_sdl/Config.cpp8
-rw-r--r--src/frontend/qt_sdl/Config.h6
-rw-r--r--src/frontend/qt_sdl/DateTimeDialog.cpp91
-rw-r--r--src/frontend/qt_sdl/DateTimeDialog.h70
-rw-r--r--src/frontend/qt_sdl/DateTimeDialog.ui148
-rw-r--r--src/frontend/qt_sdl/Platform.cpp10
-rw-r--r--src/frontend/qt_sdl/ROMManager.cpp15
-rw-r--r--src/frontend/qt_sdl/main.cpp42
-rw-r--r--src/frontend/qt_sdl/main.h6
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)
diff --git a/src/GPU.h b/src/GPU.h
index 9686704..cec8a2d 100644
--- a/src/GPU.h
+++ b/src/GPU.h
@@ -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;
diff --git a/src/NDS.h b/src/NDS.h
index e81e952..de038b2 100644
--- a/src/NDS.h
+++ b/src/NDS.h
@@ -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(&timestamp, &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(&timestamp, &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);
}
diff --git a/src/RTC.h b/src/RTC.h
index 0fed9fd..ff84271 100644
--- a/src/RTC.h
+++ b/src/RTC.h
@@ -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;