From 32dd014efe0a5a37190e4ded9eb9a4ad99e0ee95 Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Wed, 7 Oct 2026 19:00:30 -0300 Subject: [PATCH] fix(rtc): parse ISO text in sceRtcParseDateTime by the RFC 3339 rules On a PS5 (firmware 13.60) every input that starts with a digit gets the same result and tick from sceRtcParseDateTime as from sceRtcParseRFC3339, except that leading spaces and tabs are skipped first. A string without a zone, or with a space instead of T, is a parse error rather than UTC. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceRtc/Export.cpp | 43 +++--------------------------- core/libs/tests/GuestRtc.cpp | 20 ++++++++------ docs/dev/TechnicalDebt.md | 2 +- 3 files changed, 17 insertions(+), 48 deletions(-) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index e1e6656cd..a2eb63578 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -149,23 +149,6 @@ bool parseDigits(const char*& cursor, int count, int& value) { return true; } -bool parseZoneSuffix(const char*& cursor, std::int64_t& offsetMinutes) { - offsetMinutes = 0; - if (*cursor == 'Z' || *cursor == 'z') { - ++cursor; - return true; - } - if (*cursor == '+' || *cursor == '-') { - const int sign = *cursor++ == '-' ? -1 : 1; - int offsetHours = 0, offsetMinute = 0; - if (!parseDigits(cursor, 2, offsetHours) || *cursor++ != ':' || !parseDigits(cursor, 2, offsetMinute)) return false; - if (offsetHours > 23 || offsetMinute > 59) return false; - offsetMinutes = sign * (offsetHours * 60 + offsetMinute); - return true; - } - return false; -} - bool parseName(const char*& cursor, const char* const* names, int count, int& index) { for (index = 0; index < count; ++index) { if (std::strncmp(cursor, names[index], 3) == 0) { @@ -229,26 +212,6 @@ int parseRfc3339(RtcTick* utc, const char* cursor) { return storeParsedTick(utc, time, offsetMinutes); } -bool parseIsoDateTime(const char* cursor, RtcDateTime& time, std::int64_t& offsetMinutes) { - int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0; - if (!parseDigits(cursor, 4, year) || *cursor++ != '-' || !parseDigits(cursor, 2, month) || *cursor++ != '-' || !parseDigits(cursor, 2, day)) return false; - if (*cursor != 'T' && *cursor != 't' && *cursor != ' ') return false; - ++cursor; - if (!parseClock(cursor, hour, minute, second)) return false; - std::uint32_t microsecond = 0; - if (*cursor == '.') { - ++cursor; - std::uint32_t scale = 100000; - if (*cursor < '0' || *cursor > '9') return false; - for (; *cursor >= '0' && *cursor <= '9'; ++cursor, scale /= 10) microsecond += static_cast(*cursor - '0') * scale; - } - offsetMinutes = 0; - if (*cursor != 0 && !parseZoneSuffix(cursor, offsetMinutes)) return false; - if (*cursor != 0) return false; - time = makeDateTime(year, month, day, hour, minute, second, microsecond); - return true; -} - bool parseRfc2822DateTime(const char* cursor, RtcDateTime& time, std::int64_t& offsetMinutes) { int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0; if (!parseWeekday(cursor) || *cursor++ != ',' || *cursor++ != ' ' || !parseDigits(cursor, 2, day) || *cursor++ != ' ') return false; @@ -470,10 +433,12 @@ int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) { int APS5_VABI sceRtcParseDateTime(RtcTick* utc, const char* date_time) { if (!utc || !date_time) return SCE_RTC_ERROR_INVALID_POINTER; + const char* cursor = date_time; + while (*cursor == ' ' || *cursor == '\t') ++cursor; + if (*cursor >= '0' && *cursor <= '9') return parseRfc3339(utc, cursor); RtcDateTime time{}; std::int64_t offsetMinutes = 0; - if (!parseIsoDateTime(date_time, time, offsetMinutes) && !parseRfc2822DateTime(date_time, time, offsetMinutes) - && !parseAsctimeDateTime(date_time, time, offsetMinutes)) { + if (!parseRfc2822DateTime(date_time, time, offsetMinutes) && !parseAsctimeDateTime(date_time, time, offsetMinutes)) { NotImplemented_nid_no_patch(__func__); return SCE_RTC_ERROR_BAD_PARSE; } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index aeba3a722..7aa1c8658 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -198,12 +198,21 @@ int main() { Require(sceRtcParseRFC3339(&tick, "2024-02-29T12:34:56.789-23:59") == 0 && tick.tick == leapDayTick + 86340000000ull); Require(sceRtcParseRFC3339(nullptr, "1970-01-01T00:00:00Z") == invalidPointer); - Require(sceRtcParseDateTime(&tick, "2024-02-29T12:34:56.789") == 0 && tick.tick == leapDayTick); - Require(sceRtcParseDateTime(&tick, "2024-02-29 12:34:56") == 0 && tick.tick == leapDayTick - 789000ull); + for (const char* text : {"2024-02-29T12:34:56.789", "2024-02-29 12:34:56", "2024-02-29 12:34:56Z", "2024-02-29", "2024/02/29T12:34:56Z", "2024-02-29T12:34:56."}) { + tick.tick = 123; + Require(sceRtcParseDateTime(&tick, text) == badParse); + Require(tick.tick == 123); + } + Require(sceRtcParseDateTime(&tick, " \t 2024-02-29T12:34:56.789Z") == 0 && tick.tick == leapDayTick); + Require(sceRtcParseDateTime(&tick, "2024-02-29T12:34:56.789Zjunk") == 0 && tick.tick == leapDayTick); + Require(sceRtcParseDateTime(&tick, "2024-02-29T12:34:56.789+99:99") == 0 && tick.tick == leapDayTick - 6039ull * 60000000ull); + tick.tick = 123; + Require(sceRtcParseDateTime(&tick, "2024-02-29T12:34:60-01:00") == 0 && tick.tick == 123ull + 3600000000ull); + Require(sceRtcParseDateTime(&tick, "2024-13-01T00:00:00Z") == static_cast(0x80B50009)); Require(sceRtcParseDateTime(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick); Require(sceRtcParseDateTime(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick); Require(sceRtcParseDateTime(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick); - Require(sceRtcParseDateTime(&tick, "2023-02-29T00:00:00") == invalidDay); + Require(sceRtcParseDateTime(&tick, "2023-02-29T00:00:00Z") == invalidDay); Require(sceRtcParseDateTime(nullptr, "1970-01-01T00:00:00Z") == invalidPointer); Require(sceRtcParseDateTime(&tick, "Thu, 29 Feb 2024 12:34:56") == 0 && tick.tick == leapDayTick - 789000ull); Require(sceRtcParseDateTime(&tick, "Thu, 29 Feb 2024 12:34:56 GMT") == 0 && tick.tick == leapDayTick - 789000ull); @@ -215,11 +224,6 @@ int main() { Require(sceRtcParseDateTime(&tick, "Thu Jan 1 00:00:00 1970") == 0 && tick.tick == unixEpochTick); Require(sceRtcParseDateTime(&tick, "Thu Feb 30 00:00:00 2024") == invalidDay); const char* unparseable[] = { - "2024-02-29", - "2024/02/29T12:34:56", - "2024-02-29T12:34:56Zjunk", - "2024-02-29T12:34:56+99:99", - "2024-02-29T12:34:56.", "Thu, 29 Feb 2024 12:34:56 +01:30", "Thu, 29 Feb 2024 12:34:56 PST", "Thu, 29 Xyz 2024 12:34:56", diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 084451064..bd40d94b2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -47,7 +47,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutIsOutputSupported](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - output mode encoding unknown: every mode except the default reports not supported (0), and `sceVideoOutConfigureOutput` returns `VIDEO_OUT_ERROR_UNAVAILABLE_OUTPUT_MODE` for it, instead of returning `VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE` for values the console does not define. PPSA12544 probes `0xd000000a` and selects 7680 pixels when it is supported, 3840 otherwise - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [s_setkill](../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified -- [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`), plus the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC like the RFC 3339 path (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware +- [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - text that starts with a digit after leading spaces and tabs is parsed by the `sceRtcParseRFC3339` rules, as measured on a PS5; other text is assumed to be the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware - [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too - [sceAudio3dObjectReserve](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order