rpcsx/ps3fw/cellRtc.cpp
DH 62ad27d1e2 split rpcs3 and hle libraries
merge rpcs3 utilities
2025-04-08 19:46:57 +03:00

1850 lines
48 KiB
C++

#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);
template <>
void fmt_class_string<CellRtcError>::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<CellRtcTick> pTick)
{
cellRtc.trace("cellRtcGetCurrentTick(pTick=*0x%x)", pTick);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pTick.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<s64> sec;
vm::var<s64> 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<CellRtcDateTime> pClock, s32 iTimeZone)
{
cellRtc.trace("cellRtcGetCurrentClock(pClock=*0x%x, iTimeZone=%d)", pClock, iTimeZone);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pClock.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<CellRtcTick> tick;
if (sys_memory_get_page_attribute(ppu, tick.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<s64> sec;
vm::var<s64> 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<CellRtcDateTime> pClock)
{
cellRtc.trace("cellRtcGetCurrentClockLocalTime(pClock=*0x%x)", pClock);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pClock.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<s32> timezone;
vm::var<s32> 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<CellRtcTick> tick;
if (sys_memory_get_page_attribute(ppu, tick.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<s64> sec;
vm::var<s64> 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<char> pszDateTime, vm::cptr<CellRtcTick> pUtc, s32 iTimeZone)
{
cellRtc.notice("cellRtcFormatRfc2822(pszDateTime=*0x%x, pUtc=*0x%x, iTimeZone=%d)", pszDateTime, pUtc, iTimeZone);
const vm::var<sys_page_attr_t> 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<CellRtcTick> 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<CellRtcDateTime> 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<char> pszDateTime, vm::cptr<CellRtcTick> pUtc)
{
cellRtc.notice("cellRtcFormatRfc2822LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc);
const vm::var<sys_page_attr_t> 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<s32> timezone;
vm::var<s32> 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<char> pszDateTime, vm::cptr<CellRtcTick> pUtc, s32 iTimeZone)
{
cellRtc.notice("cellRtcFormatRfc3339(pszDateTime=*0x%x, pUtc=*0x%x, iTimeZone=%d)", pszDateTime, pUtc, iTimeZone);
const vm::var<sys_page_attr_t> 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<CellRtcTick> 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<CellRtcDateTime> 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<char> pszDateTime, vm::cptr<CellRtcTick> pUtc)
{
cellRtc.notice("cellRtcFormatRfc3339LocalTime(pszDateTime=*0x%x, pUtc=*0x%x)", pszDateTime, pUtc);
const vm::var<sys_page_attr_t> 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<s32> timezone;
vm::var<s32> 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<char> 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 <usz size>
u8 rtcParseName(vm::cptr<char> pszDateTime, u32& pos, const std::array<std::string_view, size>& names, bool allow_short_name = true)
{
for (u8 name_idx = 0; name_idx < names.size(); name_idx++)
{
const u32 name_length = static_cast<u32>(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<CellRtcTick> pUtc, vm::cptr<char> 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<CellRtcDateTime> 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<CellRtcTick> pUtc, vm::cptr<char> pszDateTime);
/*
Takes a RFC2822 / RFC3339 / asctime String, and converts it to a CellRtcTick
*/
error_code cellRtcParseDateTime(ppu_thread& ppu, vm::ptr<CellRtcTick> pUtc, vm::cptr<char> pszDateTime)
{
cellRtc.notice("cellRtcParseDateTime(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime);
const vm::var<sys_page_attr_t> 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<u16>(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<CellRtcDateTime> 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<CellRtcTick> pUtc, vm::cptr<char> pszDateTime)
{
cellRtc.notice("cellRtcParseRfc3339(pUtc=*0x%x, pszDateTime=%s)", pUtc, pszDateTime);
const vm::var<sys_page_attr_t> 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<CellRtcDateTime> 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<CellRtcDateTime> pTime, vm::ptr<CellRtcTick> pTick)
{
cellRtc.trace("cellRtcGetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick);
const vm::var<sys_page_attr_t> 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<u32>(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<u16>(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<CellRtcDateTime> pTime, vm::cptr<CellRtcTick> pTick)
{
cellRtc.trace("cellRtcSetTick(pTime=*0x%x, pTick=*0x%x)", pTime, pTick);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s64 lAdd)
{
cellRtc.trace("cellRtcTickAddTicks(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s64 lAdd)
{
cellRtc.trace("cellRtcTickAddMicroseconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s64 lAdd)
{
cellRtc.trace("cellRtcTickAddSeconds(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s64 lAdd)
{
cellRtc.trace("cellRtcTickAddMinutes(pTick0=*0x%x, pTick1=*0x%x, lAdd=%lld)", pTick0, pTick1, lAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s32 iAdd)
{
cellRtc.trace("cellRtcTickAddHours(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s32 iAdd)
{
cellRtc.trace("cellRtcTickAddDays(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s32 iAdd)
{
cellRtc.trace("cellRtcTickAddWeeks(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd);
const vm::var<sys_page_attr_t> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s32 iAdd)
{
cellRtc.notice("cellRtcTickAddMonths(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd);
const vm::var<sys_page_attr_t> 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<CellRtcDateTime> 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<s32>(date_time->day))
{
date_time->day = static_cast<u16>(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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> pTick1, s32 iAdd)
{
cellRtc.notice("cellRtcTickAddYears(pTick0=*0x%x, pTick1=*0x%x, iAdd=%d)", pTick0, pTick1, iAdd);
const vm::var<sys_page_attr_t> 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<CellRtcDateTime> 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<s32>(date_time->day))
{
date_time->day = static_cast<u16>(month_days);
}
date_time->year = new_year;
cellRtcGetTick(ppu, date_time, pTick0);
return CELL_OK;
}
error_code cellRtcConvertUtcToLocalTime(ppu_thread& ppu, vm::cptr<CellRtcTick> pUtc, vm::ptr<CellRtcTick> pLocalTime)
{
cellRtc.trace("cellRtcConvertUtcToLocalTime(pUtc=*0x%x, pLocalTime=*0x%x)", pUtc, pLocalTime);
vm::var<s32> timezone;
vm::var<s32> 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<CellRtcTick> pLocalTime, vm::ptr<CellRtcTick> pUtc)
{
cellRtc.notice("cellRtcConvertLocalTimeToUtc(pLocalTime=*0x%x, pUtc=*0x%x)", pLocalTime, pUtc);
vm::var<s32> timezone;
vm::var<s32> 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<CellRtcTick> tick)
{
cellRtc.notice("cellRtcGetCurrentSecureTick(tick=*0x%x)", tick);
const vm::var<sys_page_attr_t> 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<u64> time{0};
const vm::var<u64> 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<s32>(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<CellRtcDateTime> pDateTime, vm::ptr<u32> puiDosTime)
{
cellRtc.notice("cellRtcGetDosTime(pDateTime=*0x%x, puiDosTime=*0x%x)", pDateTime, puiDosTime);
const vm::var<sys_page_attr_t> 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<CellRtcDateTime> pDateTime, vm::ptr<s64> pTimeStamp)
{
cellRtc.notice("cellRtcGetSystemTime(pDateTime=*0x%x, pTimeStamp=*0x%x)", pDateTime, pTimeStamp);
const vm::var<sys_page_attr_t> 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<CellRtcTick> 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<CellRtcDateTime> pDateTime, vm::ptr<s64> piTime)
{
cellRtc.trace("cellRtcGetTime_t(pDateTime=*0x%x, piTime=*0x%x)", pDateTime, piTime);
const vm::var<sys_page_attr_t> 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<CellRtcTick> 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<CellRtcDateTime> pDateTime, vm::ptr<u64> pulWin32FileTime)
{
cellRtc.notice("cellRtcGetWin32FileTime(pDateTime=*0x%x, pulWin32FileTime=*0x%x)", pDateTime, pulWin32FileTime);
const vm::var<sys_page_attr_t> 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<CellRtcTick> 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<CellRtcTick> 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<CellRtcTick> 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<CellRtcDateTime> pDateTime, u32 uiDosTime)
{
cellRtc.notice("cellRtcSetDosTime(pDateTime=*0x%x, uiDosTime=0x%x)", pDateTime, uiDosTime);
const vm::var<sys_page_attr_t> 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<CellRtcDateTime> pDateTime, u64 iTime)
{
cellRtc.notice("cellRtcSetTime_t(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<CellRtcTick> tick;
tick->tick = iTime * cellRtcGetTickResolution() + RTC_MAGIC_OFFSET;
cellRtcSetTick(ppu, pDateTime, tick);
return CELL_OK;
}
error_code cellRtcSetSystemTime(ppu_thread& ppu, vm::ptr<CellRtcDateTime> pDateTime, u64 iTime)
{
cellRtc.notice("cellRtcSetSystemTime(pDateTime=*0x%x, iTime=0x%llx)", pDateTime, iTime);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<CellRtcTick> tick;
tick->tick = iTime * cellRtcGetTickResolution() + RTC_SYSTEM_TIME_MIN;
cellRtcSetTick(ppu, pDateTime, tick);
return CELL_OK;
}
error_code cellRtcSetWin32FileTime(ppu_thread& ppu, vm::ptr<CellRtcDateTime> pDateTime, u64 ulWin32FileTime)
{
cellRtc.notice("cellRtcSetWin32FileTime(pDateTime=*0x%x, ulWin32FileTime=0x%llx)", pDateTime, ulWin32FileTime);
const vm::var<sys_page_attr_t> page_attr;
if (sys_memory_get_page_attribute(ppu, pDateTime.addr(), page_attr) != CELL_OK)
{
return CELL_RTC_ERROR_INVALID_POINTER;
}
vm::var<CellRtcTick> 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<CellRtcDateTime> 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<CellRtcTick> pTick0, vm::cptr<CellRtcTick> 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);
});