#include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "cellRtc.h" #include "cellos/sys_time.h" #include "cellos/sys_memory.h" #include "cellos/sys_ss.h" LOG_CHANNEL(cellRtc); 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; }); } static inline char ascii(u8 num) { return num + '0'; } static inline s8 digit(char c) { return c - '0'; } static bool is_leap_year(u32 year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } error_code cellRtcGetCurrentTick(ppu_thread& ppu, vm::ptr pTick) { cellRtc.trace("cellRtcGetCurrentTick(pTick=*0x%x)", pTick); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick.addr(), page_attr) != CELL_OK) { 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(ppu_thread& ppu, vm::ptr pClock, s32 iTimeZone) { cellRtc.trace("cellRtcGetCurrentClock(pClock=*0x%x, iTimeZone=%d)", pClock, iTimeZone); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pClock.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; if (sys_memory_get_page_attribute(ppu, tick.addr(), page_attr) != CELL_OK) { 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(ppu, tick, tick, iTimeZone); cellRtcSetTick(ppu, pClock, tick); return CELL_OK; } error_code cellRtcGetCurrentClockLocalTime(ppu_thread& ppu, vm::ptr pClock) { cellRtc.trace("cellRtcGetCurrentClockLocalTime(pClock=*0x%x)", pClock); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pClock.addr(), page_attr) != CELL_OK) { 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 (sys_memory_get_page_attribute(ppu, pClock.addr(), page_attr) != CELL_OK) // Should always evaluate to false, already checked above { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; if (sys_memory_get_page_attribute(ppu, tick.addr(), page_attr) != CELL_OK) { 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(ppu, tick, tick, *timezone + *summertime); cellRtcSetTick(ppu, pClock, tick); return CELL_OK; } error_code cellRtcFormatRfc2822(ppu_thread& ppu, vm::ptr pszDateTime, vm::cptr pUtc, s32 iTimeZone) { cellRtc.notice("cellRtcFormatRfc2822(pszDateTime=*0x%x, pUtc=*0x%x, iTimeZone=%d)", pszDateTime, pUtc, iTimeZone); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var rtc_tick; if (!pUtc) // Should always evaluate to false, nullptr was already checked above { cellRtcGetCurrentTick(ppu, rtc_tick); } else { rtc_tick->tick = pUtc->tick; } vm::var date_time; cellRtcTickAddMinutes(ppu, rtc_tick, rtc_tick, iTimeZone); cellRtcSetTick(ppu, date_time, rtc_tick); error_code ret = cellRtcCheckValid(date_time); if (ret != CELL_OK) { return ret; } s32 weekdayIdx = cellRtcGetDayOfWeek(date_time->year, date_time->month, date_time->day); // Day name pszDateTime[0] = std::toupper(WEEKDAY_NAMES[weekdayIdx][0]); pszDateTime[1] = WEEKDAY_NAMES[weekdayIdx][1]; pszDateTime[2] = WEEKDAY_NAMES[weekdayIdx][2]; pszDateTime[3] = ','; pszDateTime[4] = ' '; // Day number pszDateTime[5] = ascii(date_time->day / 10); pszDateTime[6] = ascii(date_time->day % 10); pszDateTime[7] = ' '; // month name pszDateTime[8] = std::toupper(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 pszDateTime[0xc] = ascii(date_time->year / 1000); pszDateTime[0xd] = ascii(date_time->year / 100 % 10); pszDateTime[0xe] = ascii(date_time->year / 10 % 10); pszDateTime[0xf] = ascii(date_time->year % 10); pszDateTime[0x10] = ' '; // Hours pszDateTime[0x11] = ascii(date_time->hour / 10); pszDateTime[0x12] = ascii(date_time->hour % 10); pszDateTime[0x13] = ':'; // Minutes pszDateTime[0x14] = ascii(date_time->minute / 10); pszDateTime[0x15] = ascii(date_time->minute % 10); pszDateTime[0x16] = ':'; // Seconds pszDateTime[0x17] = ascii(date_time->second / 10); pszDateTime[0x18] = ascii(date_time->second % 10); pszDateTime[0x19] = ' '; // Timezone -/+ if (iTimeZone < 0) { iTimeZone = -iTimeZone; pszDateTime[0x1a] = '-'; } else { pszDateTime[0x1a] = '+'; } const u32 time_zone_hours = iTimeZone / 60 % 100; const u32 time_zone_minutes = iTimeZone % 60; pszDateTime[0x1b] = ascii(time_zone_hours / 10); pszDateTime[0x1c] = ascii(time_zone_hours % 10); pszDateTime[0x1d] = ascii(time_zone_minutes / 10); pszDateTime[0x1e] = ascii(time_zone_minutes % 10); pszDateTime[0x1f] = '\0'; return CELL_OK; } error_code cellRtcFormatRfc2822LocalTime(ppu_thread& ppu, vm::ptr pszDateTime, vm::cptr pUtc) { cellRtc.notice("cellRtcFormatRfc2822LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK) { 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(ppu, pszDateTime, pUtc, *timezone + *summertime); } error_code cellRtcFormatRfc3339(ppu_thread& ppu, vm::ptr pszDateTime, vm::cptr pUtc, s32 iTimeZone) { cellRtc.notice("cellRtcFormatRfc3339(pszDateTime=*0x%x, pUtc=*0x%x, iTimeZone=%d)", pszDateTime, pUtc, iTimeZone); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var rtc_tick; if (!pUtc) // Should always evaluate to false, nullptr was already checked above { cellRtcGetCurrentTick(ppu, rtc_tick); } else { rtc_tick->tick = pUtc->tick; } vm::var date_time; cellRtcTickAddMinutes(ppu, rtc_tick, rtc_tick, iTimeZone); cellRtcSetTick(ppu, date_time, rtc_tick); error_code ret = cellRtcCheckValid(date_time); if (ret != CELL_OK) { return ret; } // Year - XXXX-04-13T10:56:31.35+66:40 pszDateTime[0x0] = ascii(date_time->year / 1000); pszDateTime[0x1] = ascii(date_time->year / 100 % 10); pszDateTime[0x2] = ascii(date_time->year / 10 % 10); pszDateTime[0x3] = ascii(date_time->year % 10); pszDateTime[0x4] = '-'; // Month - 2020-XX-13T10:56:31.35+66:40 pszDateTime[0x5] = ascii(date_time->month / 10); pszDateTime[0x6] = ascii(date_time->month % 10); pszDateTime[0x7] = '-'; // Day - 2020-04-XXT10:56:31.35+66:40 pszDateTime[0x8] = ascii(date_time->day / 10); pszDateTime[0x9] = ascii(date_time->day % 10); pszDateTime[0xa] = 'T'; // Hours - 2020-04-13TXX:56:31.35+66:40 pszDateTime[0xb] = ascii(date_time->hour / 10); pszDateTime[0xc] = ascii(date_time->hour % 10); pszDateTime[0xd] = ':'; // Minutes - 2020-04-13T10:XX:31.35+66:40 pszDateTime[0xe] = ascii(date_time->minute / 10); pszDateTime[0xf] = ascii(date_time->minute % 10); pszDateTime[0x10] = ':'; // Seconds - 2020-04-13T10:56:XX.35+66:40 pszDateTime[0x11] = ascii(date_time->second / 10); pszDateTime[0x12] = ascii(date_time->second % 10); pszDateTime[0x13] = '.'; // Hundredths of a second - 2020-04-13T10:56:31.XX+66:40 pszDateTime[0x14] = ascii(date_time->microsecond / 100'000); pszDateTime[0x15] = ascii(date_time->microsecond / 10'000 % 10); // Time zone - 'Z' for UTC if (iTimeZone == 0) { pszDateTime[0x16] = 'Z'; pszDateTime[0x17] = '\0'; } // Time zone - ±hh:mm else { if (iTimeZone < 0) { iTimeZone = -iTimeZone; pszDateTime[0x16] = '-'; } else { pszDateTime[0x16] = '+'; } const u32 time_zone_hours = iTimeZone / 60 % 100; const u32 time_zone_minutes = iTimeZone % 60; pszDateTime[0x17] = ascii(time_zone_hours / 10); pszDateTime[0x18] = ascii(time_zone_hours % 10); pszDateTime[0x19] = ':'; pszDateTime[0x1a] = ascii(time_zone_minutes / 10); pszDateTime[0x1b] = ascii(time_zone_minutes % 10); pszDateTime[0x1c] = '\0'; } return CELL_OK; } error_code cellRtcFormatRfc3339LocalTime(ppu_thread& ppu, vm::ptr pszDateTime, vm::cptr pUtc) { cellRtc.notice("cellRtcFormatRfc3339LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK) { 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(ppu, pszDateTime, pUtc, *timezone + *summertime); } u16 rtcParseComponent(vm::cptr pszDateTime, u32& pos, char delimiter, const char* component_name) { if (delimiter != 0) { if (pszDateTime[pos] != delimiter) { cellRtc.error("rtcParseComponent(): failed to parse %s: invalid or missing delimiter", component_name); return umax; } pos++; } if (!std::isdigit(pszDateTime[pos])) { cellRtc.error("rtcParseComponent(): failed to parse %s: ASCII value 0x%x at position %d is not a digit", component_name, pszDateTime[pos + 1], pos); return umax; } u16 ret = digit(pszDateTime[pos]); pos++; if (std::isdigit(pszDateTime[pos])) { ret = ret * 10 + digit(pszDateTime[pos]); pos++; } return ret; } template u8 rtcParseName(vm::cptr pszDateTime, u32& pos, const std::array& names, bool allow_short_name = true) { for (u8 name_idx = 0; name_idx < names.size(); name_idx++) { const u32 name_length = static_cast(names[name_idx].length()); u32 ch_idx = 0; while (ch_idx < name_length && std::tolower(pszDateTime[pos + ch_idx]) == names[name_idx][ch_idx]) // Not case sensitive { ch_idx++; } if (ch_idx == name_length) // Full name matched { pos += name_length; return name_idx; } if (allow_short_name && ch_idx >= 3) // Short name matched { pos += 3; // Only increment by 3, even if more letters were matched return name_idx; } } return size; } error_code rtcParseRfc2822(ppu_thread& ppu, vm::ptr pUtc, vm::cptr pszDateTime, u32 pos) { // Day: "X" or "XX" const u16 day = rtcParseComponent(pszDateTime, pos, 0, "day"); if (day == umax) { return CELL_RTC_ERROR_BAD_PARSE; } // Mandatory space or hyphen if (pszDateTime[pos] != ' ' && pszDateTime[pos] != '-') { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): invalid or missing delimiter after day"}; } pos++; // Month: at least the first three letters const u16 month = rtcParseName(pszDateTime, pos, MONTH_NAMES) + 1; if (month > MONTH_NAMES.size()) // No match { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): failed to parse month: string at position %d doesn't match any name", pos}; } // Mandatory space or hyphen if (pszDateTime[pos] != ' ' && pszDateTime[pos] != '-') { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): invalid or missing delimiter after month"}; } pos++; // Year: "XX" or "XXXX" u16 year = 0; if (!std::isdigit(pszDateTime[pos]) || !std::isdigit(pszDateTime[pos + 1])) { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): failed to parse year: one of the first two ASCII values 0x%x, 0x%x at position %d is not a digit", pszDateTime[pos], pszDateTime[pos + 1], pos}; } if (!std::isdigit(pszDateTime[pos + 2]) || !std::isdigit(pszDateTime[pos + 3])) { year = digit(pszDateTime[pos]) * 10 + digit(pszDateTime[pos + 1]); year += (year < 50) ? 2000 : 1900; pos += 2; } else { year = digit(pszDateTime[pos]) * 1000 + digit(pszDateTime[pos + 1]) * 100 + digit(pszDateTime[pos + 2]) * 10 + digit(pszDateTime[pos + 3]); pos += 4; } // Hour: " X" or " XX" const u16 hour = rtcParseComponent(pszDateTime, pos, ' ', "hour"); if (hour == umax) { return CELL_RTC_ERROR_BAD_PARSE; } if (hour > 25) // LLE uses 25 { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): failed to parse hour: hour greater than 25"}; } // Minute: ":X" or ":XX" const u16 minute = rtcParseComponent(pszDateTime, pos, ':', "minute"); if (minute == umax) { return CELL_RTC_ERROR_BAD_PARSE; } // Second, optional: ":X" or ":XX" // The string can't end with '\0' here, there must a space before it u16 second = 0; if (pszDateTime[pos] != ' ') { second = rtcParseComponent(pszDateTime, pos, ':', "second"); if (second == umax) { return CELL_RTC_ERROR_BAD_PARSE; } } else { // If there are no seconds in the string, time zone requires two preceding spaces to be properly parsed pos++; } // Time zone, optional, error if there is no valid time zone after the space s32 time_zone = 0; if (pszDateTime[pos] == ' ') { pos++; if (pszDateTime[pos] == '+' || pszDateTime[pos] == '-') { // "±hhmm" if (std::isdigit(pszDateTime[pos + 1]) && std::isdigit(pszDateTime[pos + 2]) && std::isdigit(pszDateTime[pos + 3]) && std::isdigit(pszDateTime[pos + 4])) { const s32 time_zone_hhmm = digit(pszDateTime[pos + 1]) * 1000 + digit(pszDateTime[pos + 2]) * 100 + digit(pszDateTime[pos + 3]) * 10 + digit(pszDateTime[pos + 4]); time_zone = time_zone_hhmm / 100 * 60 + time_zone_hhmm % 60; // LLE uses % 60 instead of % 100 } else { // No error, LLE does this for some reason time_zone = -1; } if (pszDateTime[pos] == '-') { time_zone = -time_zone; } } else if (pszDateTime[pos] != 'U' && pszDateTime[pos + 1] != 'T') // Case sensitive, should be || but LLE uses && { // "GMT", "EST", "EDT", etc. const u32 time_zone_idx = rtcParseName(pszDateTime, pos, TIME_ZONE_NAMES, false); if (time_zone_idx < TIME_ZONE_NAMES.size()) { time_zone = TIME_ZONE_VALUES[time_zone_idx] * 30; } else { // Military time zones // "A", "B", "C", ..., not case sensitive // These are all off by one ("A" should be UTC+01:00, "B" should be UTC+02:00, etc.) const char letter = std::toupper(pszDateTime[pos]); if (letter >= 'A' && letter <= 'M' && letter != 'J') { time_zone = (letter - 'A') * 60; } else if (letter >= 'N' && letter <= 'Y') { time_zone = ('N' - letter) * 60; } else if (letter != 'Z') { return {CELL_RTC_ERROR_BAD_PARSE, "rtcParseRfc2822(): failed to parse time zone"}; } } } } const vm::var date_time{{year, month, day, hour, minute, second, 0}}; cellRtcGetTick(ppu, date_time, pUtc); cellRtcTickAddMinutes(ppu, pUtc, pUtc, -time_zone); // The time zone value needs to be subtracted return CELL_OK; } error_code cellRtcParseRfc3339(ppu_thread& ppu, vm::ptr pUtc, vm::cptr pszDateTime); /* Takes a RFC2822 / RFC3339 / asctime String, and converts it to a CellRtcTick */ error_code cellRtcParseDateTime(ppu_thread& ppu, vm::ptr pUtc, vm::cptr pszDateTime) { cellRtc.notice("cellRtcParseDateTime(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK || sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { 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(ppu, pUtc, pszDateTime + pos); } // Day of the week: at least the first three letters if (rtcParseName(pszDateTime, pos, WEEKDAY_NAMES) == WEEKDAY_NAMES.size()) // No match { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): failed to parse day of the week: string at position %d doesn't match any name", pos}; } // Optional comma if (pszDateTime[pos] == ',') { pos++; } // Skip spaces and tabs while (std::isblank(pszDateTime[pos])) { pos++; } // Month: at least the first three letters const u16 month = rtcParseName(pszDateTime, pos, MONTH_NAMES) + 1; if (month > MONTH_NAMES.size()) // No match { cellRtc.notice("cellRtcParseDateTime(): string uses RFC 2822 format"); return rtcParseRfc2822(ppu, pUtc, pszDateTime, pos); } // Mandatory space if (pszDateTime[pos] != ' ') { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): no space after month"}; } pos++; // Day: " X", "XX" or "X" // There may be a second space before day u16 day = 0; if (pszDateTime[pos] == ' ') { pos++; // Due to using a signed type instead of unsigned, LLE doesn't check if the char is less than '0' if (pszDateTime[pos] > '9') { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): failed to parse day: ASCII value 0x%x at position %d is not a digit", pszDateTime[pos], pos}; } if (pszDateTime[pos] < '0') { cellRtc.warning("cellRtcParseDateTime(): ASCII value 0x%x at position %d is not a digit", pszDateTime[pos], pos); } day = static_cast(pszDateTime[pos]) - '0'; // Needs to be sign extended first to match LLE for values from 0x80 to 0xb0 pos++; } else if (std::isdigit(pszDateTime[pos])) { day = digit(pszDateTime[pos]); pos++; if (std::isdigit(pszDateTime[pos])) { day = day * 10 + digit(pszDateTime[pos]); pos++; } } else { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): failed to parse day: ASCII value 0x%x at position %d is not a digit or space", pszDateTime[pos], pos}; } // Hour: " X" or " XX" const u16 hour = rtcParseComponent(pszDateTime, pos, ' ', "hour"); if (hour == umax) { return CELL_RTC_ERROR_BAD_PARSE; } // Minute: ":X" or ":XX" const u16 minute = rtcParseComponent(pszDateTime, pos, ':', "minute"); if (minute == umax) { return CELL_RTC_ERROR_BAD_PARSE; } // Second: ":X" or ":XX" const u16 second = rtcParseComponent(pszDateTime, pos, ':', "second"); if (second == umax) { return CELL_RTC_ERROR_BAD_PARSE; } // Mandatory space if (pszDateTime[pos] != ' ') { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): no space after second"}; } pos++; // Year: XXXX if (!std::isdigit(pszDateTime[pos]) || !std::isdigit(pszDateTime[pos + 1]) || !std::isdigit(pszDateTime[pos + 2]) || !std::isdigit(pszDateTime[pos + 3])) { return {CELL_RTC_ERROR_BAD_PARSE, "cellRtcParseDateTime(): failed to parse year: one of the ASCII values 0x%x, 0x%x, 0x%x, or 0x%x is not a digit", pszDateTime[pos], pszDateTime[pos + 1], pszDateTime[pos + 2], pszDateTime[pos + 3]}; } const u16 year = digit(pszDateTime[pos]) * 1000 + digit(pszDateTime[pos + 1]) * 100 + digit(pszDateTime[pos + 2]) * 10 + digit(pszDateTime[pos + 3]); const vm::var date_time{{year, month, day, hour, minute, second, 0}}; cellRtcGetTick(ppu, date_time, pUtc); return CELL_OK; } // Rfc3339: 1995-12-03T13:23:00.00Z error_code cellRtcParseRfc3339(ppu_thread& ppu, vm::ptr pUtc, vm::cptr pszDateTime) { cellRtc.notice("cellRtcParseRfc3339(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pUtc.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pszDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; // 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(ppu, date_time, pUtc); cellRtcTickAddMinutes(ppu, pUtc, pUtc, minutes_to_add); return CELL_OK; } error_code cellRtcGetTick(ppu_thread& ppu, vm::cptr pTime, vm::ptr pTick) { cellRtc.trace("cellRtcGetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pTime->year >= 10000 || pTime->year == 0) { return CELL_RTC_ERROR_INVALID_VALUE; } pTick->tick = date_time_to_tick(*pTime); return CELL_OK; } CellRtcDateTime tick_to_date_time(u64 tick) { 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; u32 days_tmp = static_cast(tick / (24ULL * 60ULL * 60ULL * 1000000ULL)); const u32 year_400 = days_tmp / DAYS_IN_400_YEARS; days_tmp -= year_400 * DAYS_IN_400_YEARS; const u32 year_within_400_interval = (days_tmp - days_tmp / (DAYS_IN_4_YEARS - 1) + days_tmp / DAYS_IN_100_YEARS - days_tmp / (DAYS_IN_400_YEARS - 1)) / 365; days_tmp -= year_within_400_interval * 365 + year_within_400_interval / 4 - year_within_400_interval / 100 + year_within_400_interval / 400; const u16 years = year_400 * 400 + year_within_400_interval + 1; const auto& month_offset = is_leap_year(years) ? MONTH_OFFSET_LEAP : MONTH_OFFSET; u32 month_approx = days_tmp / 29; if (month_offset[month_approx] > days_tmp) { month_approx--; } const u16 months = month_approx + 1; days_tmp = days_tmp - month_offset[month_approx]; 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 u32 days_in_previous_years = date_time.year * 365 + (date_time.year + 3) / 4 - (date_time.year + 99) / 100 + (date_time.year + 399) / 400 - 366; // Not checked on LLE if (date_time.month == 0u) { cellRtc.warning("date_time_to_tick(): month invalid, clamping to 1"); date_time.month = 1; } else if (date_time.month > 12u) { cellRtc.warning("date_time_to_tick(): month invalid, clamping to 12"); date_time.month = 12; } const u16 days_in_previous_months = is_leap_year(date_time.year) ? MONTH_OFFSET_LEAP[date_time.month - 1] : MONTH_OFFSET[date_time.month - 1]; const u32 days = days_in_previous_years + days_in_previous_months + date_time.day - 1; 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(ppu_thread& ppu, vm::ptr pTime, vm::cptr pTick) { cellRtc.trace("cellRtcSetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } *pTime = tick_to_date_time(pTick->tick); return CELL_OK; } error_code cellRtcTickAddTicks(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddTicks(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd; return CELL_OK; } error_code cellRtcTickAddMicroseconds(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddMicroseconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd; return CELL_OK; } error_code cellRtcTickAddSeconds(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddSeconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddMinutes(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s64 lAdd) { cellRtc.trace("cellRtcTickAddMinutes(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + lAdd * 60 * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddHours(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddHours(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddDays(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddDays(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * 24ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddWeeks(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.trace("cellRtcTickAddWeeks(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } pTick0->tick = pTick1->tick + iAdd * 60ULL * 60ULL * 24ULL * 7ULL * cellRtcGetTickResolution(); return CELL_OK; } error_code cellRtcTickAddMonths(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.notice("cellRtcTickAddMonths(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; cellRtcSetTick(ppu, date_time, pTick1); const s32 total_months = date_time->year * 12 + date_time->month + iAdd - 1; const u16 new_year = total_months / 12; const u16 new_month = total_months - new_year * 12 + 1; s32 month_days; if (new_year == 0u || new_month < 1u || new_month > 12u) { month_days = CELL_RTC_ERROR_INVALID_ARG; // LLE writes the error to this variable } else { month_days = is_leap_year(new_year) ? DAYS_IN_MONTH_LEAP[new_month - 1] : DAYS_IN_MONTH[new_month - 1]; } if (month_days < static_cast(date_time->day)) { date_time->day = static_cast(month_days); } date_time->month = new_month; date_time->year = new_year; cellRtcGetTick(ppu, date_time, pTick0); return CELL_OK; } error_code cellRtcTickAddYears(ppu_thread& ppu, vm::ptr pTick0, vm::cptr pTick1, s32 iAdd) { cellRtc.notice("cellRtcTickAddYears(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pTick0.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTick1.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var date_time; cellRtcSetTick(ppu, date_time, pTick1); const u16 month = date_time->month; const u16 new_year = date_time->year + iAdd; s32 month_days; if (new_year == 0u || month < 1u || month > 12u) { month_days = CELL_RTC_ERROR_INVALID_ARG; // LLE writes the error to this variable } else { month_days = is_leap_year(new_year) ? DAYS_IN_MONTH_LEAP[month - 1] : DAYS_IN_MONTH[month - 1]; } if (month_days < static_cast(date_time->day)) { date_time->day = static_cast(month_days); } date_time->year = new_year; cellRtcGetTick(ppu, date_time, pTick0); return CELL_OK; } error_code cellRtcConvertUtcToLocalTime(ppu_thread& ppu, vm::cptr pUtc, vm::ptr pLocalTime) { cellRtc.trace("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(ppu, pLocalTime, pUtc, *timezone + *summertime); } return ret; } error_code cellRtcConvertLocalTimeToUtc(ppu_thread& ppu, vm::cptr pLocalTime, vm::ptr pUtc) { cellRtc.notice("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(ppu, pUtc, pLocalTime, -(*timezone + *summertime)); } return ret; } error_code cellRtcGetCurrentSecureTick(ppu_thread& ppu, vm::ptr tick) { cellRtc.notice("cellRtcGetCurrentSecureTick(tick=*0x%x)", tick); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, tick.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } tick->tick = RTC_SYSTEM_TIME_MIN; const vm::var time{0}; const vm::var status{0}; error_code ret = sys_ss_secure_rtc(0x3002, 0, time.addr(), status.addr()); if (ret >= CELL_OK) { tick->tick += *time * cellRtcGetTickResolution(); } else if (ret == static_cast(SYS_SS_RTC_ERROR_UNK)) { switch (*status) { case 1: ret = CELL_RTC_ERROR_NO_CLOCK; break; case 2: ret = CELL_RTC_ERROR_NOT_INITIALIZED; break; case 4: ret = CELL_RTC_ERROR_INVALID_VALUE; break; case 8: ret = CELL_RTC_ERROR_NO_CLOCK; break; default: return ret; } } return ret; } error_code cellRtcGetDosTime(ppu_thread& ppu, vm::cptr pDateTime, vm::ptr puiDosTime) { cellRtc.notice("cellRtcGetDosTime(pDateTime=*0x%x, puiDosTime=*0x%x)", pDateTime, puiDosTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, puiDosTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pDateTime->year < 1980) { if (puiDosTime) // Should always evaluate to true, nullptr was already checked above { *puiDosTime = 0; } return -1; } if (pDateTime->year >= 2108) { if (puiDosTime) // Should always evaluate to true, nullptr was already checked above { *puiDosTime = 0xff9fbf7d; // kHighDosTime } return -1; } if (puiDosTime) // Should always evaluate to true, nullptr was already checked above { 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; } error_code cellRtcGetSystemTime(ppu_thread& ppu, vm::cptr pDateTime, vm::ptr pTimeStamp) { cellRtc.notice("cellRtcGetSystemTime(pDateTime=*0x%x, pTimeStamp=*0x%x)", pDateTime, pTimeStamp); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pTimeStamp.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; cellRtcGetTick(ppu, pDateTime, tick); if (tick->tick < RTC_SYSTEM_TIME_MIN) { if (pTimeStamp) // Should always evaluate to true, nullptr was already checked above { *pTimeStamp = 0; } return CELL_RTC_ERROR_INVALID_VALUE; } if (tick->tick >= RTC_SYSTEM_TIME_MAX + cellRtcGetTickResolution()) { if (pTimeStamp) // Should always evaluate to true, nullptr was already checked above { *pTimeStamp = (RTC_SYSTEM_TIME_MAX - RTC_SYSTEM_TIME_MIN) / cellRtcGetTickResolution(); } return CELL_RTC_ERROR_INVALID_VALUE; } if (pTimeStamp) // Should always evaluate to true, nullptr was already checked above { *pTimeStamp = (tick->tick - RTC_SYSTEM_TIME_MIN) / cellRtcGetTickResolution(); } return CELL_OK; } error_code cellRtcGetTime_t(ppu_thread& ppu, vm::cptr pDateTime, vm::ptr piTime) { cellRtc.trace("cellRtcGetTime_t(pDateTime=*0x%x, piTime=*0x%x)", pDateTime, piTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, piTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; cellRtcGetTick(ppu, pDateTime, tick); if (tick->tick < RTC_MAGIC_OFFSET) { if (piTime) // Should always evaluate to true, nullptr was already checked above { *piTime = 0; } return CELL_RTC_ERROR_INVALID_VALUE; } if (piTime) // Should always evaluate to true, nullptr was already checked above { *piTime = (tick->tick - RTC_MAGIC_OFFSET) / cellRtcGetTickResolution(); } return CELL_OK; } error_code cellRtcGetWin32FileTime(ppu_thread& ppu, vm::cptr pDateTime, vm::ptr pulWin32FileTime) { cellRtc.notice("cellRtcGetWin32FileTime(pDateTime=*0x%x, pulWin32FileTime=*0x%x)", pDateTime, pulWin32FileTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } if (sys_memory_get_page_attribute(ppu, pulWin32FileTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; cellRtcGetTick(ppu, pDateTime, tick); if (tick->tick < RTC_FILETIME_OFFSET) { if (pulWin32FileTime) // Should always evaluate to true, nullptr was already checked above { *pulWin32FileTime = 0; } return CELL_RTC_ERROR_INVALID_VALUE; } if (pulWin32FileTime) // Should always evaluate to true, nullptr was already checked above { *pulWin32FileTime = (tick->tick - RTC_FILETIME_OFFSET) * 10; } return CELL_OK; } error_code cellRtcSetCurrentSecureTick(vm::ptr pTick) { cellRtc.notice("cellRtcSetCurrentSecureTick(pTick=*0x%x)", pTick); if (!pTick) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pTick->tick > RTC_SYSTEM_TIME_MAX) { return CELL_RTC_ERROR_INVALID_VALUE; } return sys_ss_secure_rtc(0x3003, (pTick->tick - RTC_SYSTEM_TIME_MIN) / cellRtcGetTickResolution(), 0, 0); } error_code cellRtcSetCurrentTick(vm::cptr pTick) { cellRtc.notice("cellRtcSetCurrentTick(pTick=*0x%x)", pTick); if (!pTick) { return CELL_RTC_ERROR_INVALID_POINTER; } if (pTick->tick < RTC_MAGIC_OFFSET) { return CELL_RTC_ERROR_INVALID_VALUE; } const u64 unix_time = pTick->tick - RTC_MAGIC_OFFSET; const error_code ret = sys_time_set_current_time(unix_time / cellRtcGetTickResolution(), unix_time % cellRtcGetTickResolution() * 1000); return ret >= CELL_OK ? CELL_OK : ret; } error_code cellRtcSetConf(s64 unk1, s64 unk2, u32 timezone, u32 summertime) { cellRtc.notice("cellRtcSetConf(unk1=0x%x, unk2=0x%x, timezone=%d, summertime=%d)", unk1, unk2, timezone, summertime); // Seems the first 2 args are ignored :| return sys_time_set_timezone(timezone, summertime); } error_code cellRtcSetDosTime(ppu_thread& ppu, vm::ptr pDateTime, u32 uiDosTime) { cellRtc.notice("cellRtcSetDosTime(pDateTime=*0x%x, uiDosTime=0x%x)", pDateTime, uiDosTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { 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; } constexpr u32 cellRtcGetTickResolution() { // Amount of ticks in a second return 1000000; } error_code cellRtcSetTime_t(ppu_thread& ppu, vm::ptr pDateTime, u64 iTime) { cellRtc.notice("cellRtcSetTime_t(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = iTime * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET; cellRtcSetTick(ppu, pDateTime, tick); return CELL_OK; } error_code cellRtcSetSystemTime(ppu_thread& ppu, vm::ptr pDateTime, u64 iTime) { cellRtc.notice("cellRtcSetSystemTime(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = iTime * cellRtcGetTickResolution() + RTC_SYSTEM_TIME_MIN; cellRtcSetTick(ppu, pDateTime, tick); return CELL_OK; } error_code cellRtcSetWin32FileTime(ppu_thread& ppu, vm::ptr pDateTime, u64 ulWin32FileTime) { cellRtc.notice("cellRtcSetWin32FileTime(pDateTime=*0x%x, ulWin32FileTime=0x%llx)", pDateTime, ulWin32FileTime); const vm::var page_attr; if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK) { return CELL_RTC_ERROR_INVALID_POINTER; } vm::var tick; tick->tick = ulWin32FileTime / 10 + RTC_FILETIME_OFFSET; return cellRtcSetTick(ppu, pDateTime, tick); } error_code cellRtcIsLeapYear(s32 year) { cellRtc.notice("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.notice("cellRtcGetDaysInMonth(year=%d, month=%d)", year, month); if ((year <= 0) || (month <= 0) || (month > 12)) { return CELL_RTC_ERROR_INVALID_ARG; } return not_an_error(is_leap_year(year) ? DAYS_IN_MONTH_LEAP[month - 1] : DAYS_IN_MONTH[month - 1]); } s32 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 ((month * 13 + 8) / 5 + year + year / 4 - year / 100 + year / 400 + day) % 7; } error_code cellRtcCheckValid(vm::cptr pTime) { cellRtc.notice("cellRtcCheckValid(pTime=*0x%x)", pTime); ensure(!!pTime); // Not checked on LLE cellRtc.notice("cellRtcCheckValid year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d, microsecond: %d", 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; } const auto& days_in_month = is_leap_year(pTime->year) ? DAYS_IN_MONTH_LEAP : DAYS_IN_MONTH; if (pTime->day == 0 || pTime->day > days_in_month[pTime->month - 1]) { 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; } s32 cellRtcCompareTick(vm::cptr pTick0, vm::cptr pTick1) { cellRtc.notice("cellRtcCompareTick(pTick0=*0x%x, pTick1=*0x%x)", pTick0, pTick1); ensure(!!pTick0 && !!pTick1); // Not checked on LLE s32 ret = -1; if (pTick1->tick <= pTick0->tick) { ret = pTick1->tick < pTick0->tick; } return 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); });