// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "common/logging/log.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/process.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" #include "core/libraries/rtc/rtc.h" #include "core/libraries/rtc/rtc_error.h" namespace Libraries::Rtc { /* * Internal code */ int _sceRtcTickSubMicroseconds(OrbisRtcTick* pTick0, const OrbisRtcTick* pTick1, int64_t lSub) { // FUN_01003270 if (!pTick0 || !pTick1) return ORBIS_RTC_ERROR_INVALID_POINTER; if (lSub == 0) { pTick0->tick = pTick1->tick; return ORBIS_OK; } uint64_t t1 = pTick1->tick; if (lSub < 0) { if (t1 < static_cast(-lSub)) return ORBIS_RTC_ERROR_INVALID_VALUE; } else { if ((~t1) < static_cast(lSub)) return ORBIS_RTC_ERROR_INVALID_VALUE; } t1 += lSub; pTick0->tick = t1; return ORBIS_OK; } static const int MonthDays[2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // non-leap {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} // leap }; // Leap-year calculation inline bool leap_year(int year) { if (year == (((year >> 4) / 19) * 400)) { return true; } else if (year == (((year >> 2) / 19) * 100)) { return false; } else { return (year & 3) == 0; } } /* * Module code */ int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) { if (pTime == NULL) return ORBIS_RTC_ERROR_INVALID_POINTER; uint32_t year = pTime->year; if (year == 0 || year > 9999) return ORBIS_RTC_ERROR_INVALID_YEAR; if (pTime->month == 0 || pTime->month > 12) return ORBIS_RTC_ERROR_INVALID_MONTH; if (pTime->day == 0) return ORBIS_RTC_ERROR_INVALID_DAY; int leap = leap_year(year); if (pTime->day > MonthDays[leap][pTime->month]) return ORBIS_RTC_ERROR_INVALID_DAY; if (pTime->hour >= 24) return ORBIS_RTC_ERROR_INVALID_HOUR; if (pTime->minute >= 60) return ORBIS_RTC_ERROR_INVALID_MINUTE; if (pTime->second >= 60) return ORBIS_RTC_ERROR_INVALID_SECOND; if (pTime->microsecond >= 1000000) return ORBIS_RTC_ERROR_INVALID_MICROSECOND; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick0, OrbisRtcTick* pTick1) { if (pTick0 == nullptr || pTick1 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; int result = -1; if (pTick1->tick <= pTick0->tick) { result = (-((int)(pTick1->tick < pTick0->tick))) & 1; } return result; } int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pLocalTime, OrbisRtcTick* pUtc) { if (pLocalTime == NULL) return ORBIS_RTC_ERROR_INVALID_POINTER; time_t utc_time; Kernel::OrbisKernelTimezone tz; int result = Kernel::sceKernelConvertLocaltimeToUtc( (pLocalTime->tick + 0xFF23400100D44000ULL) / 1000000ULL, 0xFFFFFFFF, &utc_time, &tz, NULL); if (result >= 0) { int offset_minutes = tz.tz_dsttime + tz.tz_minuteswest; result = sceRtcTickAddMinutes(pUtc, pLocalTime, -offset_minutes); } return result; } int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal) { LOG_TRACE(Lib_Rtc, "called"); if (pTickUtc == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisTimesec tsec{}; time_t local_time{}; // Convert PS4 UTC tick → Unix seconds uint64_t utc_micro = (pTickUtc->tick + 0xFF23400100D44000ULL) / 1000000ULL; s32 result = Kernel::sceKernelConvertUtcToLocaltime(utc_micro, &local_time, &tsec, nullptr); if (result < 0) return result; // Apply timezone + DST offset const int64_t offset_us = ((tsec.dst_sec + tsec.west_sec) / 60) * 60000000LL; return _sceRtcTickSubMicroseconds(pTickLocal, pTickUtc, offset_us); } int PS4_SYSV_ABI sceRtcEnd() { return ORBIS_OK; } int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, int iTimeZoneMinutes) { LOG_TRACE(Lib_Rtc, "called"); if (pszDateTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; OrbisRtcTick formatTick; if (pTickUtc == nullptr) { sceRtcGetCurrentTick(&formatTick); } else { formatTick.tick = pTickUtc->tick; } sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); OrbisRtcDateTime formatTime; sceRtcSetTick(&formatTime, &formatTick); int validTime = sceRtcCheckValid(&formatTime); std::string formattedString; if (validTime >= 0) { int weekDay = sceRtcGetDayOfWeek(formatTime.year, formatTime.month, formatTime.day); switch (weekDay) { case 0: formattedString = "Sun, "; break; case 1: formattedString = "Mon, "; break; case 2: formattedString = "Tue, "; break; case 3: formattedString = "Wed, "; break; case 4: formattedString = "Thu, "; break; case 5: formattedString = "Fri, "; break; case 6: formattedString = "Sat, "; break; } if (formatTime.day < 10) { formattedString += "0" + std::to_string(formatTime.day) + " "; } else { formattedString += std::to_string(formatTime.day) + " "; } switch (formatTime.month) { case 1: formattedString += "Jan "; break; case 2: formattedString += "Feb "; break; case 3: formattedString += "Mar "; break; case 4: formattedString += "Apr "; break; case 5: formattedString += "May "; break; case 6: formattedString += "Jun "; break; case 7: formattedString += "Jul "; break; case 8: formattedString += "Aug "; break; case 9: formattedString += "Sep "; break; case 10: formattedString += "Oct "; break; case 11: formattedString += "Nov "; break; case 12: formattedString += "Dec "; break; } formattedString += std::to_string(formatTime.year) + " "; if (formatTime.hour < 10) { formattedString += "0" + std::to_string(formatTime.hour) + ":"; } else { formattedString += std::to_string(formatTime.hour) + ":"; } if (formatTime.minute < 10) { formattedString += "0" + std::to_string(formatTime.minute) + ":"; } else { formattedString += std::to_string(formatTime.minute) + ":"; } if (formatTime.second < 10) { formattedString += "0" + std::to_string(formatTime.second) + " "; } else { formattedString += std::to_string(formatTime.second) + " "; } if (iTimeZoneMinutes == 0) { formattedString += "+0000"; } else { int timeZoneHours = iTimeZoneMinutes / 60; int timeZoneRemainder = iTimeZoneMinutes % 60; if (timeZoneHours < 0) { formattedString += "-"; timeZoneHours *= -1; } else { formattedString += "+"; } if (timeZoneHours < 10) { formattedString += "0" + std::to_string(timeZoneHours); } else { formattedString += std::to_string(timeZoneHours); } if (timeZoneRemainder == 0) { formattedString += "00"; } else { if (timeZoneRemainder < 0) timeZoneRemainder *= -1; formattedString += std::to_string(timeZoneRemainder); } } for (int i = 0; i < formattedString.size() + 1; ++i) { pszDateTime[i] = formattedString.c_str()[i]; } } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { LOG_TRACE(Lib_Rtc, "called"); Kernel::OrbisKernelTimezone timeZone; Kernel::sceKernelGettimezone(&timeZone); return sceRtcFormatRFC2822(pszDateTime, pTickUtc, -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, int iTimeZoneMinutes) { LOG_TRACE(Lib_Rtc, "called"); return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, iTimeZoneMinutes); } int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { LOG_TRACE(Lib_Rtc, "called"); Kernel::OrbisKernelTimezone timeZone; Kernel::sceKernelGettimezone(&timeZone); return sceRtcFormatRFC3339(pszDateTime, pTickUtc, -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc, int iTimeZoneMinutes) { LOG_TRACE(Lib_Rtc, "called"); if (pszDateTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; OrbisRtcTick formatTick; if (pTickUtc == nullptr) { sceRtcGetCurrentTick(&formatTick); } else { formatTick.tick = pTickUtc->tick; } sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); OrbisRtcDateTime formatTime; sceRtcSetTick(&formatTime, &formatTick); std::string formattedString; formattedString = std::to_string(formatTime.year) + "-"; if (formatTime.month < 10) { formattedString += "0" + std::to_string(formatTime.month) + "-"; } else { formattedString += std::to_string(formatTime.month) + "-"; } if (formatTime.day < 10) { formattedString += "0" + std::to_string(formatTime.day) + "T"; } else { formattedString += std::to_string(formatTime.day) + "T"; } if (formatTime.hour < 10) { formattedString += "0" + std::to_string(formatTime.hour) + ":"; } else { formattedString += std::to_string(formatTime.hour) + ":"; } if (formatTime.minute < 10) { formattedString += "0" + std::to_string(formatTime.minute) + ":"; } else { formattedString += std::to_string(formatTime.minute) + ":"; } if (formatTime.second < 10) { formattedString += "0" + std::to_string(formatTime.second); } else { formattedString += std::to_string(formatTime.second); } if (formatTime.microsecond != 0) { formattedString += "." + std::to_string(formatTime.microsecond / 1000).substr(0, 2); } else { formattedString += ".00"; } if (iTimeZoneMinutes == 0) { formattedString += "Z"; } else { int timeZoneHours = iTimeZoneMinutes / 60; int timeZoneRemainder = iTimeZoneMinutes % 60; if (timeZoneHours < 0) { formattedString += "-"; timeZoneHours *= -1; } else { formattedString += "+"; } if (timeZoneHours < 10) { formattedString += "0" + std::to_string(timeZoneHours); } else { formattedString += std::to_string(timeZoneHours); } if (timeZoneRemainder == 0) { formattedString += ":00"; } else { if (timeZoneRemainder < 0) timeZoneRemainder *= -1; formattedString += ":" + std::to_string(timeZoneRemainder); } } for (int i = 0; i < formattedString.size() + 1; ++i) { pszDateTime[i] = formattedString.c_str()[i]; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { LOG_TRACE(Lib_Rtc, "called"); Kernel::OrbisKernelTimezone timeZone; Kernel::sceKernelGettimezone(&timeZone); return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue == ORBIS_OK) { pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; } else { return ORBIS_RTC_ERROR_NOT_INITIALIZED; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr) return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue == ORBIS_OK) { OrbisRtcTick clockTick; clockTick.tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; sceRtcTickAddMinutes(&clockTick, &clockTick, timeZone); sceRtcSetTick(pTime, &clockTick); } return returnValue; } int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime) { LOG_TRACE(Lib_Rtc, "called"); if (!pTime) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisKernelTimespec ts{}; int result = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &ts); if (result < 0) return result; uint64_t _tick = static_cast(ts.tv_sec) * 1000000ULL + static_cast(ts.tv_nsec) / 1000ULL; uint64_t tick = _tick + UNIX_EPOCH_TICKS; time_t local_time{}; Kernel::OrbisTimesec tzsec{}; result = sceKernelConvertUtcToLocaltime(_tick / 1000000ULL, &local_time, &tzsec, nullptr); if (result < 0) return result; OrbisRtcTick rtcTick{tick}; int64_t offset_minutes = static_cast(tzsec.dst_sec + tzsec.west_sec) / 60; result = sceRtcTickAddMinutes(&rtcTick, &rtcTick, offset_minutes); if (result < 0) return result; result = sceRtcSetTick(pTime, &rtcTick); return result; } int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue == ORBIS_OK) { pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; } else { return ORBIS_RTC_ERROR_NOT_INITIALIZED; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue == ORBIS_OK) { pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; } else { return ORBIS_RTC_ERROR_NOT_INITIALIZED; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue == ORBIS_OK) { pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; } else { return ORBIS_RTC_ERROR_NOT_INITIALIZED; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTick == nullptr) return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; Kernel::OrbisKernelTimespec clocktime; int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); if (returnValue >= 0) { pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day) { LOG_TRACE(Lib_Rtc, "called"); int sdk_version = 0; if (Kernel::sceKernelGetCompiledSdkVersion(&sdk_version) != ORBIS_OK) sdk_version = 0; // Year/month validation if (sdk_version < 0x3000000) { if (year < 1) return ORBIS_RTC_ERROR_INVALID_YEAR; if (month <= 0 || month > 12) return ORBIS_RTC_ERROR_INVALID_MONTH; } else { if (year < 1 || year > 9999) return ORBIS_RTC_ERROR_INVALID_YEAR; if (month <= 0 || month > 12) return ORBIS_RTC_ERROR_INVALID_MONTH; } int daysInMonth = sceRtcGetDaysInMonth(year, month); if (day <= 0 || day > daysInMonth) return ORBIS_RTC_ERROR_INVALID_DAY; // Zeller's congruence adjustment if (month < 3) { month += 12; year -= 1; } int weekday = ((13 * month + 8) / 5 - (year / 100) + year + (year / 4) + (year / 400) + day) % 7; return weekday; } int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month) { LOG_TRACE(Lib_Rtc, "called"); if (year <= 0) return ORBIS_RTC_ERROR_INVALID_YEAR; if (month <= 0 || month > 12) return ORBIS_RTC_ERROR_INVALID_MONTH; bool isLeap = leap_year(year); return MonthDays[isLeap ? 1 : 0][month]; } int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, u32* dosTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr || dosTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; // Check if the RTC time is valid int result = sceRtcCheckValid(pTime); if (result != ORBIS_OK) return result; uint16_t year = pTime->year; uint8_t month = pTime->month; uint8_t day = pTime->day; if (year < 1980) { *dosTime = 0; return ORBIS_OK; } else if (year < 2108) { *dosTime = ((pTime->second >> 1) & 0x1F) | // seconds / 2 ((pTime->minute & 0x3F) << 5) | // minutes ((pTime->hour & 0x1F) << 11) | // hours ((((month & 0x0F) * 0x20 + 0x8800 + (year * 0x200)) | (day & 0x1F)) << 16); // day/month/year return ORBIS_OK; } else { *dosTime = 0xFF9FBF7D; return ORBIS_RTC_ERROR_INVALID_YEAR; } } int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr || pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; int isTimeValid = sceRtcCheckValid(pTime); if (isTimeValid != 0) return isTimeValid; if (pTime->month > 2) { pTime->month -= 3; } else { pTime->month += 9; pTime->year -= 1; } int c = pTime->year / 100; int ya = pTime->year - 100 * c; u64 days; u64 msec; days = ((146097 * c) >> 2) + ((1461 * ya) >> 2) + (153 * pTime->month + 2) / 5 + pTime->day; days -= 307; days *= 86400000000; msec = pTime->hour * 3600000000 + pTime->minute * 60000000 + pTime->second * 1000000 + pTime->microsecond; pTick->tick = days + msec; return ORBIS_OK; } u32 PS4_SYSV_ABI sceRtcGetTickResolution() { LOG_TRACE(Lib_Rtc, "called"); return 1000000; } int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr || llTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; int isValid = sceRtcCheckValid(pTime); if (isValid != ORBIS_OK) { return isValid; } OrbisRtcTick timeTick; sceRtcGetTick(pTime, &timeTick); if (timeTick.tick < UNIX_EPOCH_TICKS) { *llTime = 0; } else { *llTime = (timeTick.tick - UNIX_EPOCH_TICKS) / 1000000; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr || ulWin32Time == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; int isValid = sceRtcCheckValid(pTime); if (isValid != ORBIS_OK) { return isValid; } OrbisRtcTick timeTick; sceRtcGetTick(pTime, &timeTick); if (timeTick.tick < WIN32_FILETIME_EPOCH_TICKS) { *ulWin32Time = 0; } else { *ulWin32Time = (timeTick.tick - WIN32_FILETIME_EPOCH_TICKS) * 10; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcInit() { return ORBIS_OK; } int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt) { LOG_TRACE(Lib_Rtc, "called"); if (yearInt < 1) return ORBIS_RTC_ERROR_INVALID_YEAR; using namespace std::chrono; year_month_day_last ymdl{year(yearInt) / February / last}; return (ymdl.day() == 29d); } int GetMonthFromString(std::string monthStr) { if (monthStr == "Jan") return 1; if (monthStr == "Feb") return 2; if (monthStr == "Mar") return 3; if (monthStr == "Apr") return 4; if (monthStr == "May") return 5; if (monthStr == "Jun") return 6; if (monthStr == "Jul") return 7; if (monthStr == "Aug") return 8; if (monthStr == "Sep") return 9; if (monthStr == "Oct") return 10; if (monthStr == "Nov") return 11; if (monthStr == "Dec") return 12; return 1; } int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTickUtc == nullptr || pszDateTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; std::string dateTimeString = std::string(pszDateTime); char formatKey = dateTimeString[19]; OrbisRtcDateTime dateTime; if (formatKey == 'Z' || formatKey == '-' || formatKey == '+' || formatKey == '.') { // RFC3339 sceRtcParseRFC3339(pTickUtc, pszDateTime); } else if (formatKey == ':') { // RFC2822 dateTime.day = std::stoi(dateTimeString.substr(5, 2)); dateTime.month = GetMonthFromString(dateTimeString.substr(8, 3)); dateTime.year = std::stoi(dateTimeString.substr(12, 4)); dateTime.hour = std::stoi(dateTimeString.substr(17, 2)); dateTime.minute = std::stoi(dateTimeString.substr(20, 2)); dateTime.second = std::stoi(dateTimeString.substr(23, 2)); dateTime.microsecond = 0; sceRtcGetTick(&dateTime, pTickUtc); if (dateTimeString[26] == '+') { int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); } else if (dateTimeString[26] == '-') { int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); timeZoneOffset *= -1; sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); } } else { // asctime dateTime.month = GetMonthFromString(dateTimeString.substr(4, 3)); dateTime.day = std::stoi(dateTimeString.substr(8, 2)); dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); dateTime.second = std::stoi(dateTimeString.substr(17, 2)); dateTime.year = std::stoi(dateTimeString.substr(20, 4)); dateTime.microsecond = 0; sceRtcGetTick(&dateTime, pTickUtc); } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTickUtc == nullptr || pszDateTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; std::string dateTimeString = std::string(pszDateTime); OrbisRtcDateTime dateTime; dateTime.year = std::stoi(dateTimeString.substr(0, 4)); dateTime.month = std::stoi(dateTimeString.substr(5, 2)); dateTime.day = std::stoi(dateTimeString.substr(8, 2)); dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); dateTime.second = std::stoi(dateTimeString.substr(17, 2)); s32 timezone_pos = 22; if (dateTimeString[19] == '.') { dateTime.microsecond = std::stoi(dateTimeString.substr(20, 2)); } else { timezone_pos = 19; dateTime.microsecond = 0; } sceRtcGetTick(&dateTime, pTickUtc); if (dateTimeString[timezone_pos] != 'Z') { if (dateTimeString[timezone_pos] == '-') { int timeZoneOffset = std::stoi(dateTimeString.substr(timezone_pos + 1, 2)) * 60; timeZoneOffset += std::stoi(dateTimeString.substr(timezone_pos + 4, 2)); timeZoneOffset *= -1; sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); } else if (dateTimeString[timezone_pos] == '+') { int timeZoneOffset = std::stoi(dateTimeString.substr(timezone_pos + 1, 2)) * 60; timeZoneOffset += std::stoi(dateTimeString.substr(timezone_pos + 4, 2)); sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); } } return ORBIS_OK; } void PS4_SYSV_ABI sceRtcSetConf(void* p1, void* p2, s32 minuteswest, s32 dsttime) { LOG_INFO(Lib_Rtc, "called"); Kernel::OrbisKernelTimezone tz{minuteswest, dsttime}; Kernel::sceKernelSettimeofday(0, &tz); return; } int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick) { LOG_INFO(Lib_Rtc, "called"); if (UNIX_EPOCH_TICKS >= pTick->tick) { return ORBIS_RTC_ERROR_INVALID_VALUE; } s32 ret = 0; if (pTick != nullptr) { u64 temp = pTick->tick - UNIX_EPOCH_TICKS; Kernel::OrbisKernelTimespec ts(temp / 1000000, temp % 1000000); ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_AD_NETWORK, &ts); } else { ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_AD_NETWORK, nullptr); } if (ret < 0) { return Kernel::ErrnoToSceKernelError(*Kernel::__Error()); } return ret; } int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { LOG_INFO(Lib_Rtc, "called"); if (UNIX_EPOCH_TICKS >= pTick->tick) { return ORBIS_RTC_ERROR_INVALID_VALUE; } s32 ret = 0; if (pTick != nullptr) { u64 temp = pTick->tick - UNIX_EPOCH_TICKS; Kernel::OrbisKernelTimespec ts(temp / 1000000, temp % 1000000); ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_DEBUG_NETWORK, &ts); } else { ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_DEBUG_NETWORK, nullptr); } if (ret < 0) { return Kernel::ErrnoToSceKernelError(*Kernel::__Error()); } return ret; } int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick) { LOG_INFO(Lib_Rtc, "called"); if (UNIX_EPOCH_TICKS >= pTick->tick) { return ORBIS_RTC_ERROR_INVALID_VALUE; } s32 ret = 0; if (pTick != nullptr) { u64 temp = pTick->tick - UNIX_EPOCH_TICKS; Kernel::OrbisKernelTimespec ts(temp / 1000000, temp % 1000000); ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_NETWORK, &ts); } else { ret = Kernel::posix_clock_settime(ORBIS_RTC_CLOCK_ID_NETWORK, nullptr); } if (ret < 0) { return Kernel::ErrnoToSceKernelError(*Kernel::__Error()); } return ret; } int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick) { LOG_INFO(Lib_Rtc, "called"); if (pTick == nullptr) { return ORBIS_RTC_ERROR_INVALID_POINTER; } if (UNIX_EPOCH_TICKS >= pTick->tick) { return ORBIS_RTC_ERROR_INVALID_VALUE; } s64 temp = pTick->tick - UNIX_EPOCH_TICKS; Kernel::OrbisKernelTimeval tv(temp / 1000000, temp % 1000000); return Kernel::sceKernelSettimeofday(&tv, nullptr); } int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTime->microsecond = 0; pTime->second = (dosTime << 1) & 0x3e; pTime->minute = (dosTime >> 5) & 0x3f; pTime->hour = (dosTime & 0xf800) >> 0xb; int16_t days = dosTime >> 0x10; pTime->day = days & 0x1f; pTime->month = (days >> 5) & 0x0f; pTime->year = (days >> 9) + 1980; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr || pTick == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; u32 ly, ld, lm, j; u64 days, msec; days = pTick->tick / 86400000000; msec = pTick->tick % 86400000000; days += 307; j = (days << 2) - 1; ly = j / 146097; j -= (146097 * ly); ld = j >> 2; j = ((ld << 2) + 3) / 1461; ld = (((ld << 2) + 7) - 1461 * j) >> 2; lm = (5 * ld - 3) / 153; ld = (5 * ld + 2 - 153 * lm) / 5; ly = 100 * ly + j; if (lm < 10) { lm += 3; } else { lm -= 9; ly++; } pTime->year = ly; pTime->month = lm; pTime->day = ld; pTime->hour = msec / 3600000000; msec %= 3600000000; pTime->minute = msec / 60000000; msec %= 60000000; pTime->second = msec / 1000000; msec %= 1000000; pTime->microsecond = msec; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; int sdk_version; int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version); if (sdkResult != ORBIS_OK) { sdk_version = 0; } OrbisRtcTick newTick; if (sdk_version < 0x3000000) { newTick.tick = (llTime & 0xffffffff) * 1000000; } else { if (llTime < 0) { return ORBIS_RTC_ERROR_INVALID_VALUE; } newTick.tick = llTime * 1000000; } newTick.tick += UNIX_EPOCH_TICKS; sceRtcSetTick(pTime, &newTick); return ORBIS_OK; } int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time) { LOG_TRACE(Lib_Rtc, "called"); if (pTime == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; u64 convertedTime = (ulWin32Time / 10) + WIN32_FILETIME_EPOCH_TICKS; OrbisRtcTick convertedTick; convertedTick.tick = convertedTime; sceRtcSetTick(pTime, &convertedTick); return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = (lAdd * 86400000000) + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = (int64_t(lAdd) * 3600000000LL) + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = lAdd + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (!pTick1 || !pTick2) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = pTick2->tick + (lAdd * 60000000ULL); return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; if (lAdd == 0) { pTick1->tick = pTick2->tick; return ORBIS_OK; } OrbisRtcDateTime time; s64 s; s64 tempMonth; sceRtcSetTick(&time, pTick1); if (lAdd >= 0) { s = 1; } else { s = -1; } time.year += (lAdd / 12); tempMonth = time.month + (lAdd % 12) - 1; if (tempMonth > 11 || tempMonth < 0) { tempMonth -= s * 12; time.year += s; } time.month = tempMonth + 1; using namespace std::chrono; year chronoYear = year(time.year); month chronoMonth = month(time.month); int lastDay = static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day())); if (time.day > lastDay) { time.day = lastDay; } int timeIsValid = sceRtcCheckValid(&time); if (timeIsValid == ORBIS_OK) { sceRtcGetTick(&time, pTick1); } else { return timeIsValid; } return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = (lAdd * 1000000) + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = lAdd + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; pTick1->tick = (int64_t(lAdd) * 604800000000LL) + pTick2->tick; return ORBIS_OK; } int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { LOG_TRACE(Lib_Rtc, "called"); if (pTick1 == nullptr || pTick2 == nullptr) return ORBIS_RTC_ERROR_INVALID_POINTER; if (lAdd == 0) { pTick1->tick = pTick2->tick; return ORBIS_OK; } OrbisRtcDateTime time{}; sceRtcSetTick(&time, pTick2); time.year += lAdd; int result = sceRtcCheckValid(&time); if (result != ORBIS_OK) return result; sceRtcGetTick(&time, pTick1); return ORBIS_OK; } void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("lPEBYdVX0XQ", "libSceRtc", 1, "libSceRtc", sceRtcCheckValid); LIB_FUNCTION("fNaZ4DbzHAE", "libSceRtc", 1, "libSceRtc", sceRtcCompareTick); LIB_FUNCTION("8Yr143yEnRo", "libSceRtc", 1, "libSceRtc", sceRtcConvertLocalTimeToUtc); LIB_FUNCTION("M1TvFst-jrM", "libSceRtc", 1, "libSceRtc", sceRtcConvertUtcToLocalTime); LIB_FUNCTION("8SljQx6pDP8", "libSceRtc", 1, "libSceRtc", sceRtcEnd); LIB_FUNCTION("eiuobaF-hK4", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC2822); LIB_FUNCTION("AxHBk3eat04", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC2822LocalTime); LIB_FUNCTION("WJ3rqFwymew", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC3339); LIB_FUNCTION("DwuHIlLGW8I", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC3339LocalTime); LIB_FUNCTION("lja0nNPWojg", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC3339Precise); LIB_FUNCTION("tOZ6fwwHZOA", "libSceRtc", 1, "libSceRtc", sceRtcFormatRFC3339PreciseLocalTime); LIB_FUNCTION("LN3Zcb72Q0c", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentAdNetworkTick); LIB_FUNCTION("8lfvnRMqwEM", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentClock); LIB_FUNCTION("ZPD1YOKI+Kw", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentClockLocalTime); LIB_FUNCTION("Ot1DE3gif84", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentDebugNetworkTick); LIB_FUNCTION("zO9UL3qIINQ", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentNetworkTick); LIB_FUNCTION("HWxHOdbM-Pg", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentRawNetworkTick); LIB_FUNCTION("18B2NS1y9UU", "libSceRtc", 1, "libSceRtc", sceRtcGetCurrentTick); LIB_FUNCTION("CyIK-i4XdgQ", "libSceRtc", 1, "libSceRtc", sceRtcGetDayOfWeek); LIB_FUNCTION("3O7Ln8AqJ1o", "libSceRtc", 1, "libSceRtc", sceRtcGetDaysInMonth); LIB_FUNCTION("E7AR4o7Ny7E", "libSceRtc", 1, "libSceRtc", sceRtcGetDosTime); LIB_FUNCTION("8w-H19ip48I", "libSceRtc", 1, "libSceRtc", sceRtcGetTick); LIB_FUNCTION("jMNwqYr4R-k", "libSceRtc", 1, "libSceRtc", sceRtcGetTickResolution); LIB_FUNCTION("BtqmpTRXHgk", "libSceRtc", 1, "libSceRtc", sceRtcGetTime_t); LIB_FUNCTION("jfRO0uTjtzA", "libSceRtc", 1, "libSceRtc", sceRtcGetWin32FileTime); LIB_FUNCTION("LlodCMDbk3o", "libSceRtc", 1, "libSceRtc", sceRtcInit); LIB_FUNCTION("Ug8pCwQvh0c", "libSceRtc", 1, "libSceRtc", sceRtcIsLeapYear); LIB_FUNCTION("NxEI1KByvCI", "libSceRtc", 1, "libSceRtc", sceRtcParseDateTime); LIB_FUNCTION("99bMGglFW3I", "libSceRtc", 1, "libSceRtc", sceRtcParseRFC3339); LIB_FUNCTION("fFLgmNUpChg", "libSceRtc", 1, "libSceRtc", sceRtcSetConf); LIB_FUNCTION("sV2tK+yOhBU", "libSceRtc", 1, "libSceRtc", sceRtcSetCurrentAdNetworkTick); LIB_FUNCTION("VLDUPKmw5L8", "libSceRtc", 1, "libSceRtc", sceRtcSetCurrentDebugNetworkTick); LIB_FUNCTION("qhDBtIo+auw", "libSceRtc", 1, "libSceRtc", sceRtcSetCurrentNetworkTick); LIB_FUNCTION("d4fHLCGmY80", "libSceRtc", 1, "libSceRtc", sceRtcSetCurrentTick); LIB_FUNCTION("aYPCd1cChyg", "libSceRtc", 1, "libSceRtc", sceRtcSetDosTime); LIB_FUNCTION("ueega6v3GUw", "libSceRtc", 1, "libSceRtc", sceRtcSetTick); LIB_FUNCTION("bDEVVP4bTjQ", "libSceRtc", 1, "libSceRtc", sceRtcSetTime_t); LIB_FUNCTION("n5JiAJXsbcs", "libSceRtc", 1, "libSceRtc", sceRtcSetWin32FileTime); LIB_FUNCTION("NR1J0N7L2xY", "libSceRtc", 1, "libSceRtc", sceRtcTickAddDays); LIB_FUNCTION("MDc5cd8HfCA", "libSceRtc", 1, "libSceRtc", sceRtcTickAddHours); LIB_FUNCTION("XPIiw58C+GM", "libSceRtc", 1, "libSceRtc", sceRtcTickAddMicroseconds); LIB_FUNCTION("mn-tf4QiFzk", "libSceRtc", 1, "libSceRtc", sceRtcTickAddMinutes); LIB_FUNCTION("CL6y9q-XbuQ", "libSceRtc", 1, "libSceRtc", sceRtcTickAddMonths); LIB_FUNCTION("07O525HgICs", "libSceRtc", 1, "libSceRtc", sceRtcTickAddSeconds); LIB_FUNCTION("AqVMssr52Rc", "libSceRtc", 1, "libSceRtc", sceRtcTickAddTicks); LIB_FUNCTION("gI4t194c2W8", "libSceRtc", 1, "libSceRtc", sceRtcTickAddWeeks); LIB_FUNCTION("-5y2uJ62qS8", "libSceRtc", 1, "libSceRtc", sceRtcTickAddYears); }; } // namespace Libraries::Rtc