diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index d5d542cf1..ee0f04f5c 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -369,6 +369,35 @@ int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { return 0; } +int APS5_VABI sceRtcGetDosTime(const RtcDateTime* time, uint32_t* dos_time) { + if (!dos_time) return SCE_RTC_ERROR_INVALID_POINTER; + if (const int result = validate(time); result != 0) return result; + if (time->year < 1980 || time->year > 2107) { + NotImplemented_nid_no_patch(__func__); + return SCE_RTC_ERROR_INVALID_YEAR; + } + *dos_time = static_cast(time->year - 1980) << 25 | static_cast(time->month) << 21 + | static_cast(time->day) << 16 | static_cast(time->hour) << 11 + | static_cast(time->minute) << 5 | static_cast(time->second / 2); + return 0; +} + +int APS5_VABI sceRtcSetDosTime(RtcDateTime* time, uint32_t dos_time) { + if (!time) return SCE_RTC_ERROR_INVALID_POINTER; + if (dos_time >> 31 != 0) { + NotImplemented_nid_no_patch(__func__); + return SCE_RTC_ERROR_INVALID_VALUE; + } + time->year = static_cast(1980 + (dos_time >> 25)); + time->month = static_cast(dos_time >> 21 & 0x0f); + time->day = static_cast(dos_time >> 16 & 0x1f); + time->hour = static_cast(dos_time >> 11 & 0x1f); + time->minute = static_cast(dos_time >> 5 & 0x3f); + time->second = static_cast((dos_time & 0x1f) * 2); + time->microsecond = 0; + return 0; +} + int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER; if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index e81c0e44f..695feed8c 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -23,6 +23,8 @@ int APS5_VABI sceRtcGetTime_t(const RtcDateTime*, std::int64_t*); int APS5_VABI sceRtcSetTime_t(RtcDateTime*, std::int64_t); int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime*, std::uint64_t*); int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime*, std::uint64_t); +int APS5_VABI sceRtcGetDosTime(const RtcDateTime*, std::uint32_t*); +int APS5_VABI sceRtcSetDosTime(RtcDateTime*, std::uint32_t); int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int); int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*); int APS5_VABI sceRtcParseDateTime(RtcTick*, const char*); @@ -106,6 +108,29 @@ int main() { std::uint64_t fileTime = 0; Require(sceRtcGetWin32FileTime(&epoch, &fileTime) == 0 && fileTime == 116444736000000000ull); Require(sceRtcSetWin32FileTime(&converted, 116444736000000000ull) == 0 && Equal(converted, epoch)); + std::uint32_t dosTime = 0xffffffffu; + const RtcDateTime dosDate{2024, 2, 29, 12, 34, 57, 789000}; + Require(sceRtcGetDosTime(&dosDate, &dosTime) == 0 && dosTime == 0x585d645cu); + const RtcDateTime dosFirst{1980, 1, 1, 0, 0, 0, 0}; + Require(sceRtcGetDosTime(&dosFirst, &dosTime) == 0 && dosTime == 0x00210000u); + const RtcDateTime dosLast{2107, 12, 31, 23, 59, 59, 0}; + Require(sceRtcGetDosTime(&dosLast, &dosTime) == 0 && dosTime == 0xff9fbf7du); + Require(sceRtcGetDosTime(&dosDate, nullptr) == invalidPointer); + Require(sceRtcGetDosTime(nullptr, &dosTime) == invalidPointer); + const RtcDateTime dosBadMonth{2024, 13, 1, 0, 0, 0, 0}; + Require(sceRtcGetDosTime(&dosBadMonth, &dosTime) == invalidMonth); + const RtcDateTime dosEarly{1979, 12, 31, 0, 0, 0, 0}; + bool dosEarlyThrew = false; + try { sceRtcGetDosTime(&dosEarly, &dosTime); } catch (const std::exception&) { dosEarlyThrew = true; } + Require(dosEarlyThrew); + converted = RtcDateTime{1, 1, 1, 1, 1, 1, 1}; + Require(sceRtcSetDosTime(&converted, 0x585d645cu) == 0 && Equal(converted, RtcDateTime{2024, 2, 29, 12, 34, 56, 0})); + Require(sceRtcSetDosTime(&converted, 0x7f9fbf7du) == 0 && Equal(converted, RtcDateTime{2043, 12, 31, 23, 59, 58, 0})); + Require(sceRtcSetDosTime(&converted, 0) == 0 && Equal(converted, RtcDateTime{1980, 0, 0, 0, 0, 0, 0})); + Require(sceRtcSetDosTime(nullptr, 0) == invalidPointer); + bool dosHighThrew = false; + try { sceRtcSetDosTime(&converted, 0x80210000u); } catch (const std::exception&) { dosHighThrew = true; } + Require(dosHighThrew); char text[32]; tick.tick = leapDayTick; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index fbfddc202..20cd80f75 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -22,6 +22,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceSystemService](../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer` and `sceSystemServiceReenableMusicPlayer` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify and no system music player to pause or resume - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - Without `VK_KHR_fragment_shader_barycentric` the shader recompiler [does not load the barycentric VGPRs](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp): [`v_interp_p1_f32` is dropped and `v_interp_p2_f32` reads the attribute Vulkan interpolated](../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp), so pixel shaders that use the barycentrics for anything other than `v_interp` get wrong values. +- [sceRtcGetDosTime, sceRtcSetDosTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - bit layout from shadPS4 (PS4, the standard FAT date/time). Years outside 1980-2107 (`GetDosTime`) and year fields above 63 (`SetDosTime`, where shadPS4 sign-extends the 16-bit date to years before 1980) throw, since no reference says what the library returns for them. `SetDosTime` does not validate the fields, as in shadPS4 - [libSceAudio3d](../../core/libs/prx/libSceAudio3d/Export.cpp) - the port produces no sound: the library has no bed or object exports to give it audio, so `sceAudio3dPortPush` only paces the queue at `granularity` samples per frame at 48 kHz. `sceAudio3dPortSetAttribute` accepts the late reverb level and the downmix spread attributes and ignores them - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. - [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success.