This commit is contained in:
georgemoralis 2026-01-30 09:45:30 +00:00 committed by GitHub
commit 53b9212b34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 234 additions and 116 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
@ -12,14 +12,61 @@
#include "core/libraries/rtc/rtc_error.h"
namespace Libraries::Rtc {
int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) {
LOG_TRACE(Lib_Rtc, "called");
if (pTime == nullptr)
/*
* Internal code
*/
int _sceRtcTickSubMicroseconds(OrbisRtcTick* pTick0, const OrbisRtcTick* pTick1,
int64_t lSub) { // FUN_01003270
if (!pTick0 || !pTick1)
return ORBIS_RTC_ERROR_INVALID_POINTER;
if (pTime->year == 0 || pTime->year > 9999)
if (lSub == 0) {
pTick0->tick = pTick1->tick;
return ORBIS_OK;
}
uint64_t t1 = pTick1->tick;
if (lSub < 0) {
if (t1 < static_cast<uint64_t>(-lSub))
return ORBIS_RTC_ERROR_INVALID_VALUE;
} else {
if ((~t1) < static_cast<uint64_t>(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)
@ -28,13 +75,9 @@ int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) {
if (pTime->day == 0)
return ORBIS_RTC_ERROR_INVALID_DAY;
using namespace std::chrono;
year chronoYear = year(pTime->year);
month chronoMonth = month(pTime->month);
int lastDay =
static_cast<int>(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day()));
int leap = leap_year(year);
if (pTime->day > lastDay)
if (pTime->day > MonthDays[leap][pTime->month])
return ORBIS_RTC_ERROR_INVALID_DAY;
if (pTime->hour >= 24)
@ -52,53 +95,55 @@ int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2) {
LOG_TRACE(Lib_Rtc, "called");
if (pTick1 == nullptr || pTick2 == nullptr)
int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick0, OrbisRtcTick* pTick1) {
if (pTick0 == nullptr || pTick1 == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
if (pTick1->tick <= pTick2->tick)
return 1;
else
return 0;
int result = -1;
return ORBIS_FAIL;
}
int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc) {
LOG_TRACE(Lib_Rtc, "called");
if (pTickLocal == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
time_t seconds;
Kernel::OrbisKernelTimezone timezone;
int convertValue = Kernel::sceKernelConvertLocaltimeToUtc(
(pTickLocal->tick - UNIX_EPOCH_TICKS) / 1000000, 0xffffffff, &seconds, &timezone, 0);
if (convertValue >= 0) {
convertValue = sceRtcTickAddMinutes(
pTickUtc, pTickLocal, -(((timezone.tz_dsttime * 60) - timezone.tz_minuteswest)));
if (pTick1->tick <= pTick0->tick) {
result = (-((int)(pTick1->tick < pTick0->tick))) & 1;
}
return convertValue;
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::OrbisKernelTimezone timeZone;
int returnValue = Kernel::sceKernelGettimezone(&timeZone);
Kernel::OrbisTimesec tsec{};
time_t local_time{};
sceRtcTickAddMinutes(pTickLocal, pTickUtc,
-(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)));
// 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;
return 0;
// 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() {
@ -428,28 +473,33 @@ int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone) {
int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime) {
LOG_TRACE(Lib_Rtc, "called");
if (!pTime)
return ORBIS_RTC_ERROR_INVALID_POINTER;
if (pTime == nullptr)
return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED;
Kernel::OrbisKernelTimespec ts{};
int result = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &ts);
if (result < 0)
return result;
Kernel::OrbisKernelTimezone timeZone;
int returnValue = Kernel::sceKernelGettimezone(&timeZone);
uint64_t _tick =
static_cast<uint64_t>(ts.tv_sec) * 1000000ULL + static_cast<uint64_t>(ts.tv_nsec) / 1000ULL;
uint64_t tick = _tick + UNIX_EPOCH_TICKS;
if (returnValue >= 0) {
Kernel::OrbisKernelTimespec clocktime;
time_t local_time{};
Kernel::OrbisTimesec tzsec{};
result = sceKernelConvertUtcToLocaltime(_tick / 1000000ULL, &local_time, &tzsec, nullptr);
if (result < 0)
return result;
// calculate total timezone offset for converting UTC to local time
uint64_t tzOffset = -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60));
OrbisRtcTick rtcTick{tick};
if (returnValue >= 0) {
OrbisRtcTick newTick;
sceRtcGetCurrentTick(&newTick);
sceRtcTickAddMinutes(&newTick, &newTick, tzOffset);
sceRtcSetTick(pTime, &newTick);
}
}
int64_t offset_minutes = static_cast<s32>(tzsec.dst_sec + tzsec.west_sec) / 60;
result = sceRtcTickAddMinutes(&rtcTick, &rtcTick, offset_minutes);
if (result < 0)
return result;
return returnValue;
result = sceRtcSetTick(pTime, &rtcTick);
return result;
}
int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick) {
@ -526,37 +576,35 @@ int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day) {
LOG_TRACE(Lib_Rtc, "called");
int sdk_version = 0;
int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version);
if (sdkResult != ORBIS_OK) {
if (Kernel::sceKernelGetCompiledSdkVersion(&sdk_version) != ORBIS_OK)
sdk_version = 0;
}
// Year/month validation
if (sdk_version < 0x3000000) {
if (year < 1) {
if (year < 1)
return ORBIS_RTC_ERROR_INVALID_YEAR;
}
if (month > 12 || month <= 0) {
if (month <= 0 || month > 12)
return ORBIS_RTC_ERROR_INVALID_MONTH;
}
} else {
if (year > 9999 || year < 1) {
if (year < 1 || year > 9999)
return ORBIS_RTC_ERROR_INVALID_YEAR;
}
if (month > 12 || month <= 0) {
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;
std::chrono::sys_days chrono_time{std::chrono::year(year) / std::chrono::month(month) /
std::chrono::day(day)};
std::chrono::weekday chrono_weekday{chrono_time};
// Zeller's congruence adjustment
if (month < 3) {
month += 12;
year -= 1;
}
return chrono_weekday.c_encoding();
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) {
@ -564,16 +612,11 @@ int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month) {
if (year <= 0)
return ORBIS_RTC_ERROR_INVALID_YEAR;
if (month <= 0 || month > 12)
return ORBIS_RTC_ERROR_INVALID_MONTH;
std::chrono::year chronoYear = std::chrono::year(year);
std::chrono::month chronoMonth = std::chrono::month(month);
int lastDay = static_cast<int>(unsigned(
std::chrono::year_month_day_last{chronoYear / chronoMonth / std::chrono::last}.day()));
return lastDay;
bool isLeap = leap_year(year);
return MonthDays[isLeap ? 1 : 0][month];
}
int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, u32* dosTime) {
@ -582,19 +625,29 @@ int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, u32* dosTime) {
if (pTime == nullptr || dosTime == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
int isValid = sceRtcCheckValid(pTime);
if (isValid != ORBIS_OK) {
return isValid;
// 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;
}
*dosTime |= (pTime->second / 2) & 0x1F;
*dosTime |= (pTime->minute & 0x3F) << 5;
*dosTime |= (pTime->hour & 0x1F) << 11;
*dosTime |= (pTime->day & 0x1F) << 16;
*dosTime |= (pTime->month & 0x0F) << 21;
*dosTime |= ((pTime->year - 1980) & 0x7F) << 25;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) {
@ -1040,7 +1093,7 @@ int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2,
if (pTick1 == nullptr || pTick2 == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
pTick1->tick = (lAdd * 3600000000) + pTick2->tick;
pTick1->tick = (int64_t(lAdd) * 3600000000LL) + pTick2->tick;
return ORBIS_OK;
}
@ -1060,10 +1113,10 @@ int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* p
int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) {
LOG_TRACE(Lib_Rtc, "called");
if (pTick1 == nullptr || pTick2 == nullptr)
if (!pTick1 || !pTick2)
return ORBIS_RTC_ERROR_INVALID_POINTER;
pTick1->tick = (lAdd * 60000000) + pTick2->tick;
pTick1->tick = pTick2->tick + (lAdd * 60000000ULL);
return ORBIS_OK;
}
@ -1149,7 +1202,7 @@ int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2,
if (pTick1 == nullptr || pTick2 == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
pTick1->tick = (lAdd * 604800000000) + pTick2->tick;
pTick1->tick = (int64_t(lAdd) * 604800000000LL) + pTick2->tick;
return ORBIS_OK;
}
@ -1160,24 +1213,21 @@ int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2,
if (pTick1 == nullptr || pTick2 == nullptr)
return ORBIS_RTC_ERROR_INVALID_POINTER;
OrbisRtcDateTime time;
if (lAdd == 0) {
pTick1->tick = pTick2->tick;
return ORBIS_OK;
}
sceRtcSetTick(&time, pTick1);
OrbisRtcDateTime time{};
sceRtcSetTick(&time, pTick2);
time.year += lAdd;
int timeIsValid = sceRtcCheckValid(&time);
if (timeIsValid == ORBIS_OK) {
sceRtcGetTick(&time, pTick1);
} else {
return timeIsValid;
}
int result = sceRtcCheckValid(&time);
if (result != ORBIS_OK)
return result;
sceRtcGetTick(&time, pTick1);
return ORBIS_OK;
}

