RTC revamp (#1867)
* get this started * implement DSi RTC commands * set up RTC clock timer. lay down basic idea of a clock. * make the date/time registers writable * move RTC state to its own structure, to make it easier to deal with * more RTC work lay base for date/time dialog * get the bulk of the RTC functionality going * much simpler design for RTC stuff * aha, that is what it is * start working on the RTC IRQ * implement all types of RTC IRQ * start refining sleep mode. code still kinda sucks. * implement keypad IRQ * refine it some more * shut the fuck uuuuuupppppppppppppp
This commit is contained in:
parent
21590b0709
commit
9a450f5f28
|
@ -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
|
||||
{
|
||||
|
|
18
src/GPU.cpp
18
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);
|
||||
|
||||
|
|
312
src/NDS.cpp
312
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)
|
|||
}
|
||||
}
|
||||
|
||||
u64 NextTargetSleep()
|
||||
{
|
||||
u64 minEvent = UINT64_MAX;
|
||||
|
||||
u32 mask = SchedListMask;
|
||||
for (int i = 0; i < Event_MAX; i++)
|
||||
{
|
||||
if (!mask) break;
|
||||
if (i == Event_SPU || i == Event_RTC)
|
||||
{
|
||||
if (mask & 0x1)
|
||||
{
|
||||
if (SchedList[i].Timestamp < minEvent)
|
||||
minEvent = SchedList[i].Timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
return minEvent;
|
||||
}
|
||||
|
||||
void RunSystemSleep(u64 timestamp)
|
||||
{
|
||||
u64 offset = timestamp - SysTimestamp;
|
||||
SysTimestamp = timestamp;
|
||||
|
||||
u32 mask = SchedListMask;
|
||||
for (int i = 0; i < Event_MAX; i++)
|
||||
{
|
||||
if (!mask) break;
|
||||
if (i == Event_SPU || i == Event_RTC)
|
||||
{
|
||||
if (mask & 0x1)
|
||||
{
|
||||
if (SchedList[i].Timestamp <= SysTimestamp)
|
||||
{
|
||||
SchedListMask &= ~(1<<i);
|
||||
|
||||
u32 param;
|
||||
if (i == Event_SPU)
|
||||
param = 1;
|
||||
else
|
||||
param = SchedList[i].Param;
|
||||
|
||||
SchedList[i].Func(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mask & 0x1)
|
||||
{
|
||||
if (SchedList[i].Timestamp <= SysTimestamp)
|
||||
{
|
||||
SchedList[i].Timestamp += offset;
|
||||
}
|
||||
}
|
||||
|
||||
mask >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool EnableJIT, int ConsoleType>
|
||||
u32 RunFrame()
|
||||
{
|
||||
FrameStartTimestamp = SysTimestamp;
|
||||
|
||||
GPU::TotalScanlines = 0;
|
||||
|
||||
LagFrameFlag = true;
|
||||
bool runFrame = Running && !(CPUStop & 0x40000000);
|
||||
if (runFrame)
|
||||
bool runFrame = Running && !(CPUStop & CPUStop_Sleep);
|
||||
while (Running)
|
||||
{
|
||||
ARM9->CheckGdbIncoming();
|
||||
ARM7->CheckGdbIncoming();
|
||||
u64 frametarget = SysTimestamp + 560190;
|
||||
|
||||
GPU::StartFrame();
|
||||
|
||||
while (Running && GPU::TotalScanlines==0)
|
||||
if (CPUStop & CPUStop_Sleep)
|
||||
{
|
||||
u64 target = NextTarget();
|
||||
ARM9Target = target << ARM9ClockShift;
|
||||
CurCPU = 0;
|
||||
// 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
|
||||
|
||||
if (CPUStop & 0x80000000)
|
||||
while (Running && (SysTimestamp < frametarget))
|
||||
{
|
||||
// GXFIFO stall
|
||||
s32 cycles = GPU3D::CyclesToRunFor();
|
||||
u64 target = NextTargetSleep();
|
||||
if (target > frametarget)
|
||||
target = frametarget;
|
||||
|
||||
ARM9Timestamp = std::min(ARM9Target, ARM9Timestamp+(cycles<<ARM9ClockShift));
|
||||
}
|
||||
else if (CPUStop & 0x0FFF)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef JIT_ENABLED
|
||||
if (EnableJIT)
|
||||
ARM9->ExecuteJIT();
|
||||
else
|
||||
#endif
|
||||
ARM9->Execute();
|
||||
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;
|
||||
|
||||
while (ARM7Timestamp < target)
|
||||
if (!(CPUStop & CPUStop_Wakeup))
|
||||
{
|
||||
ARM7Target = target; // might be changed by a reschedule
|
||||
GPU::StartFrame();
|
||||
}
|
||||
CPUStop &= ~CPUStop_Wakeup;
|
||||
|
||||
if (CPUStop & 0x0FFF0000)
|
||||
while (Running && GPU::TotalScanlines==0)
|
||||
{
|
||||
u64 target = NextTarget();
|
||||
ARM9Target = target << ARM9ClockShift;
|
||||
CurCPU = 0;
|
||||
|
||||
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;
|
||||
|
||||
|
|
29
src/NDS.h
29
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;
|
||||
|
|
|
@ -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
|
||||
|
|
881
src/RTC.cpp
881
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,6 +112,740 @@ 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;
|
||||
}
|
||||
|
||||
u8 BCDSanitize(u8 val, u8 vmin, u8 vmax)
|
||||
{
|
||||
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)))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// CHECKME: is the IRQ status readable in RCNT?
|
||||
NDS::SetIRQ(1, NDS::IRQ_RTC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ClearIRQ(0x10);
|
||||
}
|
||||
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 0b0010:
|
||||
case 0b0110: // per-minute edge interrupt
|
||||
if ((type == 0) || (type == 2 && (State.IRQFlag & 0x01)))
|
||||
{
|
||||
SetIRQ(0x10);
|
||||
}
|
||||
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 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 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;
|
||||
|
||||
default: // 32KHz output
|
||||
if (type == 1)
|
||||
{
|
||||
SetIRQ(0x10);
|
||||
ClearIRQ(0x10);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// INT2
|
||||
|
||||
if (State.StatusReg2 & (1<<6))
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 0x04: // Apr
|
||||
case 0x06: // Jun
|
||||
case 0x09: // Sep
|
||||
case 0x11: // Nov
|
||||
numdays = 0x30;
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
hour = 0;
|
||||
CountDay();
|
||||
}
|
||||
|
||||
pm = (hour >= 0x12) ? 0x40 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 12-hour mode
|
||||
|
||||
if (hour >= 0x12)
|
||||
{
|
||||
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 2: // month
|
||||
State.DateTime[1] = BCDSanitize(val & 0x1F, 0x01, 0x12);
|
||||
break;
|
||||
|
||||
case 3: // day
|
||||
State.DateTime[2] = BCDSanitize(val & 0x3F, 0x01, 0x31);
|
||||
CheckEndOfMonth();
|
||||
break;
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -124,107 +859,25 @@ void ByteIn(u8 val)
|
|||
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)
|
||||
{
|
||||
switch (CurCmd & 0x70)
|
||||
{
|
||||
case 0x00: Output[0] = StatusReg1; break;
|
||||
case 0x40: Output[0] = StatusReg2; 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 0x60:
|
||||
{
|
||||
time_t timestamp = time(NULL);
|
||||
struct tm timedata;
|
||||
localtime_r(×tamp, &timedata);
|
||||
|
||||
Output[0] = BCD(timedata.tm_hour);
|
||||
Output[1] = BCD(timedata.tm_min);
|
||||
Output[2] = BCD(timedata.tm_sec);
|
||||
}
|
||||
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 0x50:
|
||||
Output[0] = Alarm2[0];
|
||||
Output[1] = Alarm2[1];
|
||||
Output[2] = Alarm2[2];
|
||||
break;
|
||||
|
||||
case 0x30: Output[0] = ClockAdjust; break;
|
||||
case 0x70: Output[0] = FreeReg; break;
|
||||
}
|
||||
CmdRead();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (CurCmd & 0x70)
|
||||
{
|
||||
case 0x00:
|
||||
if (InputPos == 1) StatusReg1 = val & 0x0E;
|
||||
break;
|
||||
|
||||
case 0x40:
|
||||
if (InputPos == 1) StatusReg2 = val;
|
||||
if (StatusReg2 & 0x4F) Log(LogLevel::Debug, "RTC INTERRUPT ON: %02X\n", StatusReg2);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
// TODO: set time somehow??
|
||||
break;
|
||||
|
||||
case 0x60:
|
||||
// same shit
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
if (StatusReg2 & 0x04)
|
||||
{
|
||||
if (InputPos <= 3) Alarm1[InputPos-1] = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (InputPos == 1) Alarm1[2] = val;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x50:
|
||||
if (InputPos <= 3) Alarm2[InputPos-1] = val;
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
if (InputPos == 1) ClockAdjust = val;
|
||||
break;
|
||||
|
||||
case 0x70:
|
||||
if (InputPos == 1) FreeReg = val;
|
||||
break;
|
||||
}
|
||||
CmdWrite(val);
|
||||
}
|
||||
|
||||
|
||||
|
|
29
src/RTC.h
29
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);
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ union UserData
|
|||
u8 TouchCalibrationPixel2[2];
|
||||
u16 Settings;
|
||||
u8 Year;
|
||||
u8 Unknown1;
|
||||
u8 RTCClockAdjust;
|
||||
u32 RTCOffset;
|
||||
u8 Unused2[4];
|
||||
u16 UpdateCounter;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <stdio.h>
|
||||
#include "types.h"
|
||||
|
||||
#define SAVESTATE_MAJOR 10
|
||||
#define SAVESTATE_MAJOR 11
|
||||
#define SAVESTATE_MINOR 1
|
||||
|
||||
class Savestate
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue