#include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "cellRtc.h" #include "Emu/Cell/lv2/sys_time.h" #include "Emu/Cell/lv2/sys_memory.h" #include "Emu/Cell/lv2/sys_ss.h" LOG_CHANNEL(cellRtc); // clang-format off template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto error) { switch (error) { STR_CASE(CELL_RTC_ERROR_NOT_INITIALIZED); STR_CASE(CELL_RTC_ERROR_INVALID_POINTER); STR_CASE(CELL_RTC_ERROR_INVALID_VALUE); STR_CASE(CELL_RTC_ERROR_INVALID_ARG); STR_CASE(CELL_RTC_ERROR_NOT_SUPPORTED); STR_CASE(CELL_RTC_ERROR_NO_CLOCK); STR_CASE(CELL_RTC_ERROR_BAD_PARSE); STR_CASE(CELL_RTC_ERROR_INVALID_YEAR); STR_CASE(CELL_RTC_ERROR_INVALID_MONTH); STR_CASE(CELL_RTC_ERROR_INVALID_DAY); STR_CASE(CELL_RTC_ERROR_INVALID_HOUR); STR_CASE(CELL_RTC_ERROR_INVALID_MINUTE); STR_CASE(CELL_RTC_ERROR_INVALID_SECOND); STR_CASE(CELL_RTC_ERROR_INVALID_MICROSECOND); } return unknown; }); } // clang-format on // Grabbed from JPCSP // This is the # of microseconds between January 1, 0001 and January 1, 1970. constexpr u64 RTC_MAGIC_OFFSET = 62135596800000000ULL; // This is the # of microseconds between January 1, 0001 and January 1, 1601 (for Win32 FILETIME.) constexpr u64 RTC_FILETIME_OFFSET = 50491123200000000ULL; constexpr u64 EPOCH_AS_FILETIME = 116444736000000000ULL; // Also stores leap year constexpr u8 DAYS_IN_MONTH[24] = {0x1F, 0x1C, 0x1F, 0x1E, 0x1F, 0x1E, 0x1F, 0x1F, 0x1E, 0x1F, 0x1E, 0x1F, 0x1F, 0x1D, 0x1F, 0x1E, 0x1F, 0x1E, 0x1F, 0x1F, 0x1E, 0x1F, 0x1E, 0x1F}; constexpr char WEEKDAY_NAMES[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; // 4 as terminator constexpr char MONTH_NAMES[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; // 4 as terminator s64 convertToUNIXTime(u16 seconds, u16 minutes, u16 hours, u16 days, s32 years) { return s64{seconds} + s64{minutes} * 60 + s64{hours} * 3600 + s64{days} * 86400 + s64{s64{years} - 70} * 31536000 + s64{(years - 69) / 4} * 86400 - s64{(years - 1) / 100} * 86400 + s64{(years + 299) / 400} * 86400; } u64 convertToWin32FILETIME(u16 seconds, u16 minutes, u16 hours, u16 days, s32 years) { s64 unixtime = convertToUNIXTime(seconds, minutes, hours, days, years); u64 win32time = static_cast(unixtime) * 10000000 + EPOCH_AS_FILETIME; u64 win32filetime = win32time | win32time >> 32; return win32filetime; } // TODO make all this use tick resolution and figure out the magic numbers // TODO MULHDU instruction returns high 64 bit of the 128 bit result of two 64-bit regs multiplication // need to be replaced with utils::mulh64, utils::umulh64, utils::div128, utils::udiv128 in needed places // cellRtcSetTick / cellRtcSetWin32FileTime / cellRtcSetCurrentTick / cellRtcSetCurrentSecureTick // TODO undo optimized division // Internal helper functions in cellRtc error_code set_secure_rtc_time(u64 time) { return sys_ss_secure_rtc(0x3003 /* SET_TIME */, time, 0, 0); // TODO } error_code get_secure_rtc_time(u64 unk1, u64 unk2, u64 unk3) { return sys_ss_secure_rtc(0x3002 /* GET_TIME */, unk1, unk2, unk3); // TODO } // End of internal helper functions // Helper methods static bool is_leap_year(u32 year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } // End of helper methods error_code cellRtcGetCurrentTick(vm::ptr pTick) { cellRtc.todo("cellRtcGetCurrentTick(pTick=*0x%x)", pTick); if (!vm::check_addr(pTick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var sec; vm::var nsec; error_code ret = sys_time_get_current_time(sec, nsec); if (ret != CELL_OK) { return ret; } pTick->tick = *nsec / 1000 + *sec * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET; return CELL_OK; } error_code cellRtcGetCurrentClock(vm::ptr pClock, s32 iTimeZone) { cellRtc.todo("cellRtcGetCurrentClock(pClock=*0x%x, iTimeZone=%d)", pClock, iTimeZone); if (!vm::check_addr(pClock.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; if (!vm::check_addr(tick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var sec; vm::var nsec; error_code ret = sys_time_get_current_time(sec, nsec); if (ret != CELL_OK) { return ret; } tick->tick = *nsec / 1000 + *sec * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET; cellRtcTickAddMinutes(tick, tick, iTimeZone); cellRtcSetTick(pClock, tick); return CELL_OK; } error_code cellRtcGetCurrentClockLocalTime(vm::ptr pClock) { cellRtc.todo("cellRtcGetCurrentClockLocalTime(pClock=*0x%x)", pClock); if (!vm::check_addr(pClock.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var timezone; vm::var summertime; error_code ret = sys_time_get_timezone(timezone, summertime); if (ret != CELL_OK) { return ret; } if (!vm::check_addr(pClock.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; if (!vm::check_addr(tick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var sec; vm::var nsec; ret = sys_time_get_current_time(sec, nsec); if (ret != CELL_OK) { return ret; } tick->tick = *nsec / 1000 + *sec * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET; cellRtcTickAddMinutes(tick, tick, s64{*timezone} + s64{*summertime}); cellRtcSetTick(pClock, tick); return CELL_OK; } error_code cellRtcFormatRfc2822(vm::ptr pszDateTime, vm::cptr pUtc, s32 iTimeZone) { cellRtc.todo("cellRtcFormatRfc2822(pszDateTime=*0x%x, pUtc=*0x%x, time_zone=%d)", pszDateTime, pUtc, iTimeZone); if (!vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pUtc.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var rtc_tick; if (pUtc->tick == 0ULL) { cellRtcGetCurrentTick(rtc_tick); } else { rtc_tick->tick = pUtc->tick; } vm::var date_time; cellRtcTickAddMinutes(rtc_tick, rtc_tick, iTimeZone); cellRtcSetTick(date_time, rtc_tick); error_code ret = cellRtcCheckValid(date_time); if (ret != CELL_OK) { return ret; } s32 tzone = iTimeZone; s32 weekdayIdx = cellRtcGetDayOfWeek(date_time->year, date_time->month, date_time->day); // Day name *pszDateTime = WEEKDAY_NAMES[weekdayIdx][0]; pszDateTime[1] = WEEKDAY_NAMES[weekdayIdx][1]; pszDateTime[2] = WEEKDAY_NAMES[weekdayIdx][2]; pszDateTime[3] = ','; pszDateTime[4] = ' '; // Day number if (date_time->day < 10) { pszDateTime[5] = '0'; pszDateTime[6] = date_time->day + '0'; } else { pszDateTime[5] = (date_time->day / 10) + '0'; pszDateTime[6] = (date_time->day - ((date_time->day / 10 << 1) + (date_time->day / 10 << 3))) + '0'; } pszDateTime[7] = ' '; // month name pszDateTime[8] = MONTH_NAMES[date_time->month - 1][0]; pszDateTime[9] = MONTH_NAMES[date_time->month - 1][1]; pszDateTime[10] = MONTH_NAMES[date_time->month - 1][2]; pszDateTime[0xb] = ' '; // year std::array yearDigits{}; snprintf(yearDigits.data(), yearDigits.size(), "%04hi", u16{date_time->year}); pszDateTime[0xc] = yearDigits[0]; pszDateTime[0xd] = yearDigits[1]; pszDateTime[0xe] = yearDigits[2]; pszDateTime[0xf] = yearDigits[3]; pszDateTime[0x10] = ' '; // Hours std::array hourDigits{}; snprintf(hourDigits.data(), hourDigits.size(), "%02hi", u16{date_time->hour}); pszDateTime[0x11] = hourDigits[0]; pszDateTime[0x12] = hourDigits[1]; pszDateTime[0x13] = ':'; // Minutes std::array minDigits{}; snprintf(minDigits.data(), minDigits.size(), "%02hi", u16{date_time->minute}); pszDateTime[0x14] = minDigits[0]; pszDateTime[0x15] = minDigits[1]; pszDateTime[0x16] = ':'; // Seconds std::array secDigits{}; snprintf(secDigits.data(), secDigits.size(), "%02hi", u16{date_time->second}); pszDateTime[0x17] = secDigits[0]; pszDateTime[0x18] = secDigits[1]; pszDateTime[0x19] = ' '; // Timezone -/+ if (iTimeZone < 0) { tzone = -tzone; pszDateTime[0x1a] = '-'; } else { pszDateTime[0x1a] = '+'; } // Timezone - matches lle result. u32 unk_1 = tzone >> 31; u32 unk_2 = (tzone / 0x3c + unk_1) - unk_1; tzone -= (unk_2 << 6) - (unk_2 << 2); u32 unk_3 = (unk_2 / 10 + (unk_2 >> 0x1f)) - (unk_2 >> 31); u32 unk_4 = (tzone / 10 + unk_1) - unk_1; pszDateTime[0x1b] = unk_3 + (unk_3 / 10) * -10 + '0'; pszDateTime[0x1c] = (unk_2 - ((unk_3 << 1) + (unk_3 << 3))) + '0'; pszDateTime[0x1d] = unk_4 + (unk_4 / 10) * -10 + '0'; pszDateTime[0x1e] = (tzone - ((unk_4 << 1) + (unk_4 << 3))) + '0'; pszDateTime[0x1f] = '\0'; return CELL_OK; } error_code cellRtcFormatRfc2822LocalTime(vm::ptr pszDateTime, vm::cptr pUtc) { cellRtc.todo("cellRtcFormatRfc2822LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc); if (!vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pUtc.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var timezone; vm::var summertime; error_code ret = sys_time_get_timezone(timezone, summertime); if (ret != CELL_OK) { return ret; } return cellRtcFormatRfc2822(pszDateTime, pUtc, *timezone + *summertime); } error_code cellRtcFormatRfc3339(vm::ptr pszDateTime, vm::cptr pUtc, s32 iTimeZone) { cellRtc.todo("cellRtcFormatRfc3339(pszDateTime=*0x%x, pUtc=*0x%x, iTimeZone=%d)", pszDateTime, pUtc, iTimeZone); if (!vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pUtc.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var rtc_tick; if (pUtc->tick == 0ULL) { cellRtcGetCurrentTick(rtc_tick); } else { rtc_tick->tick = pUtc->tick; } vm::var date_time; cellRtcTickAddMinutes(rtc_tick, rtc_tick, iTimeZone); cellRtcSetTick(date_time, rtc_tick); error_code ret = cellRtcCheckValid(date_time); if (ret != CELL_OK) { return ret; } s32 tzone = iTimeZone; // Year - XXXX-04-13T10:56:31.35+66:40 std::array yearDigits{}; snprintf(yearDigits.data(), yearDigits.size(), "%04hi", u16{date_time->year}); pszDateTime[0x0] = yearDigits[0]; pszDateTime[0x1] = yearDigits[1]; pszDateTime[0x2] = yearDigits[2]; pszDateTime[0x3] = yearDigits[3]; pszDateTime[0x4] = '-'; // Month - 2020-XX-13T10:56:31.35+66:40 std::array monthDigits{}; snprintf(monthDigits.data(), monthDigits.size(), "%02hi", u16{date_time->month}); pszDateTime[0x5] = monthDigits[0]; pszDateTime[0x6] = monthDigits[1]; pszDateTime[0x7] = '-'; // Day - 2020-04-XXT10:56:31.35+66:40 std::array dayDigits{}; snprintf(dayDigits.data(), dayDigits.size(), "%02hi", u16{date_time->day}); pszDateTime[0x8] = dayDigits[0]; pszDateTime[0x9] = dayDigits[1]; pszDateTime[0xa] = 'T'; // Hours - 2020-04-13TXX:56:31.35+66:40 std::array hourDigits{}; snprintf(hourDigits.data(), hourDigits.size(), "%02hi", u16{date_time->hour}); pszDateTime[0xb] = hourDigits[0]; pszDateTime[0xc] = hourDigits[1]; pszDateTime[0xd] = ':'; // Minutes - 2020-04-13T10:XX:31.35+66:40 std::array minDigits{}; snprintf(minDigits.data(), minDigits.size(), "%02hi", u16{date_time->minute}); pszDateTime[0xe] = minDigits[0]; pszDateTime[0xf] = minDigits[1]; pszDateTime[0x10] = ':'; // Seconds - 2020-04-13T10:56:XX.35+66:40 std::array secDigits{}; snprintf(secDigits.data(), secDigits.size(), "%02hi", u16{date_time->second}); pszDateTime[0x11] = secDigits[0]; pszDateTime[0x12] = secDigits[1]; pszDateTime[0x13] = '.'; // Microseconds - 2020-04-13T10:56:31.XX+66:40 std::array microDigits{}; snprintf(microDigits.data(), microDigits.size(), "%02u", u32{date_time->microsecond}); pszDateTime[0x14] = microDigits[0]; pszDateTime[0x15] = microDigits[1]; if (iTimeZone == 0) { pszDateTime[0x16] = 'Z'; pszDateTime[0x17] = '\0'; } else { if (iTimeZone < 0) { tzone = -tzone; pszDateTime[0x16] = '-'; } else { pszDateTime[0x16] = '+'; } u32 uVar1 = tzone >> 0x1f; u32 lVar9 = (tzone / 0x3c + uVar1) - uVar1; tzone -= (lVar9 << 6) - (lVar9 << 2); uVar1 = tzone >> 0x1f; u32 lVar11 = (lVar9 / 10 + (lVar9 >> 0x1f)) - (lVar9 >> 0x1f); u32 lVar8 = (tzone / 10 + uVar1) - uVar1; pszDateTime[0x17] = lVar11 + (lVar11 / 10) * -10 + '0'; pszDateTime[0x18] = (lVar9 - ((lVar11 << 1) + (lVar11 << 3))) + '0'; pszDateTime[0x19] = ':'; pszDateTime[0x1a] = lVar8 + (lVar8 / 10) * -10 + '0'; pszDateTime[0x1b] = (tzone - ((lVar8 << 1) + (lVar8 << 3))) + '0'; pszDateTime[0x1c] = '\0'; } return CELL_OK; } error_code cellRtcFormatRfc3339LocalTime(vm::ptr pszDateTime, vm::cptr pUtc) { cellRtc.todo("cellRtcFormatRfc3339LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc); if (!vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pUtc.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var timezone; vm::var summertime; error_code ret = sys_time_get_timezone(timezone, summertime); if (ret != CELL_OK) { return ret; } return cellRtcFormatRfc3339(pszDateTime, pUtc, *timezone + *summertime); } error_code cellRtcParseRfc3339(vm::ptr pUtc, vm::cptr pszDateTime); /* Takes a RFC2822 / RFC3339 / asctime String, and converts it to a CellRtcTick */ error_code cellRtcParseDateTime(vm::ptr pUtc, vm::cptr pszDateTime) { cellRtc.todo("cellRtcParseDateTime(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime); if (!vm::check_addr(pUtc.addr()) || !vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } u32 pos = 0; while (std::isblank(pszDateTime[pos])) { pos++; } if (std::isdigit(pszDateTime[pos]) && std::isdigit(pszDateTime[pos + 1]) && std::isdigit(pszDateTime[pos + 2]) && std::isdigit(pszDateTime[pos + 3])) { return cellRtcParseRfc3339(pUtc, pszDateTime + pos); } // Below code kinda works /* std::tm t = {}; std::string tz; vm::var date_time; // Not done like the library does it in the least.. s32 timezoneMins; std::istringstream iss(std::string(pszDateTime.get_ptr(), strlen(pszDateTime.get_ptr()))); iss >> std::get_time(&t, "%a, %d %b %Y %H:%M:%S") >> tz; // rfc2822 if (!iss.fail()) { // Looks wrong, works if (tz[0] == '+') { timezoneMins = -std::stoi(tz.substr(1)); } else { timezoneMins = +std::stoi(tz.substr(1)); } } else { iss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S") >> tz; // rfc3339 2020-04-03T13:23:30.30Z if (!iss.fail()) { // TODO timezone } else { // TODO asctime iss >> std::get_time(&t, "%a %b %d %H:%M:%S %Y");//Mon Apr 6 21:58:35 2020 if (!iss.fail()) { // TODO timezone } else { return CELL_RTC_ERROR_BAD_PARSE; } } } cellRtc.todo("heh year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d, tz: %s, tz_d: %d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, tz, timezoneMins); date_time->year = t.tm_year + 1900; date_time->month = t.tm_mon + 1; date_time->day = t.tm_mday; date_time->hour = t.tm_hour; date_time->minute = t.tm_min; date_time->second = t.tm_sec; date_time->microsecond = 0; cellRtcGetTick(date_time, pUtc); cellRtcTickAddMinutes(pUtc, pUtc, timezoneMins);*/ return CELL_OK; } // Rfc3339: 1995-12-03T13:23:00.00Z error_code cellRtcParseRfc3339(vm::ptr pUtc, vm::cptr pszDateTime) { cellRtc.notice("cellRtcParseRfc3339(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime); if (!vm::check_addr(pUtc.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pszDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; const auto digit = [](char c) -> s32 { return c - '0'; }; // Year: XXXX-12-03T13:23:00.00Z if (std::isdigit(pszDateTime[0]) && std::isdigit(pszDateTime[1]) && std::isdigit(pszDateTime[2]) && std::isdigit(pszDateTime[3])) { date_time->year = digit(pszDateTime[0]) * 1000 + digit(pszDateTime[1]) * 100 + digit(pszDateTime[2]) * 10 + digit(pszDateTime[3]); } else { date_time->year = 0xffff; } if (pszDateTime[4] != '-') { return CELL_RTC_ERROR_INVALID_YEAR; } // Month: 1995-XX-03T13:23:00.00Z if (std::isdigit(pszDateTime[5]) && std::isdigit(pszDateTime[6])) { date_time->month = digit(pszDateTime[5]) * 10 + digit(pszDateTime[6]); } else { date_time->month = 0xffff; } if (pszDateTime[7] != '-') { return CELL_RTC_ERROR_INVALID_MONTH; } // Day: 1995-12-XXT13:23:00.00Z if (std::isdigit(pszDateTime[8]) && std::isdigit(pszDateTime[9])) { date_time->day = digit(pszDateTime[8]) * 10 + digit(pszDateTime[9]); } else { date_time->day = 0xffff; } if (pszDateTime[10] != 'T' && pszDateTime[10] != 't') { return CELL_RTC_ERROR_INVALID_DAY; } // Hour: 1995-12-03TXX:23:00.00Z if (std::isdigit(pszDateTime[11]) && std::isdigit(pszDateTime[12])) { date_time->hour = digit(pszDateTime[11]) * 10 + digit(pszDateTime[12]); } else { date_time->hour = 0xffff; } if (pszDateTime[13] != ':') { return CELL_RTC_ERROR_INVALID_HOUR; } // Minute: 1995-12-03T13:XX:00.00Z if (std::isdigit(pszDateTime[14]) && std::isdigit(pszDateTime[15])) { date_time->minute = digit(pszDateTime[14]) * 10 + digit(pszDateTime[15]); } else { date_time->minute = 0xffff; } if (pszDateTime[16] != ':') { return CELL_RTC_ERROR_INVALID_MINUTE; } // Second: 1995-12-03T13:23:XX.00Z if (std::isdigit(pszDateTime[17]) && std::isdigit(pszDateTime[18])) { date_time->second = digit(pszDateTime[17]) * 10 + digit(pszDateTime[18]); } else { date_time->second = 0xffff; } // Microsecond: 1995-12-03T13:23:00.XXZ date_time->microsecond = 0; u32 pos = 19; if (pszDateTime[pos] == '.') { u32 mul = 100000; for (char c = pszDateTime[++pos]; std::isdigit(c); c = pszDateTime[++pos]) { date_time->microsecond += digit(c) * mul; mul /= 10; } } const char sign = pszDateTime[pos]; if (sign != 'Z' && sign != 'z' && sign != '+' && sign != '-') { return CELL_RTC_ERROR_BAD_PARSE; } s64 minutes_to_add = 0; // Time offset: 1995-12-03T13:23:00.00+02:30 if (sign == '+' || sign == '-') { if (!std::isdigit(pszDateTime[pos + 1]) || !std::isdigit(pszDateTime[pos + 2]) || pszDateTime[pos + 3] != ':' || !std::isdigit(pszDateTime[pos + 4]) || !std::isdigit(pszDateTime[pos + 5])) { return CELL_RTC_ERROR_BAD_PARSE; } // Time offset (hours): 1995-12-03T13:23:00.00+XX:30 const s32 hours = digit(pszDateTime[pos + 1]) * 10 + digit(pszDateTime[pos + 2]); // Time offset (minutes): 1995-12-03T13:23:00.00+02:XX const s32 minutes = digit(pszDateTime[pos + 4]) * 10 + digit(pszDateTime[pos + 5]); minutes_to_add = hours * 60 + minutes; if (sign == '+') { minutes_to_add = -minutes_to_add; } } cellRtcGetTick(date_time, pUtc); cellRtcTickAddMinutes(pUtc, pUtc, minutes_to_add); return CELL_OK; } error_code cellRtcGetTick(vm::cptr pTime, vm::ptr pTick) { cellRtc.todo("cellRtcGetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick); if (!vm::check_addr(pTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pTime->year >= 10000 || pTime->year == 0) { return CELL_RTC_ERROR_INVALID_VALUE; } if (!pTick) { return CELL_RTC_ERROR_INVALID_POINTER; } s64 days_in_years = ((((pTime->year * 365ULL) + ((pTime->year + 3) / 4)) - ((pTime->year + 99) / 100)) + ((pTime->year + 399) / 400) + -366); // 1-12 if (1 < pTime->month) { u32 month_idx = pTime->month - 1; u32 monthIdx_adjusted = is_leap_year(pTime->year) * 12; do { days_in_years += DAYS_IN_MONTH[monthIdx_adjusted]; month_idx -= 1; monthIdx_adjusted += 1; } while (month_idx != 0); } pTick->tick = ((((days_in_years + (pTime->day - 1)) * 0x18 + pTime->hour) * 0x3c + pTime->minute) * 0x3c + pTime->second) * cellRtcGetTickResolution() + pTime->microsecond; return CELL_OK; } CellRtcDateTime tick_to_date_time(u64 tick) { /* u32 microseconds = round((pTick->tick % 1000000ULL)); u16 seconds = round((pTick->tick / (1000000ULL)) % 60); u16 minutes = round((pTick->tick / (60ULL * 1000000ULL)) % 60); u16 hours = round((pTick->tick / (60ULL * 60ULL * 1000000ULL)) % 24); u64 days_tmp = round((pTick->tick / (24ULL * 60ULL * 60ULL * 1000000ULL)));*/ const u32 microseconds = (tick % 1000000ULL); const u16 seconds = (tick / (1000000ULL)) % 60; const u16 minutes = (tick / (60ULL * 1000000ULL)) % 60; const u16 hours = (tick / (60ULL * 60ULL * 1000000ULL)) % 24; u64 days_tmp = (tick / (24ULL * 60ULL * 60ULL * 1000000ULL)); u16 months = 1; u16 years = 1; bool exit_while = false; do { const bool leap = is_leap_year(years); for (u32 m = 0; m < 12; m++) { const u8 daysinmonth = DAYS_IN_MONTH[m + (leap * 12)]; if (days_tmp >= daysinmonth) { months++; days_tmp -= daysinmonth; } else { exit_while = true; break; } if (m == 11) { months = 1; years++; } } } while (!exit_while); CellRtcDateTime date_time{ .year = years, .month = months, .day = ::narrow(days_tmp + 1), .hour = hours, .minute = minutes, .second = seconds, .microsecond = microseconds }; return date_time; } u64 date_time_to_tick(CellRtcDateTime date_time) { const auto get_days_in_year = [](u16 year, u16 months) -> u64 { const bool leap = is_leap_year(year); u64 days = 0; for (u16 m = 0; m < months; m++) { days += DAYS_IN_MONTH[m + (leap * 12)]; } return days; }; u64 days = 0; if (date_time.day > 1) { // We only need the whole days before "this" day days += date_time.day - 1ULL; } if (date_time.month > 1) { // We only need the whole months before "this" month days += get_days_in_year(date_time.year, date_time.month - 1ULL); } if (date_time.year > 1) { // We only need the whole years before "this" year // NOTE: tick_to_date_time starts counting with year 1, so count [1,n[ instead of [0,n-1[ for (u16 year = 1; year < date_time.year; year++) { days += get_days_in_year(year, 12); } } u64 tick = date_time.microsecond + u64{date_time.second} * 1000000ULL + u64{date_time.minute} * 60ULL * 1000000ULL + u64{date_time.hour} * 60ULL * 60ULL * 1000000ULL + days * 24ULL * 60ULL * 60ULL * 1000000ULL; return tick; } error_code cellRtcSetTick(vm::ptr pTime, vm::cptr pTick) { cellRtc.todo("cellRtcSetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick); if (!vm::check_addr(pTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } *pTime = tick_to_date_time(pTick->tick); return CELL_OK; } error_code cellRtcTickAddTicks(vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddTicks(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd; return CELL_OK; } error_code cellRtcTickAddMicroseconds(vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddMicroseconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd; return CELL_OK; } error_code cellRtcTickAddSeconds(vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddSeconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddMinutes(vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddMinutes(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd * 60 * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddHours(vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddHours(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddDays(vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddDays(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * 24ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddWeeks(vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddWeeks(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * 24ULL * 7ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddMonths(vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddMonths(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; cellRtcSetTick(date_time, pTick1); // Not pretty, but works s64 total_months = (date_time->year * 12ULL) + date_time->month + iAdd + -1; s32 total_months_s32 = ::narrow(total_months); u32 unk_1 = total_months_s32 >> 0x1f; u64 unk_2 = ((total_months_s32 / 6 + unk_1) >> 1) - unk_1; u32 unk_3 = ::narrow(unk_2); unk_1 = unk_3 & 0xffff; u64 unk_4 = (total_months - ((u64{unk_3} << 4) - (unk_3 << 2))) + 1; if (((unk_2 & 0xffff) == 0) || ((unk_3 = unk_4 & 0xffff, (unk_4 & 0xffff) == 0 || unk_3 > 12))) { return CELL_RTC_ERROR_INVALID_ARG; } u32 uVar1 = ((s64{unk_1} * 0x51eb851f) >> 0x20); // Leap year check u32 month_idx; if ((unk_1 == (uVar1 >> 7) * 400) || ((unk_1 != (uVar1 >> 5) * 100 && ((unk_2 & 3) == 0)))) { month_idx = unk_3 + 11; } else { month_idx = unk_3 - 1; } u32 month_days = DAYS_IN_MONTH[month_idx]; if (month_days < date_time->day) { date_time->day = month_days; } date_time->month = ::narrow(unk_4); date_time->year = ::narrow(unk_2); cellRtcGetTick(date_time, pTick0); return CELL_OK; } error_code cellRtcTickAddYears(vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddYears(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); if (!vm::check_addr(pTick0.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick1.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; cellRtcSetTick(date_time, pTick1); u64 total_years = iAdd + date_time->year; u32 unk_1 = total_years & 0xffff; if (unk_1 == 0 || date_time->month == 0 || date_time->month > 12) { return CELL_RTC_ERROR_INVALID_ARG; } u32 uVar1 = ((s64{unk_1} * 0x51eb851f) >> 0x20); // Leap year check u32 month_idx; if ((unk_1 == (uVar1 >> 7) * 400) || ((unk_1 != (uVar1 >> 5) * 100 && ((total_years & 3) == 0)))) { month_idx = date_time->month + 0xb; } else { month_idx = date_time->month - 1; } u32 month_days = DAYS_IN_MONTH[month_idx]; if (month_days < date_time->day) { date_time->day = month_days; } date_time->year = ::narrow(total_years); cellRtcGetTick(date_time, pTick0); return CELL_OK; } error_code cellRtcConvertUtcToLocalTime(vm::cptr pUtc, vm::ptr pLocalTime) { cellRtc.todo("cellRtcConvertUtcToLocalTime(pUtc=*0x%x, pLocalTime=*0x%x)", pUtc, pLocalTime); vm::var timezone; vm::var summertime; error_code ret = sys_time_get_timezone(timezone, summertime); if (-1 < ret) { ret = cellRtcTickAddMinutes(pLocalTime, pUtc, s64{*timezone} + s64{*summertime}); } return ret; } error_code cellRtcConvertLocalTimeToUtc(vm::cptr pLocalTime, vm::ptr pUtc) { cellRtc.todo("cellRtcConvertLocalTimeToUtc(pLocalTime=*0x%x, pUtc=*0x%x)", pLocalTime, pUtc); vm::var timezone; vm::var summertime; error_code ret = sys_time_get_timezone(timezone, summertime); if (-1 < ret) { ret = cellRtcTickAddMinutes(pUtc, pLocalTime, -(s64{*timezone} + s64{*summertime})); } return ret; } error_code cellRtcGetCurrentSecureTick(vm::ptr tick) { cellRtc.todo("cellRtcGetCurrentSecureTick(*0x%x)", tick); if (!vm::check_addr(tick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } // TODO tick->tick = 0xe01d003a63a000; return CELL_OK; } error_code cellRtcGetDosTime(vm::cptr pDateTime, vm::ptr puiDosTime) { cellRtc.todo("cellRtcGetDosTime(pDateTime=*0x%x, puiDosTime=*0x%x)", pDateTime, puiDosTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(puiDosTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pDateTime->year < 1980) { if (puiDosTime) { *puiDosTime = 0; return -1; } } else if (pDateTime->year >= 2108) { if (puiDosTime) { *puiDosTime = 0xff9fbf7d; // kHighDosTime return -1; } } else { if (!puiDosTime) { return CELL_OK; } s32 year = ((pDateTime->year - 1980) & 0x7F) << 9; s32 month = ((pDateTime->month) & 0xF) << 5; s32 hour = ((pDateTime->hour) & 0x1F) << 11; s32 minute = ((pDateTime->minute) & 0x3F) << 5; s32 day = (pDateTime->day) & 0x1F; s32 second = ((pDateTime->second) >> 1) & 0x1F; s32 ymd = year | month | day; s32 hms = hour | minute | second; *puiDosTime = (ymd << 16) | hms; return CELL_OK; } return -1; } error_code cellRtcGetSystemTime(vm::cptr pDateTime, vm::ptr pTick) { cellRtc.todo("cellRtcGetSystemTime(pDateTime=*0x%x, pTick=*0x%x)", pDateTime, pTick); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pTick.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } error_code ret; vm::var tick; cellRtcGetTick(pDateTime, tick); if (tick->tick < 63082281600000000) // Max time { ret = CELL_RTC_ERROR_INVALID_VALUE; if (pTick) { pTick->tick = 0; } } else { if (tick->tick < 0xeb5325dc3ec23f) // 66238041600999999 { ret = CELL_OK; if (pTick) { pTick->tick = (tick->tick + 0xff1fe2ffc59c6000) / cellRtcGetTickResolution(); } } else { ret = CELL_RTC_ERROR_INVALID_VALUE; if (pTick) { pTick->tick = 0xbc19137f; // 1 day? } } } return ret; } error_code cellRtcGetTime_t(vm::cptr pDateTime, vm::ptr piTime) { cellRtc.todo("cellRtcGetTime_t(pDateTime=*0x%x, piTime=*0x%x)", pDateTime, piTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(piTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; cellRtcGetTick(pDateTime, tick); error_code ret; if (tick->tick < RTC_MAGIC_OFFSET) { ret = CELL_RTC_ERROR_INVALID_VALUE; if (piTime) { *piTime = 0; } } else { ret = CELL_OK; if (piTime) { *piTime = (tick->tick + 0xff23400100d44000) / cellRtcGetTickResolution(); } } return ret; } error_code cellRtcGetWin32FileTime(vm::cptr pDateTime, vm::ptr pulWin32FileTime) { cellRtc.todo("cellRtcGetWin32FileTime(pDateTime=*0x%x, pulWin32FileTime=*0x%x)", pDateTime, pulWin32FileTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } if (!vm::check_addr(pulWin32FileTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; cellRtcGetTick(pDateTime, tick); error_code ret; if (tick->tick < RTC_FILETIME_OFFSET) { ret = CELL_RTC_ERROR_INVALID_VALUE; if (pulWin32FileTime) { *pulWin32FileTime = 0; } } else { ret = CELL_OK; if (pulWin32FileTime) { *pulWin32FileTime = tick->tick * 10 + 0xf8fe31e8dd890000; } } return ret; } error_code cellRtcSetCurrentSecureTick(vm::ptr pTick) { cellRtc.todo("cellRtcSetCurrentSecureTick(pTick=*0x%x)", pTick); if (!pTick) { return CELL_RTC_ERROR_INVALID_POINTER; } u64 uVar1 = pTick->tick + 0xff1fe2ffc59c6000; if (uVar1 >= 0xb3625a1cbe000) // 3155760000000000 { return CELL_RTC_ERROR_INVALID_VALUE; } return set_secure_rtc_time(uVar1 / cellRtcGetTickResolution()); } error_code cellRtcSetCurrentTick(vm::cptr pTick) { cellRtc.todo("cellRtcSetCurrentTick(pTick=*0x%x)", pTick); if (!pTick) { return CELL_RTC_ERROR_INVALID_POINTER; } //u64 tmp = pTick->tick + 0xff23400100d44000; if (!(0xdcbffeff2bbfff < pTick->tick)) { return CELL_RTC_ERROR_INVALID_ARG; } // TODO syscall not implemented /* u64 tmp2 = sys_time_get_system_time(tmp / cellRtcGetTickResolution(), (tmp % cellRtcGetTickResolution()) * 1000); return (tmp2 & (tmp2 | tmp2 - 1) >> 0x1f); */ return CELL_OK; } error_code cellRtcSetConf(s64 unk1, s64 unk2, u32 timezone, u32 summertime) { cellRtc.todo("cellRtcSetConf(unk1=0x%x, unk2=0x%x, timezone=%d, summertime=%d)", unk1, unk2, timezone, summertime); // Seems the first 2 args are ignored :| // TODO Syscall not implemented // return sys_time_set_timezone(timezone, summertime); return CELL_OK; } error_code cellRtcSetDosTime(vm::ptr pDateTime, u32 uiDosTime) { cellRtc.todo("cellRtcSetDosTime(pDateTime=*0x%x, uiDosTime=0x%x)", pDateTime, uiDosTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } s32 hms = uiDosTime & 0xffff; s32 ymd = uiDosTime >> 16; pDateTime->year = (ymd >> 9) + 1980; pDateTime->month = (ymd >> 5) & 0xf; pDateTime->day = ymd & 0x1f; pDateTime->hour = (hms >> 11); pDateTime->minute = (hms >> 5) & 0x3f; pDateTime->second = (hms << 1) & 0x3e; pDateTime->microsecond = 0; return CELL_OK; } u32 cellRtcGetTickResolution() { // Amount of ticks in a second return 1000000; } error_code cellRtcSetTime_t(vm::ptr pDateTime, u64 iTime) { cellRtc.todo("cellRtcSetTime_t(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = iTime * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET; cellRtcSetTick(pDateTime, tick); return CELL_OK; } error_code cellRtcSetSystemTime(vm::ptr pDateTime, u64 iTime) { cellRtc.todo("cellRtcSetSystemTime(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = iTime * cellRtcGetTickResolution() + 0xe01d003a63a000; return cellRtcSetTick(pDateTime, tick); } error_code cellRtcSetWin32FileTime(vm::ptr pDateTime, u64 ulWin32FileTime) { cellRtc.todo("cellRtcSetWin32FileTime(pDateTime=*0x%x, ulWin32FileTime=0x%llx)", pDateTime, ulWin32FileTime); if (!vm::check_addr(pDateTime.addr())) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = ulWin32FileTime / 10 + RTC_FILETIME_OFFSET; return cellRtcSetTick(pDateTime, tick); } error_code cellRtcIsLeapYear(s32 year) { cellRtc.todo("cellRtcIsLeapYear(year=%d)", year); if (year < 1) { return CELL_RTC_ERROR_INVALID_ARG; } return not_an_error(is_leap_year(year)); } error_code cellRtcGetDaysInMonth(s32 year, s32 month) { cellRtc.todo("cellRtcGetDaysInMonth(year=%d, month=%d)", year, month); if ((year <= 0) || (month <= 0) || (month > 12)) { return CELL_RTC_ERROR_INVALID_ARG; } if (is_leap_year(year)) { return not_an_error(DAYS_IN_MONTH[month + 11]); } return not_an_error(DAYS_IN_MONTH[month - 1]); } error_code cellRtcGetDayOfWeek(s32 year, s32 month, s32 day) { cellRtc.trace("cellRtcGetDayOfWeek(year=%d, month=%d, day=%d)", year, month, day); if (month == 1 || month == 2) { year--; month += 12; } return not_an_error(((month * 0xd + 8) / 5 + ((year + (year >> 2) + (year < 0 && (year & 3U) != 0)) - year / 100) + year / 400 + day) % 7); } error_code cellRtcCheckValid(vm::cptr pTime) { cellRtc.todo("cellRtcCheckValid(pTime=*0x%x)", pTime); cellRtc.todo("cellRtcCheckValid year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d, microsecond: %d\n", pTime->year, pTime->month, pTime->day, pTime->hour, pTime->minute, pTime->second, pTime->microsecond); if (pTime->year == 0 || pTime->year >= 10000) { return CELL_RTC_ERROR_INVALID_YEAR; } if (pTime->month < 1 || pTime->month > 12) { return CELL_RTC_ERROR_INVALID_MONTH; } s32 month_idx; if (is_leap_year(pTime->year)) { // Leap year check month_idx = pTime->month + 11; } else { month_idx = pTime->month - 1; } if (pTime->day == 0 || pTime->day > DAYS_IN_MONTH[month_idx]) { return CELL_RTC_ERROR_INVALID_DAY; } if (pTime->hour >= 24) { return CELL_RTC_ERROR_INVALID_HOUR; } if (pTime->minute >= 60) { return CELL_RTC_ERROR_INVALID_MINUTE; } if (pTime->second >= 60) { return CELL_RTC_ERROR_INVALID_SECOND; } if (pTime->microsecond >= cellRtcGetTickResolution()) { return CELL_RTC_ERROR_INVALID_MICROSECOND; } return CELL_OK; } error_code cellRtcCompareTick(vm::cptr pTick0, vm::cptr pTick1) { cellRtc.todo("cellRtcCompareTick(pTick0=*0x%x, pTick1=*0x%x)", pTick0, pTick1); s32 ret = -1; if (pTick1->tick <= pTick0->tick) { ret = pTick1->tick < pTick0->tick; } return not_an_error(ret); } DECLARE(ppu_module_manager::cellRtc) ("cellRtc", []() { REG_FUNC(cellRtc, cellRtcGetCurrentTick); REG_FUNC(cellRtc, cellRtcGetCurrentClock); REG_FUNC(cellRtc, cellRtcGetCurrentClockLocalTime); REG_FUNC(cellRtc, cellRtcFormatRfc2822); REG_FUNC(cellRtc, cellRtcFormatRfc2822LocalTime); REG_FUNC(cellRtc, cellRtcFormatRfc3339); REG_FUNC(cellRtc, cellRtcFormatRfc3339LocalTime); REG_FUNC(cellRtc, cellRtcParseDateTime); REG_FUNC(cellRtc, cellRtcParseRfc3339); REG_FUNC(cellRtc, cellRtcGetTick); REG_FUNC(cellRtc, cellRtcSetTick); REG_FUNC(cellRtc, cellRtcTickAddTicks); REG_FUNC(cellRtc, cellRtcTickAddMicroseconds); REG_FUNC(cellRtc, cellRtcTickAddSeconds); REG_FUNC(cellRtc, cellRtcTickAddMinutes); REG_FUNC(cellRtc, cellRtcTickAddHours); REG_FUNC(cellRtc, cellRtcTickAddDays); REG_FUNC(cellRtc, cellRtcTickAddWeeks); REG_FUNC(cellRtc, cellRtcTickAddMonths); REG_FUNC(cellRtc, cellRtcTickAddYears); REG_FUNC(cellRtc, cellRtcConvertUtcToLocalTime); REG_FUNC(cellRtc, cellRtcConvertLocalTimeToUtc); REG_FUNC(cellRtc, cellRtcGetCurrentSecureTick); REG_FUNC(cellRtc, cellRtcGetDosTime); REG_FUNC(cellRtc, cellRtcGetTickResolution); REG_FUNC(cellRtc, cellRtcGetSystemTime); REG_FUNC(cellRtc, cellRtcGetTime_t); REG_FUNC(cellRtc, cellRtcGetWin32FileTime); REG_FUNC(cellRtc, cellRtcSetConf); REG_FUNC(cellRtc, cellRtcSetCurrentSecureTick); REG_FUNC(cellRtc, cellRtcSetCurrentTick); REG_FUNC(cellRtc, cellRtcSetDosTime); REG_FUNC(cellRtc, cellRtcSetTime_t); REG_FUNC(cellRtc, cellRtcSetSystemTime); REG_FUNC(cellRtc, cellRtcSetWin32FileTime); REG_FUNC(cellRtc, cellRtcIsLeapYear); REG_FUNC(cellRtc, cellRtcGetDaysInMonth); REG_FUNC(cellRtc, cellRtcGetDayOfWeek); REG_FUNC(cellRtc, cellRtcCheckValid); REG_FUNC(cellRtc, cellRtcCompareTick); });