diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 15ababe5db..000a9d0c0b 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -579,8 +579,8 @@ int APS5_VABI sceRtcFormatRFC3339(char* dateTime, const RtcTick* utc, int timeZo const auto shift = static_cast(timeZoneMinutes) * TICKS_PER_MINUTE; if (rangeChecked() && (shift < 0 ? utc->tick < static_cast(-shift) : utc->tick > std::numeric_limits::max() - static_cast(shift))) return SCE_RTC_ERROR_INVALID_VALUE; const RtcTick local{utc->tick + static_cast(shift)}; - if (local.tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_YEAR; const RtcDateTime time = fromTick(local.tick); + if (time.year < 1 || time.year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; int written = std::snprintf(dateTime, 23, "%04u-%02u-%02uT%02u:%02u:%02u.%02u", time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond / 10000); if (timeZoneMinutes == 0) { std::snprintf(dateTime + written, 2, "Z"); @@ -611,11 +611,11 @@ int APS5_VABI sceRtcFormatRFC2822(char* dateTime, const RtcTick* utc, int timeZo } if (timeZoneMinutes < -1439 || timeZoneMinutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; const RtcTick local{utc->tick + static_cast(static_cast(timeZoneMinutes) * TICKS_PER_MINUTE)}; - if (static_cast(local.tick) > static_cast(MAX_TICK)) return SCE_RTC_ERROR_INVALID_YEAR; - if (static_cast(utc->tick) < 0 || static_cast(local.tick) < 0) return SCE_RTC_ERROR_INVALID_VALUE; const RtcDateTime time = fromTick(local.tick); + if (static_cast(local.tick) >= 0 && (time.year < 1 || time.year > 9999)) return SCE_RTC_ERROR_INVALID_YEAR; + if (static_cast(utc->tick) < 0 || static_cast(local.tick) < 0) return SCE_RTC_ERROR_INVALID_VALUE; const int offset = timeZoneMinutes < 0 ? -timeZoneMinutes : timeZoneMinutes; - std::snprintf(dateTime, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(local.tick / TICKS_PER_DAY + 1) % 7], time.day, + std::snprintf(dateTime, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(daysFromCivil(time.year, time.month, time.day) + 1) % 7], time.day, MONTH_NAMES[time.month - 1], time.year, time.hour, time.minute, time.second, timeZoneMinutes < 0 ? '-' : '+', offset / 60, offset % 60); return 0; } @@ -757,11 +757,11 @@ int APS5_VABI sceRtcFormatRFC3339Precise(char* date_time, const RtcTick* utc, in if (utc->tick < shift) return SCE_RTC_ERROR_INVALID_VALUE; localTick = utc->tick - shift; } else { - if (utc->tick > MAX_TICK || MAX_TICK - utc->tick < shift) return SCE_RTC_ERROR_INVALID_YEAR; + if (utc->tick > std::numeric_limits::max() - shift) return SCE_RTC_ERROR_INVALID_VALUE; localTick = utc->tick + shift; } - if (localTick > MAX_TICK) return SCE_RTC_ERROR_INVALID_YEAR; const RtcDateTime time = fromTick(localTick); + if (time.year < 1 || time.year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; int written = std::snprintf(date_time, 20, "%04u-%02u-%02uT%02u:%02u:%02u", time.year, time.month, time.day, time.hour, time.minute, time.second); if (precision != 0) { std::uint32_t divisor = 1; diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 57a213635e..262b0e719f 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -90,6 +90,18 @@ int main(int argc, char** argv) { char text[64]; RtcTick tick{std::numeric_limits::max()}; Require(sceRtcFormatRFC3339(text, &tick, 1) == static_cast(0x80B50003)); + Require(sceRtcFormatRFC3339Precise(text, &tick, 60, 6) == static_cast(0x80B50003)); + const RtcTick year65536{2068084742400000000ull}, year65537{2068116364800000000ull}, year75535{2383622726400000000ull}, distant{4239646359910459728ull}; + Require(sceRtcFormatRFC3339(text, &year65536, 0) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC2822(text, &year65536, 0) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC3339Precise(text, &year65536, 0, 6) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC3339(text, &year65537, 60) == 0 && std::strcmp(text, "0001-01-01T01:00:00.00+01:00") == 0); + Require(sceRtcFormatRFC2822(text, &year65537, 0) == 0 && std::strcmp(text, "Mon, 01 Jan 0001 00:00:00 +0000") == 0); + Require(sceRtcFormatRFC3339Precise(text, &year65537, 60, 6) == 0 && std::strcmp(text, "0001-01-01T01:00:00.000000+01:00") == 0); + Require(sceRtcFormatRFC3339(text, &year75535, -60) == 0 && std::strcmp(text, "9998-12-31T23:00:00.00-01:00") == 0); + Require(sceRtcFormatRFC2822(text, &year75535, 0) == 0 && std::strcmp(text, "Fri, 01 Jan 9999 00:00:00 +0000") == 0); + Require(sceRtcFormatRFC3339Precise(text, &year75535, -60, 6) == 0 && std::strcmp(text, "9998-12-31T23:00:00.000000-01:00") == 0); + Require(sceRtcFormatRFC2822(text, &distant, -821) == 0 && std::strcmp(text, "Mon, 17 Jan 3278 10:44:10 -1341") == 0); return 0; } EnterTitleDirectory("CUSA00000"); diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index 1d60af4288..89ef917a7b 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -9,6 +9,7 @@ - [sceRtcParseDateTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - a blank zone after seconds is accepted. With fully allocated input storage on PS5 firmware 13.42 and again on 13.60, the same NUL-terminated text returned `BAD_PARSE` when the byte after its terminator was zero and succeeded when that byte was uppercase `T`. This dependence on storage after the terminator is not modeled; controlled backing-byte measurements in a title process are unavailable. - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured on PS4 firmware 10.71, not on a PS5: the RFC 2822 text, `INVALID_YEAR` when the zone-shifted tick is past 9999 as a signed value, otherwise `INVALID_VALUE` when the source tick or the zone-shifted tick is negative as a signed value, and the local-time variants using the offset in effect at the given instant (+01:00 in January, +02:00 in July on a Central European console). A null tick formatting the current time and the ±1439 minute offset limit come from the PS5 measurement in the next entry and are not measured on the PS4 - [sceRtcFormatRFC3339, sceRtcGetDayOfWeek, sceRtcSetTime_t](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured on PS4 firmware 10.71 from processes compiled with SDK versions 0x01000051, 0x02ffffff, 0x03000000 and 0x05050001, not on a PS5. From 0x03000000 on: `FormatRFC3339` returns `INVALID_VALUE` when adding the zone shift to the tick overflows or underflows 64 bits (earlier SDKs wrap); `GetDayOfWeek` checks the month before the year and rejects a year past 9999 (earlier SDKs check the year first, with no upper bound); `SetTime_t` rejects negative seconds with `INVALID_VALUE` and converts all 64 bits (earlier SDKs take the low 32 bits). 0x02ffffff still behaves the earlier way and 0x03000000 the later way. The earlier rules apply only to a PS4 title (a CUSA title ID): a PS5 title has its own SDK numbering, so it always gets the later rules, which PS5 firmware 13.60 follows; there `SetTime_t` checks for negative seconds before a null pointer. +- [sceRtcFormatRFC3339, sceRtcFormatRFC2822, sceRtcFormatRFC3339Precise](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the zone-shifted tick is converted to a date with a 16-bit year, as `SceRtcDateTime` stores it, and only that year is checked, so past year 65535 it wraps: a tick in year 65536 returns `0x80B50008` and one in year 65537 formats as year 0001, with the RFC 2822 weekday of the wrapped date. `sceRtcFormatRFC3339Precise` returns `0x80B50003` when an eastern zone overflows 64 bits. From firmware 13.42's module run natively: the year rule with SDK versions 0x01000051, 0x03000000 and 0x0A000000, the overflow result with 0x03000000 and 0x0A000000 (0x01000051 wraps instead); not measured on a console - [sceRtcGetDosTime, sceRtcSetDosTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured in a payload process on a PS5 (firmware 13.60), and all 33 cases match: the standard FAT layout with seconds halved; a year before 1980 writes 0 and one after 2107 writes `0xff9fbf7d`, both returning `INVALID_YEAR`; year 0 and an invalid month, day, hour, minute, second or microsecond return their own error without writing; a null argument returns `INVALID_POINTER`; `SetDosTime` decodes every field unchecked (`0xffffffff` gives 2107-15-31 31:63:62) and clears the microseconds. What a title process sees is assumed to be the same - [sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp): the native wrappers obtain the zone from a kernel timezone query, while the host uses the offset at the supplied instant. The kernel timezone configuration and its failure paths are not modeled. Native formatting confirms that null tick pointers select current time, RFC3339 uses two fractional digits, and explicit offsets are limited to -1439 through 1439 minutes. - [Local time beyond the host's range](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - when the host cannot convert an instant to local time (on Windows, before 1970 or after 3000), the zone offset is taken from the same instant moved by whole 400-year Gregorian cycles into the host's range, which keeps a POSIX `TZ` rule's offset; a zone with historical transitions gets its current rule there, not the console's offset, which is not measured outside the probe's -03:00 zone