fix(libs): route DateTime zone suffix through RFC3339 path, throw on unverified shapes

This commit is contained in:
Pouare514 2026-10-05 01:10:25 +02:00
parent af20824af4
commit e67d50cb2f
5 changed files with 64 additions and 24 deletions

View File

@ -122,7 +122,7 @@ int APS5_VABI sceAudioInInput(int handle, void* dest) {
if (device != 0) {
captured = capture(device, out, bytes, deadline);
} else {
std::this_thread::sleep_until(wake);
while (Clock::now() < wake) std::this_thread::sleep_until(wake);
}
std::memset(out + captured, 0, bytes - captured);
lock.lock();

View File

@ -11,7 +11,8 @@ endif()
if(BUILD_TESTING)
add_test_executable(guest_rtc_tests ${LIBS_INCLUDE_DIR}/tests/GuestRtc.cpp)
target_include_directories(guest_rtc_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_rtc_tests PRIVATE libSceRtc)
target_link_libraries(guest_rtc_tests PRIVATE libSceRtc libc)
configure_windows_unwind(guest_rtc_tests)
add_test(NAME guest_rtc COMMAND guest_rtc_tests)
if(WIN32)
set_tests_properties(guest_rtc PROPERTIES ENVIRONMENT_MODIFICATION

View File

@ -139,6 +139,23 @@ 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;
}
}
extern "C" {
@ -282,17 +299,7 @@ int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) {
for (; *cursor >= '0' && *cursor <= '9'; ++cursor, scale /= 10) microsecond += static_cast<std::uint32_t>(*cursor - '0') * scale;
}
std::int64_t offsetMinutes = 0;
if (*cursor == 'Z' || *cursor == 'z') {
++cursor;
} else 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 SCE_RTC_ERROR_BAD_PARSE;
if (offsetHours > 23 || offsetMinute > 59) return SCE_RTC_ERROR_BAD_PARSE;
offsetMinutes = sign * (offsetHours * 60 + offsetMinute);
} else {
return SCE_RTC_ERROR_BAD_PARSE;
}
if (!parseZoneSuffix(cursor, offsetMinutes)) return SCE_RTC_ERROR_BAD_PARSE;
if (*cursor != 0) return SCE_RTC_ERROR_BAD_PARSE;
const RtcDateTime time{static_cast<std::uint16_t>(year), static_cast<std::uint16_t>(month), static_cast<std::uint16_t>(day),
static_cast<std::uint16_t>(hour), static_cast<std::uint16_t>(minute), static_cast<std::uint16_t>(second), microsecond};
@ -305,23 +312,29 @@ 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;
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 SCE_RTC_ERROR_BAD_PARSE;
if (*cursor != 'T' && *cursor != 't' && *cursor != ' ') return SCE_RTC_ERROR_BAD_PARSE;
++cursor;
if (!parseDigits(cursor, 2, hour) || *cursor++ != ':' || !parseDigits(cursor, 2, minute) || *cursor++ != ':' || !parseDigits(cursor, 2, second)) return SCE_RTC_ERROR_BAD_PARSE;
bool ok = parseDigits(cursor, 4, year) && *cursor++ == '-' && parseDigits(cursor, 2, month) && *cursor++ == '-' && parseDigits(cursor, 2, day);
ok = ok && (*cursor == 'T' || *cursor == 't' || *cursor == ' ');
if (ok) ++cursor;
ok = ok && parseDigits(cursor, 2, hour) && *cursor++ == ':' && parseDigits(cursor, 2, minute) && *cursor++ == ':' && parseDigits(cursor, 2, second);
std::uint32_t microsecond = 0;
if (*cursor == '.') {
if (ok && *cursor == '.') {
++cursor;
std::uint32_t scale = 100000;
if (*cursor < '0' || *cursor > '9') return SCE_RTC_ERROR_BAD_PARSE;
for (; *cursor >= '0' && *cursor <= '9'; ++cursor, scale /= 10) microsecond += static_cast<std::uint32_t>(*cursor - '0') * scale;
ok = *cursor >= '0' && *cursor <= '9';
for (; ok && *cursor >= '0' && *cursor <= '9'; ++cursor, scale /= 10) microsecond += static_cast<std::uint32_t>(*cursor - '0') * scale;
}
std::int64_t offsetMinutes = 0;
if (ok && *cursor != 0) ok = parseZoneSuffix(cursor, offsetMinutes);
if (ok && *cursor != 0) ok = false;
if (!ok) {
NotImplemented_nid_no_patch(__func__);
return SCE_RTC_ERROR_BAD_PARSE;
}
if (*cursor != 0) return SCE_RTC_ERROR_BAD_PARSE;
const RtcDateTime time{static_cast<std::uint16_t>(year), static_cast<std::uint16_t>(month), static_cast<std::uint16_t>(day),
static_cast<std::uint16_t>(hour), static_cast<std::uint16_t>(minute), static_cast<std::uint16_t>(second), microsecond};
if (const int result = validate(&time); result != 0) return result;
utc->tick = toTick(time);
return 0;
const RtcTick local{toTick(time)};
return addTicks(utc, &local, -offsetMinutes, TICKS_PER_MINUTE);
}
int APS5_VABI sceRtcTickAddTicks(RtcTick* dst, const RtcTick* src, int64_t ticks) {

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <exception>
extern "C" {
int APS5_VABI sceRtcCheckValid(const RtcDateTime*);
@ -21,6 +22,7 @@ int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime*, std::uint64_t*);
int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime*, std::uint64_t);
int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int);
int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*);
int APS5_VABI sceRtcParseDateTime(RtcTick*, const char*);
int APS5_VABI sceRtcTickAddTicks(RtcTick*, const RtcTick*, std::int64_t);
int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t);
int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t);
@ -123,6 +125,30 @@ 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);
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(nullptr, "1970-01-01T00:00:00Z") == invalidPointer);
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.",
};
for (const char* text : unparseable) {
bool thrown = false;
try {
sceRtcParseDateTime(&tick, text);
} catch (const std::exception&) {
thrown = true;
}
Require(thrown);
}
RtcTick source{leapDayTick};
RtcTick result{};
Require(sceRtcTickAddSeconds(&result, &source, 3600) == 0 && result.tick == leapDayTick + 3600000000ull);

View File

@ -23,7 +23,7 @@ Throughout the project, every function at every stage either **does exactly what
### Unknown function info
- [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
- [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), matching the sibling RFC 3339 parser; unverified against hardware
- [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`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware
- [AMPR WaitOnAddress / WaitOnCounter](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - compare encoding assumed to be the WAIT_REG_MEM one (0 always, 1 <, 2 <=, 3 ==, 4 !=, 5 >=, 6 >)
- [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted
- [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature