mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-16 00:40:59 +00:00
[RTC] Port core rtc helpers from PS5 3.20 libs (#105)
Co-authored-by: zocomputer <help@zocomputer.com>
This commit is contained in:
@@ -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<byte> 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<DateTime, DateTime> 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<byte> 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<byte> 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<byte> 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(
|
||||
|
||||
Reference in New Issue
Block a user