View File

@ -11,6 +11,15 @@
#include "core/libraries/system/systemservice_error.h"
#include "emulator.h"
#ifdef WIN32
#include <windows.h>
#else // Linux / macOS
#include <clocale>
#include <cstring>
#include <ctime>
#include <langinfo.h>
#endif
namespace Libraries::SystemService {
bool g_splash_status{true};
@ -1909,6 +1918,20 @@ int PS4_SYSV_ABI sceSystemServiceNavigateToGoHome() {
return ORBIS_OK;
}
#ifdef WIN32
struct SwTimezone {
int tz_minuteswest;
int tz_dsttime;
};
static void Swgettimezone(SwTimezone* z) {
TIME_ZONE_INFORMATION tz{};
DWORD state = GetTimeZoneInformation(&tz);
z->tz_minuteswest = tz.Bias;
z->tz_dsttime = (state == TIME_ZONE_ID_DAYLIGHT) ? 1 : 0;
}
#endif
s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(OrbisSystemServiceParamId param_id, int* value) {
// TODO this probably should be stored in config for UI configuration
LOG_DEBUG(Lib_SystemService, "called param_id {}", u32(param_id));
@ -1923,15 +1946,60 @@ s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(OrbisSystemServiceParamId param_id,
case OrbisSystemServiceParamId::DateFormat:
*value = u32(OrbisSystemParamDateFormat::FmtDDMMYYYY);
break;
case OrbisSystemServiceParamId::TimeFormat:
*value = u32(OrbisSystemParamTimeFormat::Fmt24Hour);
#ifdef WIN32
case OrbisSystemServiceParamId::TimeFormat: {
char format[2] = {0};
GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ILDATE, format, sizeof(format));
if (format[0] == '0') {
*value = u32(OrbisSystemParamTimeFormat::Fmt12Hour);
} else {
*value = u32(OrbisSystemParamTimeFormat::Fmt24Hour);
}
break;
case OrbisSystemServiceParamId::TimeZone:
*value = +120;
}
case OrbisSystemServiceParamId::TimeZone: {
SwTimezone z{};
Swgettimezone(&z);
*value = z.tz_minuteswest;
break;
case OrbisSystemServiceParamId::Summertime:
*value = 1;
}
case OrbisSystemServiceParamId::Summertime: {
SwTimezone z{};
Swgettimezone(&z);
*value = z.tz_dsttime;
break;
}
#else
case OrbisSystemServiceParamId::TimeFormat: {
setlocale(LC_TIME, "");
const char* fmt = nl_langinfo(T_FMT);
bool is24h = !fmt || std::strstr(fmt, "%I") == nullptr;
*value = is24h ? u32(OrbisSystemParamTimeFormat::Fmt24Hour)
: u32(OrbisSystemParamTimeFormat::Fmt12Hour);
break;
}
case OrbisSystemServiceParamId::TimeZone: {
std::time_t now = std::time(nullptr);
std::tm local{};
std::tm utc{};
localtime_r(&now, &local);
gmtime_r(&now, &utc);
int offset = (local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min);
if (local.tm_yday != utc.tm_yday) {
offset += (local.tm_yday > utc.tm_yday) ? 1440 : -1440;
}
*value = offset;
break;
}
case OrbisSystemServiceParamId::Summertime: {
std::time_t now = std::time(nullptr);
std::tm local{};
localtime_r(&now, &local);
*value = (local.tm_isdst > 0) ? 1 : 0;
break;
}
#endif
case OrbisSystemServiceParamId::GameParentalLevel:
*value = u32(OrbisSystemParamGameParentalLevel::Off);
break;