1 // Copyright (c) 2012- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #ifdef _WIN32
19 #include "Common/CommonWindows.h"
20 // timeval already defined in xtl.h
21 #include <Winsock2.h>
22 #else
23 #include <sys/time.h>
24 #endif
25
26 #include <time.h>
27
28 #include "Common/Serialize/Serializer.h"
29 #include "Common/Serialize/SerializeFuncs.h"
30 #include "Common/TimeUtil.h"
31 #include "Core/HLE/HLE.h"
32 #include "Core/HLE/FunctionWrappers.h"
33 #include "Core/MIPS/MIPS.h"
34 #include "Core/Reporting.h"
35 #include "Core/CoreTiming.h"
36 #include "Core/MemMapHelpers.h"
37
38 #include "Core/HLE/sceKernel.h"
39 #include "Core/HLE/sceRtc.h"
40
41 #ifdef HAVE_LIBNX
42 // I guess that works...
43 #define setenv(x, y, z) (void*)0
44 #define tzset() (void*)0
45 #define unsetenv(x) (void*)0
46 #endif // HAVE_LIBNX
47
48 // This is a base time that everything is relative to.
49 // This way, time doesn't move strangely with savestates, turbo speed, etc.
50 static PSPTimeval rtcBaseTime;
51 static u64 rtcBaseTicks;
52
53 // Grabbed from JPSCP
54 // This is the # of microseconds between January 1, 0001 and January 1, 1970.
55 const u64 rtcMagicOffset = 62135596800000000ULL;
56 // This is the # of microseconds between January 1, 0001 and January 1, 1601 (for Win32 FILETIME.)
57 const u64 rtcFiletimeOffset = 50491123200000000ULL;
58
59 // 400 years is a convenient number, since leap days and everything cycle every 400 years.
60 // 400 years is in other words 20871 full weeks.
61 const u64 rtc400YearTicks = (u64)20871 * 7 * 24 * 3600 * 1000000ULL;
62
63 // This is the last moment the clock was adjusted.
64 // It's possible games may not like the clock being adjusted in the past hour (cheating?)
65 // So this returns a static time.
66 const u64 rtcLastAdjustedTicks = rtcMagicOffset + 41 * 365 * 24 * 3600 * 1000000ULL;
67 // The reincarnated time seems related to the battery or manufacturing date.
68 // On a test PSP, it was over 3 years in the past, so we again pick a fixed date.
69 const u64 rtcLastReincarnatedTicks = rtcMagicOffset + 40 * 365 * 24 * 3600 * 1000000ULL;
70
71 const int PSP_TIME_INVALID_YEAR = -1;
72 const int PSP_TIME_INVALID_MONTH = -2;
73 const int PSP_TIME_INVALID_DAY = -3;
74 const int PSP_TIME_INVALID_HOUR = -4;
75 const int PSP_TIME_INVALID_MINUTES = -5;
76 const int PSP_TIME_INVALID_SECONDS = -6;
77 const int PSP_TIME_INVALID_MICROSECONDS = -7;
78
__RtcGetCurrentTick()79 static u64 __RtcGetCurrentTick()
80 {
81 // TODO: It's probably expecting ticks since January 1, 0001?
82 return CoreTiming::GetGlobalTimeUs() + rtcBaseTicks;
83 }
84
85 #if defined(__MINGW32__)
_get_timezone(long * seconds)86 errno_t _get_timezone(long *seconds)
87 {
88 time_t now = time(NULL);
89
90 struct tm *gm = gmtime(&now);
91 time_t gmt = mktime(gm);
92
93 struct tm *loc = localtime(&now);
94 time_t local = mktime(loc);
95
96 *seconds = local - gmt;
97 return 0;
98 }
99 #endif
100
101 #if defined(_WIN32)
102 #define FILETIME_FROM_UNIX_EPOCH_US (rtcMagicOffset - rtcFiletimeOffset)
103
gettimeofday(timeval * tv,void * ignore)104 void gettimeofday(timeval *tv, void *ignore)
105 {
106 FILETIME ft_utc, ft_local;
107 GetSystemTimeAsFileTime(&ft_utc);
108 ft_local = ft_utc;
109
110 u64 from_1601_us = (((u64) ft_local.dwHighDateTime << 32ULL) + (u64) ft_local.dwLowDateTime) / 10ULL;
111 u64 from_1970_us = from_1601_us - FILETIME_FROM_UNIX_EPOCH_US;
112
113 tv->tv_sec = long(from_1970_us / 1000000UL);
114 tv->tv_usec = from_1970_us % 1000000UL;
115 }
116
rtc_timegm(struct tm * tm)117 time_t rtc_timegm(struct tm *tm)
118 {
119 return _mkgmtime(tm);
120 }
121
122 #elif (defined(__GLIBC__) && !defined(__ANDROID__))
123 #define rtc_timegm timegm
124 #else
125
rtc_timegm(struct tm * tm)126 static time_t rtc_timegm(struct tm *tm)
127 {
128 time_t ret;
129 char *tz;
130 std::string tzcopy;
131
132 tz = getenv("TZ");
133 if (tz)
134 tzcopy = tz;
135
136 setenv("TZ", "", 1);
137 tzset();
138 ret = mktime(tm);
139 if (tz)
140 setenv("TZ", tzcopy.c_str(), 1);
141 else
142 unsetenv("TZ");
143 tzset();
144 return ret;
145 }
146
147 #endif
148
RtcUpdateBaseTicks()149 static void RtcUpdateBaseTicks() {
150 rtcBaseTicks = 1000000ULL * rtcBaseTime.tv_sec + rtcBaseTime.tv_usec + rtcMagicOffset;
151 }
152
__RtcInit()153 void __RtcInit()
154 {
155 // This is the base time, the only case we use gettimeofday() for.
156 // Everything else is relative to that, "virtual time."
157 timeval tv;
158 gettimeofday(&tv, NULL);
159 rtcBaseTime.tv_sec = tv.tv_sec;
160 rtcBaseTime.tv_usec = 0;
161 // Precalculate the current time in microseconds (rtcMagicOffset is offset to 1970.)
162 RtcUpdateBaseTicks();
163 }
164
__RtcDoState(PointerWrap & p)165 void __RtcDoState(PointerWrap &p)
166 {
167 auto s = p.Section("sceRtc", 1);
168 if (!s)
169 return;
170
171 Do(p, rtcBaseTime);
172 // Update the precalc, pointless to savestate this as it's just based on the other value.
173 RtcUpdateBaseTicks();
174 }
175
__RtcTimeOfDay(PSPTimeval * tv)176 void __RtcTimeOfDay(PSPTimeval *tv)
177 {
178 s64 additionalUs = CoreTiming::GetGlobalTimeUs();
179 *tv = rtcBaseTime;
180
181 s64 adjustedUs = additionalUs + tv->tv_usec;
182 tv->tv_sec += long(adjustedUs / 1000000UL);
183 tv->tv_usec = adjustedUs % 1000000UL;
184 }
185
RtcBaseTime(int32_t * micro)186 int32_t RtcBaseTime(int32_t *micro) {
187 if (micro) {
188 *micro = rtcBaseTime.tv_usec;
189 }
190 return rtcBaseTime.tv_sec;
191 }
192
RtcSetBaseTime(int32_t seconds,int32_t micro)193 void RtcSetBaseTime(int32_t seconds, int32_t micro) {
194 rtcBaseTime.tv_sec = seconds;
195 rtcBaseTime.tv_usec = micro;
196 RtcUpdateBaseTicks();
197 }
198
__RtcTmToPspTime(ScePspDateTime & t,const tm * val)199 static void __RtcTmToPspTime(ScePspDateTime &t, const tm *val)
200 {
201 t.year = val->tm_year + 1900;
202 t.month = val->tm_mon + 1;
203 t.day = val->tm_mday;
204 t.hour = val->tm_hour;
205 t.minute = val->tm_min;
206 t.second = val->tm_sec;
207 t.microsecond = 0;
208 }
209
__RtcPspTimeToTm(tm & val,const ScePspDateTime & pt)210 static void __RtcPspTimeToTm(tm &val, const ScePspDateTime &pt)
211 {
212 val.tm_year = pt.year - 1900;
213 val.tm_mon = pt.month - 1;
214 val.tm_mday = pt.day;
215 val.tm_wday = -1;
216 val.tm_yday = -1;
217 val.tm_hour = pt.hour;
218 val.tm_min = pt.minute;
219 val.tm_sec = pt.second;
220 val.tm_isdst = 0;
221 }
222
__RtcTicksToPspTime(ScePspDateTime & t,u64 ticks)223 static void __RtcTicksToPspTime(ScePspDateTime &t, u64 ticks)
224 {
225 int numYearAdd = 0;
226 if (ticks < 1000000ULL)
227 {
228 t.year = 1;
229 t.month = 1;
230 t.day = 1;
231 t.hour = 0;
232 t.minute = 0;
233 t.second = 0;
234 t.microsecond = ticks % 1000000ULL;
235 return;
236 }
237 else if (ticks < rtcMagicOffset)
238 {
239 // Need to get a year past 1970 for gmtime
240 // Add enough 400 year to pass over 1970.
241 numYearAdd = (int) ((rtcMagicOffset - ticks) / rtc400YearTicks + 1);
242 ticks += rtc400YearTicks * numYearAdd;
243 }
244
245 while (ticks >= rtcMagicOffset + rtc400YearTicks)
246 {
247 ticks -= rtc400YearTicks;
248 --numYearAdd;
249 }
250
251 time_t time = (ticks - rtcMagicOffset) / 1000000ULL;
252 t.microsecond = ticks % 1000000ULL;
253
254 tm *local = gmtime(&time);
255 if (!local)
256 {
257 ERROR_LOG(SCERTC, "Date is too high/low to handle, pretending to work.");
258 return;
259 }
260
261 t.year = local->tm_year + 1900 - numYearAdd * 400;
262 t.month = local->tm_mon + 1;
263 t.day = local->tm_mday;
264 t.hour = local->tm_hour;
265 t.minute = local->tm_min;
266 t.second = local->tm_sec;
267 }
268
__RtcPspTimeToTicks(const ScePspDateTime & pt)269 static u64 __RtcPspTimeToTicks(const ScePspDateTime &pt)
270 {
271 tm local;
272 __RtcPspTimeToTm(local, pt);
273
274 s64 tickOffset = 0;
275 while (local.tm_year < 70)
276 {
277 tickOffset -= rtc400YearTicks;
278 local.tm_year += 400;
279 }
280 while (local.tm_year >= 470)
281 {
282 tickOffset += rtc400YearTicks;
283 local.tm_year -= 400;
284 }
285
286 time_t seconds = rtc_timegm(&local);
287 u64 result = rtcMagicOffset + (u64) seconds * 1000000ULL;
288 result += pt.microsecond;
289 return result + tickOffset;
290 }
291
__RtcValidatePspTime(const ScePspDateTime & t)292 static bool __RtcValidatePspTime(const ScePspDateTime &t)
293 {
294 return t.year > 0 && t.year <= 9999;
295 }
296
sceRtcGetTickResolution()297 static u32 sceRtcGetTickResolution()
298 {
299 DEBUG_LOG(SCERTC, "sceRtcGetTickResolution()");
300 return 1000000;
301 }
302
sceRtcGetCurrentTick(u32 tickPtr)303 static u32 sceRtcGetCurrentTick(u32 tickPtr)
304 {
305 VERBOSE_LOG(SCERTC, "sceRtcGetCurrentTick(%08x)", tickPtr);
306
307 u64 curTick = __RtcGetCurrentTick();
308 if (Memory::IsValidAddress(tickPtr))
309 Memory::Write_U64(curTick, tickPtr);
310 hleEatCycles(300);
311 hleReSchedule("rtc current tick");
312 return 0;
313 }
314
sceRtcGetAccumulativeTime()315 static u64 sceRtcGetAccumulativeTime()
316 {
317 DEBUG_LOG(SCERTC, "sceRtcGetAccumulativeTime()");
318 hleEatCycles(300);
319 hleReSchedule("rtc accumulative time");
320 return __RtcGetCurrentTick();
321 }
322
sceRtcGetCurrentClock(u32 pspTimePtr,int tz)323 static u32 sceRtcGetCurrentClock(u32 pspTimePtr, int tz) {
324 auto pt = PSPPointer<ScePspDateTime>::Create(pspTimePtr);
325
326 PSPTimeval tv;
327 __RtcTimeOfDay(&tv);
328
329 time_t sec = (time_t)tv.tv_sec;
330 tm *utc = gmtime(&sec);
331 if (!utc) {
332 return hleLogError(SCERTC, 0, "Date is too high/low to handle, pretending to work");
333 }
334
335 utc->tm_isdst = -1;
336 utc->tm_min += tz;
337 rtc_timegm(utc); // Return gmt time with timezone offset.
338
339 if (pt.IsValid()) {
340 __RtcTmToPspTime(*pt, utc);
341 pt->microsecond = tv.tv_usec;
342 }
343
344 hleEatCycles(1900);
345 hleReSchedule("rtc current clock");
346 return hleLogSuccessI(SCERTC, 0);
347 }
348
sceRtcGetCurrentClockLocalTime(u32 pspTimePtr)349 static u32 sceRtcGetCurrentClockLocalTime(u32 pspTimePtr) {
350 auto pt = PSPPointer<ScePspDateTime>::Create(pspTimePtr);
351
352 PSPTimeval tv;
353 __RtcTimeOfDay(&tv);
354
355 time_t sec = (time_t)tv.tv_sec;
356 const tm *local = localtime(&sec);
357 if (!local) {
358 return hleLogError(SCERTC, 0, "Date is too high/low to handle, pretending to work");
359 }
360
361 if (pt.IsValid()) {
362 __RtcTmToPspTime(*pt, local);
363 pt->microsecond = tv.tv_usec;
364 }
365
366 hleEatCycles(2000);
367 hleReSchedule("rtc current clock local");
368 return hleLogSuccessI(SCERTC, 0);
369 }
370
sceRtcSetTick(u32 pspTimePtr,u32 tickPtr)371 static u32 sceRtcSetTick(u32 pspTimePtr, u32 tickPtr) {
372 auto pt = PSPPointer<ScePspDateTime>::Create(pspTimePtr);
373 auto tick = PSPPointer<u64_le>::Create(tickPtr);
374
375 if (!pt.IsValid() || !tick.IsValid())
376 return hleLogError(SCERTC, 0, "bad address");
377
378 __RtcTicksToPspTime(*pt, *tick);
379 return hleLogSuccessI(SCERTC, 0);
380 }
381
sceRtcGetTick(u32 pspTimePtr,u32 tickPtr)382 static u32 sceRtcGetTick(u32 pspTimePtr, u32 tickPtr) {
383 auto pt = PSPPointer<const ScePspDateTime>::Create(pspTimePtr);
384 auto tick = PSPPointer<u64_le>::Create(tickPtr);
385
386 if (!pt.IsValid() || !tick.IsValid())
387 return hleLogError(SCERTC, 0, "bad address");
388 if (!__RtcValidatePspTime(*pt))
389 return hleLogWarning(SCERTC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid time");
390
391 *tick = __RtcPspTimeToTicks(*pt);
392 return hleLogSuccessI(SCERTC, 0);
393 }
394
sceRtcGetDayOfWeek(u32 year,u32 month,u32 day)395 static u32 sceRtcGetDayOfWeek(u32 year, u32 month, u32 day)
396 {
397 DEBUG_LOG(SCERTC, "sceRtcGetDayOfWeek(%d, %d, %d)", year, month, day);
398
399 if(month == 0) // Mark month 0 as august, don't know why, but works
400 {
401 month = 8;
402 }
403
404 if(month > 12) // After month 12, psp months does 31/31/30/31/30 and repeat
405 {
406 int restMonth = month-12;
407 int grp5 = restMonth / 5;
408 restMonth = restMonth % 5;
409 day += grp5 * (31*3+30*2);
410 static u32 t[] = { 31, 31*2, 31*2+30, 31*3+30, 31*3+30*2 };
411 day += t[restMonth-1];
412 month = 12;
413 }
414
415 while (year < 1900)
416 year += 400;
417 while (year > 2300)
418 year -= 400;
419
420 tm local;
421 local.tm_year = year - 1900;
422 local.tm_mon = month - 1;
423 local.tm_mday = day;
424 local.tm_wday = -1;
425 local.tm_yday = -1;
426 local.tm_hour = 0;
427 local.tm_min = 0;
428 local.tm_sec = 0;
429 local.tm_isdst = -1;
430
431 mktime(&local);
432 return local.tm_wday;
433 }
434
__RtcIsLeapYear(u32 year)435 static bool __RtcIsLeapYear(u32 year)
436 {
437 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
438 }
439
__RtcDaysInMonth(u32 year,u32 month)440 static int __RtcDaysInMonth(u32 year, u32 month)
441 {
442 switch (month)
443 {
444 case 4:
445 case 6:
446 case 9:
447 case 11:
448 return 30;
449
450 case 2:
451 if (__RtcIsLeapYear(year))
452 return 29;
453 return 28;
454
455 default:
456 return 31;
457 }
458 }
459
sceRtcGetDaysInMonth(u32 year,u32 month)460 static u32 sceRtcGetDaysInMonth(u32 year, u32 month)
461 {
462 DEBUG_LOG(SCERTC, "sceRtcGetDaysInMonth(%d, %d)", year, month);
463
464 if (year == 0 || month == 0 || month > 12)
465 return SCE_KERNEL_ERROR_INVALID_ARGUMENT;
466
467 return __RtcDaysInMonth(year, month);
468 }
469
sceRtcIsLeapYear(u32 year)470 static u32 sceRtcIsLeapYear(u32 year)
471 {
472 DEBUG_LOG(SCERTC, "sceRtcIsLeapYear(%d)", year);
473 return __RtcIsLeapYear(year) ? 1 : 0;
474 }
475
sceRtcConvertLocalTimeToUTC(u32 tickLocalPtr,u32 tickUTCPtr)476 static int sceRtcConvertLocalTimeToUTC(u32 tickLocalPtr,u32 tickUTCPtr)
477 {
478 DEBUG_LOG(SCERTC, "sceRtcConvertLocalTimeToUTC(%d, %d)", tickLocalPtr, tickUTCPtr);
479 if (Memory::IsValidAddress(tickLocalPtr) && Memory::IsValidAddress(tickUTCPtr))
480 {
481 u64 srcTick = Memory::Read_U64(tickLocalPtr);
482 // TODO : Let the user select his timezone / daylight saving instead of taking system param ?
483 #ifdef _WIN32
484 long timezone_val;
485 _get_timezone(&timezone_val);
486 srcTick -= -timezone_val * 1000000ULL;
487 #elif !defined(_AIX) && !defined(__sgi) && !defined(__hpux) && !defined(HAVE_LIBNX)
488 time_t timezone = 0;
489 tm *time = localtime(&timezone);
490 srcTick -= time->tm_gmtoff*1000000ULL;
491 #endif
492 Memory::Write_U64(srcTick, tickUTCPtr);
493 }
494 else
495 {
496 return 1;
497 }
498 return 0;
499 }
500
sceRtcConvertUtcToLocalTime(u32 tickUTCPtr,u32 tickLocalPtr)501 static int sceRtcConvertUtcToLocalTime(u32 tickUTCPtr,u32 tickLocalPtr)
502 {
503 DEBUG_LOG(SCERTC, "sceRtcConvertLocalTimeToUTC(%d, %d)", tickLocalPtr, tickUTCPtr);
504 if (Memory::IsValidAddress(tickLocalPtr) && Memory::IsValidAddress(tickUTCPtr))
505 {
506 u64 srcTick = Memory::Read_U64(tickUTCPtr);
507 // TODO : Let the user select his timezone / daylight saving instead of taking system param ?
508 #ifdef _WIN32
509 long timezone_val;
510 _get_timezone(&timezone_val);
511 srcTick += -timezone_val * 1000000ULL;
512 #elif !defined(_AIX) && !defined(__sgi) && !defined(__hpux) && !defined(HAVE_LIBNX)
513 time_t timezone = 0;
514 tm *time = localtime(&timezone);
515 srcTick += time->tm_gmtoff*1000000ULL;
516 #endif
517 Memory::Write_U64(srcTick, tickLocalPtr);
518 }
519 else
520 {
521 return 1;
522 }
523 return 0;
524 }
525
sceRtcCheckValid(u32 datePtr)526 static int sceRtcCheckValid(u32 datePtr) {
527 auto pt = PSPPointer<const ScePspDateTime>::Create(datePtr);
528
529 if (!pt.IsValid() )
530 return hleLogError(SCERTC, -1, "bad address");
531
532 int result = 0;
533 if (pt->year < 1 || pt->year > 9999)
534 result = PSP_TIME_INVALID_YEAR;
535 else if (pt->month < 1 || pt->month > 12)
536 result = PSP_TIME_INVALID_MONTH;
537 else if (pt->day < 1 || pt->day > 31)
538 result = PSP_TIME_INVALID_DAY;
539 else if (pt->day > __RtcDaysInMonth((s16)pt->year, (s16)pt->month))
540 result = PSP_TIME_INVALID_DAY;
541 else if (pt->hour < 0 || pt->hour > 23)
542 result = PSP_TIME_INVALID_HOUR;
543 else if (pt->minute < 0 || pt->minute > 59)
544 result = PSP_TIME_INVALID_MINUTES;
545 else if (pt->second < 0 || pt->second > 59)
546 result = PSP_TIME_INVALID_SECONDS;
547 else if (pt->microsecond >= 1000000UL)
548 result = PSP_TIME_INVALID_MICROSECONDS;
549 return hleLogSuccessI(SCERTC, result);
550 }
551
sceRtcSetTime_t(u32 datePtr,u32 time)552 static int sceRtcSetTime_t(u32 datePtr, u32 time) {
553 auto pt = PSPPointer<ScePspDateTime>::Create(datePtr);
554 if (!pt.IsValid())
555 return hleLogError(SCERTC, 1, "bad address");
556
557 __RtcTicksToPspTime(*pt, time * 1000000ULL + rtcMagicOffset);
558 return hleLogSuccessI(SCERTC, 0);
559 }
560
sceRtcSetTime64_t(u32 datePtr,u64 time)561 static int sceRtcSetTime64_t(u32 datePtr, u64 time) {
562 auto pt = PSPPointer<ScePspDateTime>::Create(datePtr);
563 if (!pt.IsValid())
564 return hleLogError(SCERTC, 1, "bad address");
565
566 __RtcTicksToPspTime(*pt, time * 1000000ULL + rtcMagicOffset);
567 return hleLogSuccessI(SCERTC, 0);
568 }
569
sceRtcGetTime_t(u32 datePtr,u32 timePtr)570 static int sceRtcGetTime_t(u32 datePtr, u32 timePtr) {
571 auto pt = PSPPointer<const ScePspDateTime>::Create(datePtr);
572 auto timep = PSPPointer<u32_le>::Create(timePtr);
573 if (!pt.IsValid() || !timep.IsValid())
574 return hleLogError(SCERTC, 1, "bad address");
575
576 *timep = (u32)((__RtcPspTimeToTicks(*pt) - rtcMagicOffset) / 1000000ULL);
577 return hleLogSuccessI(SCERTC, 0);
578 }
579
sceRtcGetTime64_t(u32 datePtr,u32 timePtr)580 static int sceRtcGetTime64_t(u32 datePtr, u32 timePtr) {
581 auto pt = PSPPointer<const ScePspDateTime>::Create(datePtr);
582 auto timep = PSPPointer<u64_le>::Create(timePtr);
583 if (!pt.IsValid() || !timep.IsValid())
584 return hleLogError(SCERTC, 1, "bad address");
585
586 *timep = (__RtcPspTimeToTicks(*pt) - rtcMagicOffset) / 1000000ULL;
587 return hleLogSuccessI(SCERTC, 0);
588 }
589
sceRtcSetDosTime(u32 datePtr,u32 dosTime)590 static int sceRtcSetDosTime(u32 datePtr, u32 dosTime) {
591 auto pt = PSPPointer<ScePspDateTime>::Create(datePtr);
592 if (!pt.IsValid())
593 return hleLogError(SCERTC, 1, "bad address");
594
595 int hms = dosTime & 0xFFFF;
596 int ymd = dosTime >> 16;
597
598 pt->year = 1980 + (ymd >> 9);
599 pt->month = (ymd >> 5) & 0xF;
600 pt->day = ymd & 0x1F;
601 pt->hour = hms >> 11;
602 pt->minute = (hms >> 5) & 0x3F;
603 pt->second = (hms << 1) & 0x3E;
604 pt->microsecond = 0;
605
606 return hleLogSuccessI(SCERTC, 0);
607 }
608
sceRtcGetDosTime(u32 datePtr,u32 dosTime)609 static int sceRtcGetDosTime(u32 datePtr, u32 dosTime) {
610 auto datep = PSPPointer<ScePspDateTime>::Create(datePtr);
611 auto dosp = PSPPointer<u32_le>::Create(dosTime);
612 if (!datep.IsValid() || !dosp.IsValid())
613 return hleLogError(SCERTC, -1, "bad address");
614
615 if (datep->year < 1980) {
616 *dosp = 0;
617 return hleLogWarning(SCERTC, -1, "invalid year");
618 } else if (datep->year >= 2108) {
619 *dosp = 0xFF9FBF7D;
620 return hleLogWarning(SCERTC, -1, "invalid year");
621 }
622
623 int year = ((datep->year - 1980) & 0x7F) << 9;
624 int month = (datep->month & 0xF) << 5;
625 int hour = (datep->hour & 0x1F) << 11;
626 int minute = (datep->minute & 0x3F) << 5;
627 int day = datep->day & 0x1F;
628 int second = (datep->second >> 1) & 0x1F;
629 int ymd = year | month | day;
630 int hms = hour | minute | second;
631
632 *dosp = (ymd << 16) | hms;
633 return hleLogSuccessI(SCERTC, 0);
634 }
635
sceRtcSetWin32FileTime(u32 datePtr,u64 win32Time)636 static int sceRtcSetWin32FileTime(u32 datePtr, u64 win32Time)
637 {
638 if (!Memory::IsValidAddress(datePtr))
639 {
640 ERROR_LOG_REPORT(SCERTC, "sceRtcSetWin32FileTime(%08x, %lld): invalid address", datePtr, win32Time);
641 return -1;
642 }
643
644 DEBUG_LOG(SCERTC, "sceRtcSetWin32FileTime(%08x, %lld)", datePtr, win32Time);
645
646 u64 ticks = (win32Time / 10) + rtcFiletimeOffset;
647 auto pspTime = PSPPointer<ScePspDateTime>::Create(datePtr);
648 __RtcTicksToPspTime(*pspTime, ticks);
649 return 0;
650 }
651
sceRtcGetWin32FileTime(u32 datePtr,u32 win32TimePtr)652 static int sceRtcGetWin32FileTime(u32 datePtr, u32 win32TimePtr)
653 {
654 if (!Memory::IsValidAddress(datePtr))
655 {
656 ERROR_LOG_REPORT(SCERTC, "sceRtcGetWin32FileTime(%08x, %08x): invalid address", datePtr, win32TimePtr);
657 return -1;
658 }
659
660 DEBUG_LOG(SCERTC, "sceRtcGetWin32FileTime(%08x, %08x)", datePtr, win32TimePtr);
661 if (!Memory::IsValidAddress(win32TimePtr))
662 return SCE_KERNEL_ERROR_INVALID_VALUE;
663
664 auto pspTime = PSPPointer<const ScePspDateTime>::Create(datePtr);
665 u64 result = __RtcPspTimeToTicks(*pspTime);
666
667 if (!__RtcValidatePspTime(*pspTime) || result < rtcFiletimeOffset)
668 {
669 Memory::Write_U64(0, win32TimePtr);
670 return SCE_KERNEL_ERROR_INVALID_VALUE;
671 }
672
673 Memory::Write_U64((result - rtcFiletimeOffset) * 10, win32TimePtr);
674 return 0;
675 }
676
sceRtcCompareTick(u32 tick1Ptr,u32 tick2Ptr)677 static int sceRtcCompareTick(u32 tick1Ptr, u32 tick2Ptr)
678 {
679 DEBUG_LOG(SCERTC, "sceRtcCompareTick(%d,%d)", tick1Ptr, tick2Ptr);
680 if (Memory::IsValidAddress(tick1Ptr) && Memory::IsValidAddress(tick2Ptr))
681 {
682 u64 tick1 = Memory::Read_U64(tick1Ptr);
683 u64 tick2 = Memory::Read_U64(tick2Ptr);
684 if (tick1 > tick2)
685 return 1;
686 if (tick1 < tick2)
687 return -1;
688 }
689 return 0;
690 }
691
sceRtcTickAddTicks(u32 destTickPtr,u32 srcTickPtr,u64 numTicks)692 static int sceRtcTickAddTicks(u32 destTickPtr, u32 srcTickPtr, u64 numTicks)
693 {
694 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
695 {
696 u64 srcTick = Memory::Read_U64(srcTickPtr);
697
698 srcTick += numTicks;
699 Memory::Write_U64(srcTick, destTickPtr);
700 }
701
702 DEBUG_LOG(SCERTC, "sceRtcTickAddTicks(%x,%x,%llu)", destTickPtr, srcTickPtr, numTicks);
703 return 0;
704 }
705
sceRtcTickAddMicroseconds(u32 destTickPtr,u32 srcTickPtr,u64 numMS)706 static int sceRtcTickAddMicroseconds(u32 destTickPtr,u32 srcTickPtr, u64 numMS)
707 {
708 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
709 {
710 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
711
712 srcTick += numMS;
713 Memory::Write_U64(srcTick, destTickPtr);
714 }
715
716 DEBUG_LOG(SCERTC, "sceRtcTickAddMicroseconds(%x,%x,%llu)", destTickPtr, srcTickPtr, numMS);
717 return 0;
718 }
719
sceRtcTickAddSeconds(u32 destTickPtr,u32 srcTickPtr,u64 numSecs)720 static int sceRtcTickAddSeconds(u32 destTickPtr, u32 srcTickPtr, u64 numSecs)
721 {
722 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
723 {
724 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
725
726 srcTick += numSecs * 1000000UL;
727 Memory::Write_U64(srcTick, destTickPtr);
728 }
729 DEBUG_LOG(SCERTC, "sceRtcTickAddSeconds(%x,%x,%llu)", destTickPtr, srcTickPtr, numSecs);
730 return 0;
731 }
732
sceRtcTickAddMinutes(u32 destTickPtr,u32 srcTickPtr,u64 numMins)733 static int sceRtcTickAddMinutes(u32 destTickPtr, u32 srcTickPtr, u64 numMins)
734 {
735 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
736 {
737 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
738
739 srcTick += numMins*60000000UL;
740 Memory::Write_U64(srcTick, destTickPtr);
741 }
742 DEBUG_LOG(SCERTC, "sceRtcTickAddMinutes(%x,%x,%llu)", destTickPtr, srcTickPtr, numMins);
743 return 0;
744 }
745
sceRtcTickAddHours(u32 destTickPtr,u32 srcTickPtr,int numHours)746 static int sceRtcTickAddHours(u32 destTickPtr, u32 srcTickPtr, int numHours)
747 {
748 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
749 {
750 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
751 srcTick += numHours * 3600ULL * 1000000ULL;
752 Memory::Write_U64(srcTick, destTickPtr);
753 }
754 DEBUG_LOG(SCERTC, "sceRtcTickAddMinutes(%d,%d,%d)", destTickPtr, srcTickPtr, numHours);
755 return 0;
756 }
757
sceRtcTickAddDays(u32 destTickPtr,u32 srcTickPtr,int numDays)758 static int sceRtcTickAddDays(u32 destTickPtr, u32 srcTickPtr, int numDays)
759 {
760 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
761 {
762 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
763
764 srcTick += numDays * 86400ULL * 1000000ULL;
765 Memory::Write_U64(srcTick, destTickPtr);
766 }
767 DEBUG_LOG(SCERTC, "sceRtcTickAddDays(%d,%d,%d)", destTickPtr, srcTickPtr, numDays);
768 return 0;
769 }
770
sceRtcTickAddWeeks(u32 destTickPtr,u32 srcTickPtr,int numWeeks)771 static int sceRtcTickAddWeeks(u32 destTickPtr, u32 srcTickPtr, int numWeeks)
772 {
773 if (Memory::IsValidAddress(destTickPtr) && Memory::IsValidAddress(srcTickPtr))
774 {
775 s64 srcTick = (s64)Memory::Read_U64(srcTickPtr);
776
777 srcTick += numWeeks * 7ULL * 86400ULL * 1000000ULL;
778 Memory::Write_U64(srcTick, destTickPtr);
779 }
780 DEBUG_LOG(SCERTC, "sceRtcTickAddWeeks(%d,%d,%d)", destTickPtr, srcTickPtr, numWeeks);
781 return 0;
782 }
783
sceRtcTickAddMonths(u32 destTickPtr,u32 srcTickPtr,int numMonths)784 static int sceRtcTickAddMonths(u32 destTickPtr, u32 srcTickPtr, int numMonths)
785 {
786 if (!Memory::IsValidAddress(destTickPtr) || !Memory::IsValidAddress(srcTickPtr))
787 {
788 WARN_LOG(SCERTC, "sceRtcTickAddMonths(%08x, %08x, %d): invalid address", destTickPtr, srcTickPtr, numMonths);
789 return -1;
790 }
791
792 u64 srcTick = Memory::Read_U64(srcTickPtr);
793
794 ScePspDateTime pt;
795 memset(&pt, 0, sizeof(pt));
796
797 __RtcTicksToPspTime(pt,srcTick);
798 pt.year += numMonths / 12;
799 pt.month += numMonths % 12;
800
801 if (pt.month < 1)
802 {
803 pt.month += 12;
804 pt.year--;
805 }
806 if (pt.month > 12)
807 {
808 pt.month -= 12;
809 pt.year++;
810 }
811
812 if (__RtcValidatePspTime(pt))
813 {
814 // Did we land on a year that isn't a leap year?
815 if (pt.month == 2 && pt.day == 29 && !__RtcIsLeapYear((s16)pt.year))
816 pt.day = 28;
817 Memory::Write_U64(__RtcPspTimeToTicks(pt), destTickPtr);
818 }
819
820 DEBUG_LOG(SCERTC, "sceRtcTickAddMonths(%08x, %08x = %lld, %d)", destTickPtr, srcTickPtr, srcTick, numMonths);
821 return 0;
822 }
823
sceRtcTickAddYears(u32 destTickPtr,u32 srcTickPtr,int numYears)824 static int sceRtcTickAddYears(u32 destTickPtr, u32 srcTickPtr, int numYears)
825 {
826 if (!Memory::IsValidAddress(destTickPtr) || !Memory::IsValidAddress(srcTickPtr))
827 {
828 WARN_LOG(SCERTC, "sceRtcTickAddYears(%08x, %08x, %d): invalid address", destTickPtr, srcTickPtr, numYears);
829 return -1;
830 }
831
832 u64 srcTick = Memory::Read_U64(srcTickPtr);
833
834 ScePspDateTime pt;
835 memset(&pt, 0, sizeof(pt));
836
837 __RtcTicksToPspTime(pt, srcTick);
838 pt.year += numYears;
839
840 if (__RtcValidatePspTime(pt))
841 {
842 // Did we land on a year that isn't a leap year?
843 if (pt.month == 2 && pt.day == 29 && !__RtcIsLeapYear((s16)pt.year))
844 pt.day = 28;
845 Memory::Write_U64(__RtcPspTimeToTicks(pt), destTickPtr);
846 }
847
848 DEBUG_LOG(SCERTC, "sceRtcTickAddYears(%08x, %08x = %lld, %d)", destTickPtr, srcTickPtr, srcTick, numYears);
849 return 0;
850 }
851
sceRtcParseDateTime(u32 destTickPtr,u32 dateStringPtr)852 static int sceRtcParseDateTime(u32 destTickPtr, u32 dateStringPtr)
853 {
854 ERROR_LOG_REPORT(SCERTC, "UNIMPL sceRtcParseDateTime(%d,%d)", destTickPtr, dateStringPtr);
855 return 0;
856 }
857
sceRtcGetLastAdjustedTime(u32 tickPtr)858 static int sceRtcGetLastAdjustedTime(u32 tickPtr)
859 {
860 if (Memory::IsValidAddress(tickPtr))
861 Memory::Write_U64(rtcLastAdjustedTicks, tickPtr);
862 DEBUG_LOG(SCERTC, "sceRtcGetLastAdjustedTime(%d)", tickPtr);
863 return 0;
864 }
865
sceRtcGetLastReincarnatedTime(u32 tickPtr)866 static int sceRtcGetLastReincarnatedTime(u32 tickPtr)
867 {
868 if (Memory::IsValidAddress(tickPtr))
869 Memory::Write_U64(rtcLastReincarnatedTicks, tickPtr);
870 DEBUG_LOG(SCERTC, "sceRtcGetLastReincarnatedTime(%d)", tickPtr);
871 return 0;
872 }
873
874 //Returns 0 on success, according to Project Diva 2nd jpcsptrace log
sceRtcSetAlarmTick(u32 unknown1,u32 unknown2)875 static int sceRtcSetAlarmTick(u32 unknown1, u32 unknown2)
876 {
877 ERROR_LOG_REPORT(SCERTC, "UNIMPL sceRtcSetAlarmTick(%x, %x)", unknown1, unknown2);
878 return 0;
879 }
880
__RtcFormatRFC2822(u32 outPtr,u32 srcTickPtr,int tz)881 static int __RtcFormatRFC2822(u32 outPtr, u32 srcTickPtr, int tz)
882 {
883 u64 srcTick = Memory::Read_U64(srcTickPtr);
884
885 ScePspDateTime pt;
886 memset(&pt, 0, sizeof(pt));
887
888 __RtcTicksToPspTime(pt, srcTick);
889
890 tm local;
891 __RtcPspTimeToTm(local, pt);
892 while (local.tm_year < 70)
893 local.tm_year += 400;
894 while (local.tm_year >= 470)
895 local.tm_year -= 400;
896 local.tm_min += tz;
897 rtc_timegm(&local);
898
899 char *out = (char *)Memory::GetPointer(outPtr);
900 char *end = out + 32;
901 out += strftime(out, end - out, "%a, %d %b ", &local);
902 out += snprintf(out, end - out, "%04d", pt.year);
903 out += strftime(out, end - out, " %H:%M:%S ", &local);
904 if (tz < 0)
905 out += snprintf(out, end - out, "-%02d%02d", -tz / 60, -tz % 60);
906 else
907 out += snprintf(out, end - out, "+%02d%02d", tz / 60, tz % 60);
908
909 return 0;
910 }
911
__RtcFormatRFC3339(u32 outPtr,u32 srcTickPtr,int tz)912 static int __RtcFormatRFC3339(u32 outPtr, u32 srcTickPtr, int tz)
913 {
914 u64 srcTick = Memory::Read_U64(srcTickPtr);
915
916 ScePspDateTime pt;
917 memset(&pt, 0, sizeof(pt));
918
919 __RtcTicksToPspTime(pt, srcTick);
920
921 tm local;
922 __RtcPspTimeToTm(local, pt);
923 while (local.tm_year < 70)
924 local.tm_year += 400;
925 while (local.tm_year >= 470)
926 local.tm_year -= 400;
927 local.tm_min += tz;
928 rtc_timegm(&local);
929
930 char *out = (char *)Memory::GetPointer(outPtr);
931 char *end = out + 32;
932 out += snprintf(out, end - out, "%04d", pt.year);
933 out += strftime(out, end - out, "-%m-%dT%H:%M:%S.00", &local);
934 if (tz == 0)
935 out += snprintf(out, end - out, "Z");
936 else if (tz < 0)
937 out += snprintf(out, end - out, "-%02d:%02d", -tz / 60, -tz % 60);
938 else
939 out += snprintf(out, end - out, "+%02d:%02d", tz / 60, tz % 60);
940
941 return 0;
942 }
943
sceRtcFormatRFC2822(u32 outPtr,u32 srcTickPtr,int tz)944 static int sceRtcFormatRFC2822(u32 outPtr, u32 srcTickPtr, int tz)
945 {
946 if (!Memory::IsValidAddress(outPtr) || !Memory::IsValidAddress(srcTickPtr))
947 {
948 // TODO: Not well tested.
949 ERROR_LOG(SCERTC, "sceRtcFormatRFC2822(%08x, %08x, %d): invalid address", outPtr, srcTickPtr, tz);
950 return -1;
951 }
952
953 DEBUG_LOG(SCERTC, "sceRtcFormatRFC2822(%08x, %08x, %d)", outPtr, srcTickPtr, tz);
954 return __RtcFormatRFC2822(outPtr, srcTickPtr, tz);
955 }
956
sceRtcFormatRFC2822LocalTime(u32 outPtr,u32 srcTickPtr)957 static int sceRtcFormatRFC2822LocalTime(u32 outPtr, u32 srcTickPtr)
958 {
959 if (!Memory::IsValidAddress(outPtr) || !Memory::IsValidAddress(srcTickPtr))
960 {
961 // TODO: Not well tested.
962 ERROR_LOG(SCERTC, "sceRtcFormatRFC2822LocalTime(%08x, %08x): invalid address", outPtr, srcTickPtr);
963 return -1;
964 }
965
966 int tz_seconds;
967 #ifdef _WIN32
968 long timezone_val;
969 _get_timezone(&timezone_val);
970 tz_seconds = -timezone_val;
971 #elif !defined(_AIX) && !defined(__sgi) && !defined(__hpux) && !defined(HAVE_LIBNX)
972 time_t timezone = 0;
973 tm *time = localtime(&timezone);
974 tz_seconds = time->tm_gmtoff;
975 #endif
976
977 DEBUG_LOG(SCERTC, "sceRtcFormatRFC2822LocalTime(%08x, %08x)", outPtr, srcTickPtr);
978 return __RtcFormatRFC2822(outPtr, srcTickPtr, tz_seconds / 60);
979 }
980
sceRtcFormatRFC3339(u32 outPtr,u32 srcTickPtr,int tz)981 static int sceRtcFormatRFC3339(u32 outPtr, u32 srcTickPtr, int tz)
982 {
983 if (!Memory::IsValidAddress(outPtr) || !Memory::IsValidAddress(srcTickPtr))
984 {
985 // TODO: Not well tested.
986 ERROR_LOG(SCERTC, "sceRtcFormatRFC3339(%08x, %08x, %d): invalid address", outPtr, srcTickPtr, tz);
987 return -1;
988 }
989
990 DEBUG_LOG(SCERTC, "sceRtcFormatRFC3339(%08x, %08x, %d)", outPtr, srcTickPtr, tz);
991 return __RtcFormatRFC3339(outPtr, srcTickPtr, tz);
992 }
993
sceRtcFormatRFC3339LocalTime(u32 outPtr,u32 srcTickPtr)994 static int sceRtcFormatRFC3339LocalTime(u32 outPtr, u32 srcTickPtr)
995 {
996 if (!Memory::IsValidAddress(outPtr) || !Memory::IsValidAddress(srcTickPtr))
997 {
998 // TODO: Not well tested.
999 ERROR_LOG(SCERTC, "sceRtcFormatRFC3339LocalTime(%08x, %08x): invalid address", outPtr, srcTickPtr);
1000 return -1;
1001 }
1002
1003 int tz_seconds;
1004 #ifdef _WIN32
1005 long timezone_val;
1006 _get_timezone(&timezone_val);
1007 tz_seconds = -timezone_val;
1008 #elif !defined(_AIX) && !defined(__sgi) && !defined(__hpux) && !defined(HAVE_LIBNX)
1009 time_t timezone = 0;
1010 tm *time = localtime(&timezone);
1011 tz_seconds = time->tm_gmtoff;
1012 #endif
1013
1014 DEBUG_LOG(SCERTC, "sceRtcFormatRFC3339LocalTime(%08x, %08x)", outPtr, srcTickPtr);
1015 return __RtcFormatRFC3339(outPtr, srcTickPtr, tz_seconds / 60);
1016 }
1017
1018 const HLEFunction sceRtc[] =
1019 {
1020 {0XC41C2853, &WrapU_V<sceRtcGetTickResolution>, "sceRtcGetTickResolution", 'x', "" },
1021 {0X3F7AD767, &WrapU_U<sceRtcGetCurrentTick>, "sceRtcGetCurrentTick", 'x', "x" },
1022 {0X011F03C1, &WrapU64_V<sceRtcGetAccumulativeTime>, "sceRtcGetAccumulativeTime", 'X', "" },
1023 {0X029CA3B3, &WrapU64_V<sceRtcGetAccumulativeTime>, "sceRtcGetAccumlativeTime", 'X', "" },
1024 {0X4CFA57B0, &WrapU_UI<sceRtcGetCurrentClock>, "sceRtcGetCurrentClock", 'i', "xi" },
1025 {0XE7C27D1B, &WrapU_U<sceRtcGetCurrentClockLocalTime>, "sceRtcGetCurrentClockLocalTime", 'i', "x" },
1026 {0X34885E0D, &WrapI_UU<sceRtcConvertUtcToLocalTime>, "sceRtcConvertUtcToLocalTime", 'i', "xx" },
1027 {0X779242A2, &WrapI_UU<sceRtcConvertLocalTimeToUTC>, "sceRtcConvertLocalTimeToUTC", 'i', "xx" },
1028 {0X42307A17, &WrapU_U<sceRtcIsLeapYear>, "sceRtcIsLeapYear", 'x', "x" },
1029 {0X05EF322C, &WrapU_UU<sceRtcGetDaysInMonth>, "sceRtcGetDaysInMonth", 'x', "xx" },
1030 {0X57726BC1, &WrapU_UUU<sceRtcGetDayOfWeek>, "sceRtcGetDayOfWeek", 'x', "xxx"},
1031 {0X4B1B5E82, &WrapI_U<sceRtcCheckValid>, "sceRtcCheckValid", 'i', "x" },
1032 {0X3A807CC8, &WrapI_UU<sceRtcSetTime_t>, "sceRtcSetTime_t", 'i', "xx" },
1033 {0X27C4594C, &WrapI_UU<sceRtcGetTime_t>, "sceRtcGetTime_t", 'i', "xp" },
1034 {0XF006F264, &WrapI_UU<sceRtcSetDosTime>, "sceRtcSetDosTime", 'i', "xx" },
1035 {0X36075567, &WrapI_UU<sceRtcGetDosTime>, "sceRtcGetDosTime", 'i', "xp" },
1036 {0X7ACE4C04, &WrapI_UU64<sceRtcSetWin32FileTime>, "sceRtcSetWin32FileTime", 'i', "xX" },
1037 {0XCF561893, &WrapI_UU<sceRtcGetWin32FileTime>, "sceRtcGetWin32FileTime", 'i', "xx" },
1038 {0X7ED29E40, &WrapU_UU<sceRtcSetTick>, "sceRtcSetTick", 'x', "xP" },
1039 {0X6FF40ACC, &WrapU_UU<sceRtcGetTick>, "sceRtcGetTick", 'i', "xP" },
1040 {0X9ED0AE87, &WrapI_UU<sceRtcCompareTick>, "sceRtcCompareTick", 'i', "xx" },
1041 {0X44F45E05, &WrapI_UUU64<sceRtcTickAddTicks>, "sceRtcTickAddTicks", 'i', "xxX"},
1042 {0X26D25A5D, &WrapI_UUU64<sceRtcTickAddMicroseconds>, "sceRtcTickAddMicroseconds", 'i', "xxX"},
1043 {0XF2A4AFE5, &WrapI_UUU64<sceRtcTickAddSeconds>, "sceRtcTickAddSeconds", 'i', "xxX"},
1044 {0XE6605BCA, &WrapI_UUU64<sceRtcTickAddMinutes>, "sceRtcTickAddMinutes", 'i', "xxX"},
1045 {0X26D7A24A, &WrapI_UUI<sceRtcTickAddHours>, "sceRtcTickAddHours", 'i', "xxi"},
1046 {0XE51B4B7A, &WrapI_UUI<sceRtcTickAddDays>, "sceRtcTickAddDays", 'i', "xxi"},
1047 {0XCF3A2CA8, &WrapI_UUI<sceRtcTickAddWeeks>, "sceRtcTickAddWeeks", 'i', "xxi"},
1048 {0XDBF74F1B, &WrapI_UUI<sceRtcTickAddMonths>, "sceRtcTickAddMonths", 'i', "xxi"},
1049 {0X42842C77, &WrapI_UUI<sceRtcTickAddYears>, "sceRtcTickAddYears", 'i', "xxi"},
1050 {0XC663B3B9, &WrapI_UUI<sceRtcFormatRFC2822>, "sceRtcFormatRFC2822", 'i', "xxi"},
1051 {0X7DE6711B, &WrapI_UU<sceRtcFormatRFC2822LocalTime>, "sceRtcFormatRFC2822LocalTime", 'i', "xx" },
1052 {0X0498FB3C, &WrapI_UUI<sceRtcFormatRFC3339>, "sceRtcFormatRFC3339", 'i', "xxi"},
1053 {0X27F98543, &WrapI_UU<sceRtcFormatRFC3339LocalTime>, "sceRtcFormatRFC3339LocalTime", 'i', "xx" },
1054 {0XDFBC5F16, &WrapI_UU<sceRtcParseDateTime>, "sceRtcParseDateTime", 'i', "xx" },
1055 {0X28E1E988, nullptr, "sceRtcParseRFC3339", '?', "" },
1056 {0XE1C93E47, &WrapI_UU<sceRtcGetTime64_t>, "sceRtcGetTime64_t", 'i', "xP" },
1057 {0X1909C99B, &WrapI_UU64<sceRtcSetTime64_t>, "sceRtcSetTime64_t", 'i', "xX" },
1058 {0X62685E98, &WrapI_U<sceRtcGetLastAdjustedTime>, "sceRtcGetLastAdjustedTime", 'i', "x" },
1059 {0X203CEB0D, &WrapI_U<sceRtcGetLastReincarnatedTime>, "sceRtcGetLastReincarnatedTime", 'i', "x" },
1060 {0X7D1FBED3, &WrapI_UU<sceRtcSetAlarmTick>, "sceRtcSetAlarmTick", 'i', "xx" },
1061 {0XF5FCC995, nullptr, "sceRtcGetCurrentNetworkTick", '?', "" },
1062 {0X81FCDA34, nullptr, "sceRtcIsAlarmed", '?', "" },
1063 {0XFB3B18CD, nullptr, "sceRtcRegisterCallback", '?', "" },
1064 {0X6A676D2D, nullptr, "sceRtcUnregisterCallback", '?', "" },
1065 {0XC2DDBEB5, nullptr, "sceRtcGetAlarmTick", '?', "" },
1066 };
1067
Register_sceRtc()1068 void Register_sceRtc()
1069 {
1070 RegisterModule("sceRtc", ARRAY_SIZE(sceRtc), sceRtc);
1071 }
1072