From 56bf00e9a5529d078f7ba9101f4f35cd969f255b Mon Sep 17 00:00:00 2001 From: j92580498-max Date: Mon, 13 Jul 2026 18:51:48 +0300 Subject: [PATCH] [RTC] Port core rtc helpers from PS5 3.20 libs (#105) Co-authored-by: zocomputer --- src/SharpEmu.Libs/Rtc/RtcExports.cs | 887 +++++++++++++++++++++++++--- 1 file changed, 820 insertions(+), 67 deletions(-) diff --git a/src/SharpEmu.Libs/Rtc/RtcExports.cs b/src/SharpEmu.Libs/Rtc/RtcExports.cs index 5794ae4..a0fcc73 100644 --- a/src/SharpEmu.Libs/Rtc/RtcExports.cs +++ b/src/SharpEmu.Libs/Rtc/RtcExports.cs @@ -9,6 +9,189 @@ namespace SharpEmu.Libs.Rtc; public static class RtcExports { private const long DateTimeTicksPerMicrosecond = 10; + private const ulong MicrosecondsPerSecond = 1_000_000UL; + private const ulong MicrosecondsPerMinute = 60_000_000UL; + private const ulong MicrosecondsPerHour = 3_600_000_000UL; + private const ulong MicrosecondsPerDay = 86_400_000_000UL; + private const ulong MicrosecondsPerWeek = 604_800_000_000UL; + private const ulong UnixEpochTicks = 62_135_596_800_000_000UL; + private const ulong Win32FileTimeEpochTicks = 50_491_123_200_000_000UL; + + [SysAbiExport( + Nid = "lPEBYdVX0XQ", + ExportName = "sceRtcCheckValid", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcCheckValid(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + if (timeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!TryReadRtcDateTime(ctx, timeAddress, out var time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return ValidateRtcDateTime(time); + } + + [SysAbiExport( + Nid = "fNaZ4DbzHAE", + ExportName = "sceRtcCompareTick", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcCompareTick(CpuContext ctx) + { + var tick1Address = ctx[CpuRegister.Rdi]; + var tick2Address = ctx[CpuRegister.Rsi]; + if (tick1Address == 0 || tick2Address == 0) + { + return unchecked((int)0x80B50002); + } + + if (!ctx.TryReadUInt64(tick1Address, out var tick1) || !ctx.TryReadUInt64(tick2Address, out var tick2)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return tick1 < tick2 ? -1 : tick1 > tick2 ? 1 : 0; + } + + [SysAbiExport( + Nid = "8Yr143yEnRo", + ExportName = "sceRtcConvertLocalTimeToUtc", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcConvertLocalTimeToUtc(CpuContext ctx) + { + var tickLocalAddress = ctx[CpuRegister.Rdi]; + var tickUtcAddress = ctx[CpuRegister.Rsi]; + if (tickLocalAddress == 0 || tickUtcAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!ctx.TryReadUInt64(tickLocalAddress, out var tickLocal)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryConvertTickToDateTime(tickLocal, out var localDateTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + DateTime utcDateTime; + try + { + utcDateTime = TimeZoneInfo.ConvertTimeToUtc(localDateTime, TimeZoneInfo.Local); + } + catch (ArgumentException) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!TryWriteTickFromDateTime(ctx, tickUtcAddress, utcDateTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "M1TvFst-jrM", + ExportName = "sceRtcConvertUtcToLocalTime", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcConvertUtcToLocalTime(CpuContext ctx) + { + var tickUtcAddress = ctx[CpuRegister.Rdi]; + var tickLocalAddress = ctx[CpuRegister.Rsi]; + if (tickUtcAddress == 0 || tickLocalAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!ctx.TryReadUInt64(tickUtcAddress, out var tickUtc)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryConvertTickToDateTime(tickUtc, out var utcDateTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + DateTime localDateTime; + try + { + localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, TimeZoneInfo.Local); + } + catch (ArgumentException) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!TryWriteTickFromDateTime(ctx, tickLocalAddress, localDateTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "8SljQx6pDP8", + ExportName = "sceRtcEnd", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcEnd(CpuContext ctx) + { + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "LN3Zcb72Q0c", + ExportName = "sceRtcGetCurrentAdNetworkTick", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetCurrentAdNetworkTick(CpuContext ctx) => RtcGetCurrentTick(ctx); + + [SysAbiExport( + Nid = "8lfvnRMqwEM", + ExportName = "sceRtcGetCurrentClock", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetCurrentClock(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + if (timeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + var timeZoneMinutes = unchecked((int)ctx[CpuRegister.Rsi]); + DateTimeOffset currentTime; + try + { + currentTime = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromMinutes(timeZoneMinutes)); + } + catch (ArgumentOutOfRangeException) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!TryWriteRtcDateTime(ctx, timeAddress, ToRtcDateTime(currentTime.DateTime))) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } [SysAbiExport( Nid = "ZPD1YOKI+Kw", @@ -20,30 +203,38 @@ public static class RtcExports var timeAddress = ctx[CpuRegister.Rdi]; if (timeAddress == 0) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + return unchecked((int)0x80B50002); } - var now = DateTimeOffset.Now; - Span rtcDateTime = stackalloc byte[16]; - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)now.Year)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)now.Month)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)now.Day)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)now.Hour)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)now.Minute)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)now.Second)); - BinaryPrimitives.WriteUInt32LittleEndian( - rtcDateTime[12..16], - checked((uint)((now.Ticks % TimeSpan.TicksPerSecond) / 10))); - - if (!ctx.Memory.TryWrite(timeAddress, rtcDateTime)) + if (!TryWriteRtcDateTime(ctx, timeAddress, ToRtcDateTime(DateTimeOffset.Now.DateTime))) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "Ot1DE3gif84", + ExportName = "sceRtcGetCurrentDebugNetworkTick", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetCurrentDebugNetworkTick(CpuContext ctx) => RtcGetCurrentTick(ctx); + + [SysAbiExport( + Nid = "zO9UL3qIINQ", + ExportName = "sceRtcGetCurrentNetworkTick", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetCurrentNetworkTick(CpuContext ctx) => RtcGetCurrentTick(ctx); + + [SysAbiExport( + Nid = "HWxHOdbM-Pg", + ExportName = "sceRtcGetCurrentRawNetworkTick", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetCurrentRawNetworkTick(CpuContext ctx) => RtcGetCurrentTick(ctx); + [SysAbiExport( Nid = "18B2NS1y9UU", ExportName = "sceRtcGetCurrentTick", @@ -54,7 +245,7 @@ public static class RtcExports var tickAddress = ctx[CpuRegister.Rdi]; if (tickAddress == 0) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + return unchecked((int)0x80B50002); } var tickValue = unchecked((ulong)(DateTime.UtcNow.Ticks / DateTimeTicksPerMicrosecond)); @@ -63,7 +254,87 @@ public static class RtcExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "CyIK-i4XdgQ", + ExportName = "sceRtcGetDayOfWeek", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetDayOfWeek(CpuContext ctx) + { + var year = unchecked((int)ctx[CpuRegister.Rdi]); + var month = unchecked((int)ctx[CpuRegister.Rsi]); + var day = unchecked((int)ctx[CpuRegister.Rdx]); + if (!IsValidCalendarDate(year, month, day)) + { + return unchecked((int)0x80B50004); + } + + return (int)new DateTime(year, month, day).DayOfWeek; + } + + [SysAbiExport( + Nid = "3O7Ln8AqJ1o", + ExportName = "sceRtcGetDaysInMonth", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetDaysInMonth(CpuContext ctx) + { + var year = unchecked((int)ctx[CpuRegister.Rdi]); + var month = unchecked((int)ctx[CpuRegister.Rsi]); + if (year < 1 || year > 9999) + { + return unchecked((int)0x80B50008); + } + + if (month < 1 || month > 12) + { + return unchecked((int)0x80B50009); + } + + return DateTime.DaysInMonth(year, month); + } + + [SysAbiExport( + Nid = "E7AR4o7Ny7E", + ExportName = "sceRtcGetDosTime", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetDosTime(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + var dosTimeAddress = ctx[CpuRegister.Rsi]; + if (timeAddress == 0 || dosTimeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!TryReadRtcDateTime(ctx, timeAddress, out var time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var validationResult = ValidateRtcDateTime(time); + if (validationResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return validationResult; + } + + uint dosTime = 0; + dosTime |= (uint)((time.Second / 2) & 0x1F); + dosTime |= (uint)(time.Minute & 0x3F) << 5; + dosTime |= (uint)(time.Hour & 0x1F) << 11; + dosTime |= (uint)(time.Day & 0x1F) << 16; + dosTime |= (uint)(time.Month & 0x0F) << 21; + dosTime |= (uint)((time.Year - 1980) & 0x7F) << 25; + + if (!ctx.TryWriteUInt32(dosTimeAddress, dosTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + return (int)OrbisGen2Result.ORBIS_GEN2_OK; } @@ -74,45 +345,220 @@ public static class RtcExports LibraryName = "libSceRtc")] public static int RtcGetTick(CpuContext ctx) { - var dateTimeAddress = ctx[CpuRegister.Rdi]; + var timeAddress = ctx[CpuRegister.Rdi]; var tickAddress = ctx[CpuRegister.Rsi]; - if (dateTimeAddress == 0 || tickAddress == 0) + if (timeAddress == 0 || tickAddress == 0) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + return unchecked((int)0x80B50002); } - if (!TryReadRtcDateTime(ctx, dateTimeAddress, out var rtcDateTime)) + if (!TryReadRtcDateTime(ctx, timeAddress, out var time)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - ulong tickValue; - try + var validationResult = ValidateRtcDateTime(time); + if (validationResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) { - var baseDateTime = new DateTime( - rtcDateTime.Year, - rtcDateTime.Month, - rtcDateTime.Day, - rtcDateTime.Hour, - rtcDateTime.Minute, - rtcDateTime.Second, - DateTimeKind.Utc); - tickValue = checked((ulong)((baseDateTime.Ticks / DateTimeTicksPerMicrosecond) + rtcDateTime.Microsecond)); + return validationResult; } - catch (Exception ex) when (ex is ArgumentOutOfRangeException or OverflowException) + + if (!TryConvertRtcDateTimeToTick(time, out var tick)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - if (!ctx.TryWriteUInt64(tickAddress, tickValue)) + if (!ctx.TryWriteUInt64(tickAddress, tick)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "jMNwqYr4R-k", + ExportName = "sceRtcGetTickResolution", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetTickResolution(CpuContext ctx) + { + return (int)MicrosecondsPerSecond; + } + + [SysAbiExport( + Nid = "BtqmpTRXHgk", + ExportName = "sceRtcGetTime_t", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetTimeT(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + var timeTAddress = ctx[CpuRegister.Rsi]; + if (timeAddress == 0 || timeTAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!TryReadRtcDateTime(ctx, timeAddress, out var time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var validationResult = ValidateRtcDateTime(time); + if (validationResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return validationResult; + } + + if (!TryConvertRtcDateTimeToTick(time, out var tick)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + var unixSeconds = tick < UnixEpochTicks ? 0UL : (tick - UnixEpochTicks) / MicrosecondsPerSecond; + if (!ctx.TryWriteUInt64(timeTAddress, unixSeconds)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "jfRO0uTjtzA", + ExportName = "sceRtcGetWin32FileTime", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcGetWin32FileTime(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + var fileTimeAddress = ctx[CpuRegister.Rsi]; + if (timeAddress == 0 || fileTimeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!TryReadRtcDateTime(ctx, timeAddress, out var time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var validationResult = ValidateRtcDateTime(time); + if (validationResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return validationResult; + } + + if (!TryConvertRtcDateTimeToTick(time, out var tick)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + var win32Time = tick < Win32FileTimeEpochTicks ? 0UL : (tick - Win32FileTimeEpochTicks) * 10UL; + if (!ctx.TryWriteUInt64(fileTimeAddress, win32Time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "LlodCMDbk3o", + ExportName = "sceRtcInit", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcInit(CpuContext ctx) + { + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "Ug8pCwQvh0c", + ExportName = "sceRtcIsLeapYear", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcIsLeapYear(CpuContext ctx) + { + var year = unchecked((int)ctx[CpuRegister.Rdi]); + if (year < 1 || year > 9999) + { + return unchecked((int)0x80B50008); + } + + return DateTime.IsLeapYear(year) ? 1 : 0; + } + + [SysAbiExport( + Nid = "NR1J0N7L2xY", + ExportName = "sceRtcTickAddDays", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddDays(CpuContext ctx) => AddTickDelta(ctx, MicrosecondsPerDay); + + [SysAbiExport( + Nid = "MDc5cd8HfCA", + ExportName = "sceRtcTickAddHours", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddHours(CpuContext ctx) => AddTickDelta(ctx, MicrosecondsPerHour); + + [SysAbiExport( + Nid = "XPIiw58C+GM", + ExportName = "sceRtcTickAddMicroseconds", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddMicroseconds(CpuContext ctx) => AddTickDelta(ctx, 1UL); + + [SysAbiExport( + Nid = "mn-tf4QiFzk", + ExportName = "sceRtcTickAddMinutes", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddMinutes(CpuContext ctx) => AddTickDelta(ctx, MicrosecondsPerMinute); + + [SysAbiExport( + Nid = "CL6y9q-XbuQ", + ExportName = "sceRtcTickAddMonths", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddMonths(CpuContext ctx) + { + return AddCalendarDelta(ctx, dateTime => dateTime.AddMonths(unchecked((int)ctx[CpuRegister.Rdx]))); + } + + [SysAbiExport( + Nid = "07O525HgICs", + ExportName = "sceRtcTickAddSeconds", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddSeconds(CpuContext ctx) => AddTickDelta(ctx, MicrosecondsPerSecond); + + [SysAbiExport( + Nid = "AqVMssr52Rc", + ExportName = "sceRtcTickAddTicks", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddTicks(CpuContext ctx) => AddTickDelta(ctx, 1UL); + + [SysAbiExport( + Nid = "gI4t194c2W8", + ExportName = "sceRtcTickAddWeeks", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddWeeks(CpuContext ctx) => AddTickDelta(ctx, MicrosecondsPerWeek); + + [SysAbiExport( + Nid = "-5y2uJ62qS8", + ExportName = "sceRtcTickAddYears", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcTickAddYears(CpuContext ctx) + { + return AddCalendarDelta(ctx, dateTime => dateTime.AddYears(unchecked((int)ctx[CpuRegister.Rdx]))); + } + [SysAbiExport( Nid = "ueega6v3GUw", ExportName = "sceRtcSetTick", @@ -120,11 +566,11 @@ public static class RtcExports LibraryName = "libSceRtc")] public static int RtcSetTick(CpuContext ctx) { - var dateTimeAddress = ctx[CpuRegister.Rdi]; + var timeAddress = ctx[CpuRegister.Rdi]; var tickAddress = ctx[CpuRegister.Rsi]; - if (dateTimeAddress == 0 || tickAddress == 0) + if (timeAddress == 0 || tickAddress == 0) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + return unchecked((int)0x80B50002); } if (!ctx.TryReadUInt64(tickAddress, out var tickValue)) @@ -132,40 +578,249 @@ public static class RtcExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - if (tickValue > long.MaxValue / DateTimeTicksPerMicrosecond) + if (!TryConvertTickToRtcDateTime(tickValue, out var time)) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + return unchecked((int)0x80B50004); } - DateTime dateTime; - try - { - dateTime = new DateTime(checked((long)tickValue * DateTimeTicksPerMicrosecond), DateTimeKind.Utc); - } - catch (ArgumentOutOfRangeException) - { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; - } - - if (!TryWriteRtcDateTime(ctx, dateTimeAddress, dateTime)) + if (!TryWriteRtcDateTime(ctx, timeAddress, time)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } - private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime rtcDateTime) + [SysAbiExport( + Nid = "aYPCd1cChyg", + ExportName = "sceRtcSetDosTime", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcSetDosTime(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + if (timeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + var dosTime = unchecked((uint)ctx[CpuRegister.Rsi]); + var time = new RtcDateTime( + (ushort)(1980 + ((dosTime >> 25) & 0x7F)), + (ushort)((dosTime >> 21) & 0x0F), + (ushort)((dosTime >> 16) & 0x1F), + (ushort)((dosTime >> 11) & 0x1F), + (ushort)((dosTime >> 5) & 0x3F), + (ushort)((dosTime << 1) & 0x3E), + 0); + + if (!TryWriteRtcDateTime(ctx, timeAddress, time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "bDEVVP4bTjQ", + ExportName = "sceRtcSetTime_t", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcSetTimeT(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + if (timeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + var timeSeconds = unchecked((long)ctx[CpuRegister.Rsi]); + if (timeSeconds < 0) + { + return unchecked((int)0x80B50003); + } + + var tick = UnixEpochTicks + unchecked((ulong)timeSeconds) * MicrosecondsPerSecond; + if (!TryConvertTickToRtcDateTime(tick, out var time)) + { + return unchecked((int)0x80B50003); + } + + if (!TryWriteRtcDateTime(ctx, timeAddress, time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "n5JiAJXsbcs", + ExportName = "sceRtcSetWin32FileTime", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRtc")] + public static int RtcSetWin32FileTime(CpuContext ctx) + { + var timeAddress = ctx[CpuRegister.Rdi]; + if (timeAddress == 0) + { + return unchecked((int)0x80B50002); + } + + var fileTime = unchecked((long)ctx[CpuRegister.Rsi]); + if (fileTime < 0) + { + return unchecked((int)0x80B50003); + } + + var tick = Win32FileTimeEpochTicks + unchecked((ulong)fileTime / 10UL); + if (!TryConvertTickToRtcDateTime(tick, out var time)) + { + return unchecked((int)0x80B50003); + } + + if (!TryWriteRtcDateTime(ctx, timeAddress, time)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static int AddTickDelta(CpuContext ctx, ulong microsecondsPerUnit) + { + var destinationAddress = ctx[CpuRegister.Rdi]; + var sourceAddress = ctx[CpuRegister.Rsi]; + var delta = unchecked((long)ctx[CpuRegister.Rdx]); + if (destinationAddress == 0 || sourceAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!ctx.TryReadUInt64(sourceAddress, out var sourceTick)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + try + { + var resultTick = checked((long)sourceTick + checked(delta * (long)microsecondsPerUnit)); + if (resultTick < 0) + { + return unchecked((int)0x80B50003); + } + + if (!ctx.TryWriteUInt64(destinationAddress, unchecked((ulong)resultTick))) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + } + catch (OverflowException) + { + return unchecked((int)0x80B50003); + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static int AddCalendarDelta(CpuContext ctx, Func transform) + { + var destinationAddress = ctx[CpuRegister.Rdi]; + var sourceAddress = ctx[CpuRegister.Rsi]; + if (destinationAddress == 0 || sourceAddress == 0) + { + return unchecked((int)0x80B50002); + } + + if (!ctx.TryReadUInt64(sourceAddress, out var sourceTick)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryConvertTickToDateTime(sourceTick, out var sourceDateTime)) + { + return unchecked((int)0x80B50003); + } + + DateTime resultDateTime; + try + { + resultDateTime = transform(sourceDateTime); + } + catch (ArgumentOutOfRangeException) + { + return unchecked((int)0x80B50003); + } + + if (!TryWriteTickFromDateTime(ctx, destinationAddress, resultDateTime)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static int ValidateRtcDateTime(RtcDateTime time) + { + if (time.Year < 1 || time.Year > 9999) + { + return unchecked((int)0x80B50008); + } + + if (time.Month < 1 || time.Month > 12) + { + return unchecked((int)0x80B50009); + } + + if (time.Day < 1 || time.Day > DateTime.DaysInMonth(time.Year, time.Month)) + { + return unchecked((int)0x80B5000A); + } + + if (time.Hour > 23) + { + return unchecked((int)0x80B5000B); + } + + if (time.Minute > 59) + { + return unchecked((int)0x80B5000C); + } + + if (time.Second > 59) + { + return unchecked((int)0x80B5000D); + } + + if (time.Microsecond > 999_999) + { + return unchecked((int)0x80B5000E); + } + + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static bool IsValidCalendarDate(int year, int month, int day) + { + if (year < 1 || year > 9999 || month < 1 || month > 12) + { + return false; + } + + return day >= 1 && day <= DateTime.DaysInMonth(year, month); + } + + private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime time) { Span buffer = stackalloc byte[16]; if (!ctx.Memory.TryRead(address, buffer)) { - rtcDateTime = default; + time = default; return false; } - rtcDateTime = new RtcDateTime( + time = new RtcDateTime( BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]), BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]), BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]), @@ -176,19 +831,117 @@ public static class RtcExports return true; } - private static bool TryWriteRtcDateTime(CpuContext ctx, ulong address, DateTime dateTime) + private static bool TryWriteRtcDateTime(CpuContext ctx, ulong address, RtcDateTime dateTime) { - Span rtcDateTime = stackalloc byte[16]; - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)dateTime.Year)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)dateTime.Month)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)dateTime.Day)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)dateTime.Hour)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)dateTime.Minute)); - BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)dateTime.Second)); - BinaryPrimitives.WriteUInt32LittleEndian( - rtcDateTime[12..16], - checked((uint)((dateTime.Ticks % TimeSpan.TicksPerSecond) / DateTimeTicksPerMicrosecond))); - return ctx.Memory.TryWrite(address, rtcDateTime); + Span buffer = stackalloc byte[16]; + BinaryPrimitives.WriteUInt16LittleEndian(buffer[0..2], dateTime.Year); + BinaryPrimitives.WriteUInt16LittleEndian(buffer[2..4], dateTime.Month); + BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..6], dateTime.Day); + BinaryPrimitives.WriteUInt16LittleEndian(buffer[6..8], dateTime.Hour); + BinaryPrimitives.WriteUInt16LittleEndian(buffer[8..10], dateTime.Minute); + BinaryPrimitives.WriteUInt16LittleEndian(buffer[10..12], dateTime.Second); + BinaryPrimitives.WriteUInt32LittleEndian(buffer[12..16], dateTime.Microsecond); + return ctx.Memory.TryWrite(address, buffer); + } + + private static RtcDateTime ToRtcDateTime(DateTime dateTime) + { + return new RtcDateTime( + (ushort)dateTime.Year, + (ushort)dateTime.Month, + (ushort)dateTime.Day, + (ushort)dateTime.Hour, + (ushort)dateTime.Minute, + (ushort)dateTime.Second, + (uint)(dateTime.Ticks % TimeSpan.TicksPerSecond / DateTimeTicksPerMicrosecond)); + } + + private static RtcDateTime ToRtcDateTime(DateTimeOffset dateTimeOffset) + { + return ToRtcDateTime(dateTimeOffset.DateTime); + } + + private static bool TryWriteTickFromDateTime(CpuContext ctx, ulong address, DateTime dateTime) + { + return ctx.TryWriteUInt64(address, unchecked((ulong)(dateTime.Ticks / DateTimeTicksPerMicrosecond))); + } + + private static bool TryConvertTickToDateTime(ulong tick, out DateTime dateTime) + { + var maxSupportedTick = unchecked((ulong)(DateTime.MaxValue.Ticks / DateTimeTicksPerMicrosecond)); + if (tick > maxSupportedTick) + { + dateTime = default; + return false; + } + + try + { + dateTime = new DateTime(checked((long)(tick * (ulong)DateTimeTicksPerMicrosecond)), DateTimeKind.Utc); + return true; + } + catch (ArgumentOutOfRangeException) + { + dateTime = default; + return false; + } + } + + private static bool TryConvertRtcDateTimeToTick(RtcDateTime time, out ulong tick) + { + try + { + var year = time.Year; + var month = time.Month; + var day = time.Day; + if (month > 2) + { + month -= 3; + } + else + { + month += 9; + year -= 1; + } + + var century = year / 100; + var yearOfCentury = year - (100 * century); + + ulong days = ((146097UL * (ulong)century) >> 2) + + ((1461UL * (ulong)yearOfCentury) >> 2) + + ((153UL * (ulong)month + 2UL) / 5UL) + + day; + days -= 307UL; + days *= MicrosecondsPerDay; + + var timeOfDay = (ulong)time.Hour * MicrosecondsPerHour + + (ulong)time.Minute * MicrosecondsPerMinute + + (ulong)time.Second * MicrosecondsPerSecond + + time.Microsecond; + + tick = days + timeOfDay; + return true; + } + catch (OverflowException) + { + tick = 0; + return false; + } + } + + private static bool TryConvertTickToRtcDateTime(ulong tick, out RtcDateTime time) + { + try + { + var dateTime = new DateTime(checked((long)(tick * DateTimeTicksPerMicrosecond)), DateTimeKind.Utc); + time = ToRtcDateTime(dateTime); + return true; + } + catch (ArgumentOutOfRangeException) + { + time = default; + return false; + } } private readonly record struct RtcDateTime(