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:
Arisotura 2023-10-30 18:37:49 +01:00 committed by GitHub
parent 21590b0709
commit 9a450f5f28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1470 additions and 214 deletions

View File

@ -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
{

View File

@ -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)

View File

@ -607,6 +607,7 @@ void SetPowerCnt(u32 val);
void StartFrame();
void FinishFrame(u32 lines);
void BlankFrame();
void StartScanline(u32 line);
void StartHBlank(u32 line);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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(&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 0x60:
{
time_t timestamp = time(NULL);
struct tm timedata;
localtime_r(&timestamp, &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);
}

View File

@ -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);

View File

@ -396,7 +396,7 @@ union UserData
u8 TouchCalibrationPixel2[2];
u16 Settings;
u8 Year;
u8 Unknown1;
u8 RTCClockAdjust;
u32 RTCOffset;
u8 Unused2[4];
u16 UpdateCounter;

View File

@ -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();

View File

@ -24,7 +24,7 @@
#include <stdio.h>
#include "types.h"
#define SAVESTATE_MAJOR 10
#define SAVESTATE_MAJOR 11
#define SAVESTATE_MINOR 1
class Savestate

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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();

View File

@ -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;

View File

@ -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());

View File

@ -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;