mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 14:39:42 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0565d01744 | |||
| 56bf00e9a5 | |||
| 46b729c5b4 | |||
| 4b7df8623a | |||
| 3c2134474d | |||
| 298ef01809 | |||
| 2060dacaf1 |
@@ -1,3 +1,8 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Contributions are always welcome!
|
Contributions are always welcome!
|
||||||
|
|||||||
+372
-13
@@ -8,12 +8,15 @@ using SharpEmu.HLE;
|
|||||||
using SharpEmu.Logging;
|
using SharpEmu.Logging;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace SharpEmu.CLI;
|
namespace SharpEmu.CLI;
|
||||||
|
|
||||||
internal static partial class Program
|
internal static partial class Program
|
||||||
{
|
{
|
||||||
private static readonly SharpEmuLogger Log = SharpEmuLog.For("SharpEmu.CLI");
|
private static readonly SharpEmuLogger Log = SharpEmuLog.For("SharpEmu.CLI");
|
||||||
|
private static readonly object ConsoleMirrorSync = new();
|
||||||
|
private static StreamWriter? _consoleMirrorFile;
|
||||||
private const int DefaultImportTraceLimit = 32;
|
private const int DefaultImportTraceLimit = 32;
|
||||||
private const string MitigatedChildFlag = "--sharpemu-mitigated-child";
|
private const string MitigatedChildFlag = "--sharpemu-mitigated-child";
|
||||||
private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
||||||
@@ -21,11 +24,15 @@ internal static partial class Program
|
|||||||
private const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007;
|
private const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007;
|
||||||
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
|
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
|
||||||
private const int JobObjectExtendedLimitInformation = 9;
|
private const int JobObjectExtendedLimitInformation = 9;
|
||||||
|
private const int STARTF_USESTDHANDLES = 0x00000100;
|
||||||
|
private const uint HANDLE_FLAG_INHERIT = 0x00000001;
|
||||||
|
private const string MitigatedChildEnvironment = "SHARPEMU_MITIGATED_CHILD";
|
||||||
private const ulong PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF = 0x00000002UL << 40;
|
private const ulong PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF = 0x00000002UL << 40;
|
||||||
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF = 0x00000002UL << 28;
|
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF = 0x00000002UL << 28;
|
||||||
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF = 0x00000002UL << 32;
|
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF = 0x00000002UL << 32;
|
||||||
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_XTENDED_CONTROL_FLOW_GUARD_ALWAYS_OFF = 0x00000002UL << 40;
|
private const ulong PROCESS_CREATION_MITIGATION_POLICY2_XTENDED_CONTROL_FLOW_GUARD_ALWAYS_OFF = 0x00000002UL << 40;
|
||||||
private const int ATTACH_PARENT_PROCESS = -1;
|
private const int ATTACH_PARENT_PROCESS = -1;
|
||||||
|
private const int STD_INPUT_HANDLE = -10;
|
||||||
private const int STD_OUTPUT_HANDLE = -11;
|
private const int STD_OUTPUT_HANDLE = -11;
|
||||||
private const int STD_ERROR_HANDLE = -12;
|
private const int STD_ERROR_HANDLE = -12;
|
||||||
private const uint GENERIC_READ = 0x80000000;
|
private const uint GENERIC_READ = 0x80000000;
|
||||||
@@ -43,6 +50,7 @@ internal static partial class Program
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
DropConsoleFileMirror();
|
||||||
SharpEmuLog.Shutdown();
|
SharpEmuLog.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +69,10 @@ internal static partial class Program
|
|||||||
// itself to a console before the first write.
|
// itself to a console before the first write.
|
||||||
EnsureCliConsole();
|
EnsureCliConsole();
|
||||||
UseUtf8ConsoleOutput();
|
UseUtf8ConsoleOutput();
|
||||||
|
if (isMitigatedChild && TryGetLogFileArgument(args, out var earlyLogFilePath))
|
||||||
|
{
|
||||||
|
TryEnableConsoleFileMirror(earlyLogFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine($"[DEBUG] SharpEmu starting with {args.Length} args");
|
Console.Error.WriteLine($"[DEBUG] SharpEmu starting with {args.Length} args");
|
||||||
|
|
||||||
@@ -69,12 +81,17 @@ internal static partial class Program
|
|||||||
return childExitCode;
|
return childExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryParseArguments(args, out var ebootPath, out var runtimeOptions, out var logLevel))
|
if (!TryParseArguments(args, out var ebootPath, out var runtimeOptions, out var logLevel, out var logFilePath))
|
||||||
{
|
{
|
||||||
PrintUsage();
|
PrintUsage();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isMitigatedChild && !string.IsNullOrWhiteSpace(logFilePath))
|
||||||
|
{
|
||||||
|
TryEnableConsoleFileMirror(logFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
SharpEmuLog.MinimumLevel = logLevel;
|
SharpEmuLog.MinimumLevel = logLevel;
|
||||||
|
|
||||||
Log.Info(BuildInfo.Banner);
|
Log.Info(BuildInfo.Banner);
|
||||||
@@ -234,6 +251,10 @@ internal static partial class Program
|
|||||||
private static string[] NormalizeInternalArguments(string[] args, out bool isMitigatedChild)
|
private static string[] NormalizeInternalArguments(string[] args, out bool isMitigatedChild)
|
||||||
{
|
{
|
||||||
isMitigatedChild = false;
|
isMitigatedChild = false;
|
||||||
|
var trustedMitigatedChild = string.Equals(
|
||||||
|
Environment.GetEnvironmentVariable(MitigatedChildEnvironment),
|
||||||
|
"1",
|
||||||
|
StringComparison.Ordinal);
|
||||||
if (args.Length == 0)
|
if (args.Length == 0)
|
||||||
{
|
{
|
||||||
return args;
|
return args;
|
||||||
@@ -244,7 +265,7 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
if (string.Equals(arg, MitigatedChildFlag, StringComparison.Ordinal))
|
if (string.Equals(arg, MitigatedChildFlag, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
isMitigatedChild = true;
|
isMitigatedChild = trustedMitigatedChild;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,9 +304,11 @@ internal static partial class Program
|
|||||||
var commandLine = BuildCommandLine(processPath, childArgs);
|
var commandLine = BuildCommandLine(processPath, childArgs);
|
||||||
var startupInfoEx = new STARTUPINFOEX();
|
var startupInfoEx = new STARTUPINFOEX();
|
||||||
startupInfoEx.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
|
startupInfoEx.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
|
||||||
|
ConfigureInheritedStdHandles(ref startupInfoEx.StartupInfo);
|
||||||
|
|
||||||
nint attributeList = 0;
|
nint attributeList = 0;
|
||||||
nint mitigationPolicies = 0;
|
nint mitigationPolicies = 0;
|
||||||
|
var previousChildEnvironment = Environment.GetEnvironmentVariable(MitigatedChildEnvironment);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
nuint attributeListSize = 0;
|
nuint attributeListSize = 0;
|
||||||
@@ -293,7 +316,9 @@ internal static partial class Program
|
|||||||
attributeList = Marshal.AllocHGlobal((nint)attributeListSize);
|
attributeList = Marshal.AllocHGlobal((nint)attributeListSize);
|
||||||
if (!InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeListSize))
|
if (!InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeListSize))
|
||||||
{
|
{
|
||||||
return false;
|
childExitCode = 5;
|
||||||
|
Console.Error.WriteLine($"[ERROR] Failed to initialize mitigation attributes: {Marshal.GetLastWin32Error()}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
startupInfoEx.lpAttributeList = attributeList;
|
startupInfoEx.lpAttributeList = attributeList;
|
||||||
@@ -301,8 +326,7 @@ internal static partial class Program
|
|||||||
var policy1 = PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
var policy1 = PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
||||||
var policy2 =
|
var policy2 =
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF |
|
PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF |
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF |
|
PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF;
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_XTENDED_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
|
||||||
|
|
||||||
mitigationPolicies = Marshal.AllocHGlobal(sizeof(ulong) * 2);
|
mitigationPolicies = Marshal.AllocHGlobal(sizeof(ulong) * 2);
|
||||||
Marshal.WriteInt64(mitigationPolicies, unchecked((long)policy1));
|
Marshal.WriteInt64(mitigationPolicies, unchecked((long)policy1));
|
||||||
@@ -317,24 +341,31 @@ internal static partial class Program
|
|||||||
0,
|
0,
|
||||||
0))
|
0))
|
||||||
{
|
{
|
||||||
return false;
|
childExitCode = 5;
|
||||||
|
Console.Error.WriteLine($"[ERROR] Failed to apply mitigation attributes: {Marshal.GetLastWin32Error()}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdLineBuilder = new StringBuilder(commandLine);
|
var cmdLineBuilder = new StringBuilder(commandLine);
|
||||||
nint jobHandle = 0;
|
nint jobHandle = 0;
|
||||||
if (!CreateProcessW(
|
Environment.SetEnvironmentVariable(MitigatedChildEnvironment, "1");
|
||||||
|
var created = CreateProcessW(
|
||||||
processPath,
|
processPath,
|
||||||
cmdLineBuilder,
|
cmdLineBuilder,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
false,
|
true,
|
||||||
EXTENDED_STARTUPINFO_PRESENT,
|
EXTENDED_STARTUPINFO_PRESENT,
|
||||||
0,
|
0,
|
||||||
Environment.CurrentDirectory,
|
Environment.CurrentDirectory,
|
||||||
ref startupInfoEx,
|
ref startupInfoEx,
|
||||||
out var processInfo))
|
out var processInfo);
|
||||||
|
Environment.SetEnvironmentVariable(MitigatedChildEnvironment, previousChildEnvironment);
|
||||||
|
if (!created)
|
||||||
{
|
{
|
||||||
return false;
|
childExitCode = 5;
|
||||||
|
Console.Error.WriteLine($"[ERROR] Failed to launch mitigated child process: {Marshal.GetLastWin32Error()}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -388,6 +419,8 @@ internal static partial class Program
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
Environment.SetEnvironmentVariable(MitigatedChildEnvironment, previousChildEnvironment);
|
||||||
|
|
||||||
if (attributeList != 0)
|
if (attributeList != 0)
|
||||||
{
|
{
|
||||||
DeleteProcThreadAttributeList(attributeList);
|
DeleteProcThreadAttributeList(attributeList);
|
||||||
@@ -401,6 +434,238 @@ internal static partial class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetLogFileArgument(IReadOnlyList<string> args, out string path)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < args.Count; i++)
|
||||||
|
{
|
||||||
|
var argument = args[i];
|
||||||
|
if (string.Equals(argument, "--log-file", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (i + 1 < args.Count &&
|
||||||
|
!string.IsNullOrWhiteSpace(args[i + 1]) &&
|
||||||
|
!args[i + 1].StartsWith("--", StringComparison.Ordinal) &&
|
||||||
|
ShouldConsumeLogFilePath(args, i + 1))
|
||||||
|
{
|
||||||
|
path = args[i + 1];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = BuildDefaultLogFilePath(TryFindEbootPathToken(args));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string logFilePrefix = "--log-file=";
|
||||||
|
if (argument.StartsWith(logFilePrefix, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!string.IsNullOrWhiteSpace(argument[logFilePrefix.Length..]))
|
||||||
|
{
|
||||||
|
path = argument[logFilePrefix.Length..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildDefaultLogFilePath(string? ebootPath)
|
||||||
|
{
|
||||||
|
var baseDirectory = AppContext.BaseDirectory;
|
||||||
|
var logsDirectory = Path.Combine(baseDirectory, "user", "logs");
|
||||||
|
var name = TryReadTitleId(ebootPath) ?? "UNKNOWN";
|
||||||
|
|
||||||
|
foreach (var invalid in Path.GetInvalidFileNameChars())
|
||||||
|
{
|
||||||
|
name = name.Replace(invalid, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(logsDirectory, $"{name}-{DateTime.Now:yyyyMMdd-HHmmss}.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? TryReadTitleId(string? ebootPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ebootPath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(Path.GetFullPath(ebootPath));
|
||||||
|
if (string.IsNullOrEmpty(directory))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var paramPath in new[]
|
||||||
|
{
|
||||||
|
Path.Combine(directory, "sce_sys", "param.json"),
|
||||||
|
Path.Combine(directory, "param.json"),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (!File.Exists(paramPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var stream = File.OpenRead(paramPath);
|
||||||
|
using var document = JsonDocument.Parse(stream);
|
||||||
|
if (document.RootElement.TryGetProperty("titleId", out var titleIdElement) &&
|
||||||
|
titleIdElement.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
var titleId = titleIdElement.GetString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(titleId))
|
||||||
|
{
|
||||||
|
return titleId.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Logging should never block launch; unknown title ids use a stable fallback.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? TryFindEbootPathToken(IReadOnlyList<string> args)
|
||||||
|
{
|
||||||
|
for (var i = args.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var argument = args[i];
|
||||||
|
if (string.IsNullOrWhiteSpace(argument) ||
|
||||||
|
argument.StartsWith("--", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldConsumeLogFilePath(IReadOnlyList<string> args, int candidateIndex)
|
||||||
|
{
|
||||||
|
var candidate = args[candidateIndex];
|
||||||
|
if (LooksLikeLogFilePath(candidate))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = candidateIndex + 1; i < args.Count; i++)
|
||||||
|
{
|
||||||
|
var argument = args[i];
|
||||||
|
if (!string.IsNullOrWhiteSpace(argument) &&
|
||||||
|
!argument.StartsWith("--", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool LooksLikeLogFilePath(string path)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(path);
|
||||||
|
return string.Equals(extension, ".log", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryEnableConsoleFileMirror(string path)
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
if (_consoleMirrorFile is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(path);
|
||||||
|
if (!string.IsNullOrEmpty(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = new FileStream(
|
||||||
|
path,
|
||||||
|
FileMode.Create,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.ReadWrite,
|
||||||
|
bufferSize: 4096,
|
||||||
|
FileOptions.SequentialScan);
|
||||||
|
_consoleMirrorFile = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
|
||||||
|
{
|
||||||
|
AutoFlush = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.SetOut(new TeeTextWriter(Console.Out, _consoleMirrorFile));
|
||||||
|
Console.SetError(new TeeTextWriter(Console.Error, _consoleMirrorFile));
|
||||||
|
Console.Error.WriteLine($"[DEBUG] Log file: {Path.GetFullPath(path)}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[WARN] Could not open log file '{path}': {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DropConsoleFileMirror()
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_consoleMirrorFile?.Flush();
|
||||||
|
_consoleMirrorFile?.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
_consoleMirrorFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureInheritedStdHandles(ref STARTUPINFO startupInfo)
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
var output = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
var error = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
if (!IsHandleValid(output) && !IsHandleValid(error))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHandleValid(input))
|
||||||
|
{
|
||||||
|
_ = SetHandleInformation(input, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||||
|
startupInfo.hStdInput = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHandleValid(output))
|
||||||
|
{
|
||||||
|
_ = SetHandleInformation(output, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||||
|
startupInfo.hStdOutput = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHandleValid(error))
|
||||||
|
{
|
||||||
|
_ = SetHandleInformation(error, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||||
|
startupInfo.hStdError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildCommandLine(string processPath, IReadOnlyList<string> args)
|
private static string BuildCommandLine(string processPath, IReadOnlyList<string> args)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
@@ -503,27 +768,30 @@ internal static partial class Program
|
|||||||
|
|
||||||
private static void PrintUsage()
|
private static void PrintUsage()
|
||||||
{
|
{
|
||||||
Log.Info("Usage: SharpEmu.CLI [--strict] [--trace-imports[=N]] [--cpu-engine=<native>] [--log-level=<level>] <path-to-eboot.bin>");
|
Log.Info("Usage: SharpEmu.CLI [--strict] [--trace-imports[=N]] [--cpu-engine=<native>] [--log-level=<level>] [--log-file[=<path>]] <path-to-eboot.bin>");
|
||||||
Log.Info(@"Example: SharpEmu.CLI --cpu-engine=native --trace-imports=64 --log-level=debug ""E:\Games\...\eboot.bin""");
|
Log.Info(@"Example: SharpEmu.CLI --cpu-engine=native --trace-imports=64 --log-level=debug --log-file ""E:\Games\...\eboot.bin""");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryParseArguments(
|
private static bool TryParseArguments(
|
||||||
string[] args,
|
string[] args,
|
||||||
out string ebootPath,
|
out string ebootPath,
|
||||||
out SharpEmuRuntimeOptions runtimeOptions,
|
out SharpEmuRuntimeOptions runtimeOptions,
|
||||||
out LogLevel logLevel)
|
out LogLevel logLevel,
|
||||||
|
out string? logFilePath)
|
||||||
{
|
{
|
||||||
if (args.Length == 0)
|
if (args.Length == 0)
|
||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logLevel = SharpEmuLog.MinimumLevel;
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var strictDynlibResolution = false;
|
var strictDynlibResolution = false;
|
||||||
var importTraceLimit = 0;
|
var importTraceLimit = 0;
|
||||||
var cpuEngine = CpuExecutionEngine.NativeOnly;
|
var cpuEngine = CpuExecutionEngine.NativeOnly;
|
||||||
|
logFilePath = null;
|
||||||
logLevel = SharpEmuLog.MinimumLevel;
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
var pathTokens = new List<string>(args.Length);
|
var pathTokens = new List<string>(args.Length);
|
||||||
for (var i = 0; i < args.Length; i++)
|
for (var i = 0; i < args.Length; i++)
|
||||||
@@ -553,6 +821,7 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +835,7 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,6 +843,23 @@ internal static partial class Program
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(argument, "--log-file", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (i + 1 < args.Length &&
|
||||||
|
!string.IsNullOrWhiteSpace(args[i + 1]) &&
|
||||||
|
!args[i + 1].StartsWith("--", StringComparison.Ordinal) &&
|
||||||
|
ShouldConsumeLogFilePath(args, i + 1))
|
||||||
|
{
|
||||||
|
logFilePath = args[++i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logFilePath = BuildDefaultLogFilePath(TryFindEbootPathToken(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const string logLevelPrefix = "--log-level=";
|
const string logLevelPrefix = "--log-level=";
|
||||||
if (argument.StartsWith(logLevelPrefix, StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith(logLevelPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -596,6 +883,7 @@ internal static partial class Program
|
|||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logLevel = SharpEmuLog.MinimumLevel;
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,11 +906,27 @@ internal static partial class Program
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string logFilePrefix = "--log-file=";
|
||||||
|
if (argument.StartsWith(logFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logFilePath = argument[logFilePrefix.Length..];
|
||||||
|
if (string.IsNullOrWhiteSpace(logFilePath))
|
||||||
|
{
|
||||||
|
ebootPath = string.Empty;
|
||||||
|
runtimeOptions = default;
|
||||||
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--", StringComparison.Ordinal))
|
if (argument.StartsWith("--", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logLevel = SharpEmuLog.MinimumLevel;
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,6 +938,7 @@ internal static partial class Program
|
|||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logLevel = SharpEmuLog.MinimumLevel;
|
logLevel = SharpEmuLog.MinimumLevel;
|
||||||
|
logFilePath = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,6 +1040,56 @@ internal static partial class Program
|
|||||||
public nuint PeakJobMemoryUsed;
|
public nuint PeakJobMemoryUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class TeeTextWriter : TextWriter
|
||||||
|
{
|
||||||
|
private readonly TextWriter _primary;
|
||||||
|
private readonly TextWriter _mirror;
|
||||||
|
|
||||||
|
public TeeTextWriter(TextWriter primary, TextWriter mirror)
|
||||||
|
{
|
||||||
|
_primary = primary;
|
||||||
|
_mirror = mirror;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Encoding Encoding => _primary.Encoding;
|
||||||
|
|
||||||
|
public override void Write(char value)
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
_primary.Write(value);
|
||||||
|
_mirror.Write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(string? value)
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
_primary.Write(value);
|
||||||
|
_mirror.Write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteLine(string? value)
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
_primary.WriteLine(value);
|
||||||
|
_mirror.WriteLine(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
lock (ConsoleMirrorSync)
|
||||||
|
{
|
||||||
|
_primary.Flush();
|
||||||
|
_mirror.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
private static extern bool InitializeProcThreadAttributeList(
|
private static extern bool InitializeProcThreadAttributeList(
|
||||||
@@ -819,6 +1174,10 @@ internal static partial class Program
|
|||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
private static extern bool SetStdHandle(int stdHandle, nint handle);
|
private static extern bool SetStdHandle(int stdHandle, nint handle);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool SetHandleInformation(nint handle, uint mask, uint flags);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)]
|
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
private static extern nint CreateFileW(
|
private static extern nint CreateFileW(
|
||||||
string fileName,
|
string fileName,
|
||||||
|
|||||||
@@ -1992,9 +1992,16 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
byte* code = (byte*)ptr;
|
byte* code = (byte*)ptr;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
EmitByte(code, ref offset, 0x48); // sub rsp, 0x20
|
// TlsGetValue returns its TLS pointer in RAX. Preserve the guest return value
|
||||||
|
// above the 32-byte Windows shadow space while keeping the call site aligned.
|
||||||
|
EmitByte(code, ref offset, 0x48); // sub rsp, 0x30
|
||||||
EmitByte(code, ref offset, 0x83);
|
EmitByte(code, ref offset, 0x83);
|
||||||
EmitByte(code, ref offset, 0xEC);
|
EmitByte(code, ref offset, 0xEC);
|
||||||
|
EmitByte(code, ref offset, 0x30);
|
||||||
|
EmitByte(code, ref offset, 0x48); // mov [rsp+0x20], rax
|
||||||
|
EmitByte(code, ref offset, 0x89);
|
||||||
|
EmitByte(code, ref offset, 0x44);
|
||||||
|
EmitByte(code, ref offset, 0x24);
|
||||||
EmitByte(code, ref offset, 0x20);
|
EmitByte(code, ref offset, 0x20);
|
||||||
EmitByte(code, ref offset, 0xB9); // mov ecx, tlsIndex
|
EmitByte(code, ref offset, 0xB9); // mov ecx, tlsIndex
|
||||||
EmitUInt32(code, ref offset, _hostRspSlotTlsIndex);
|
EmitUInt32(code, ref offset, _hostRspSlotTlsIndex);
|
||||||
@@ -2004,13 +2011,21 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
offset += sizeof(ulong);
|
offset += sizeof(ulong);
|
||||||
EmitByte(code, ref offset, 0xFF); // call rax
|
EmitByte(code, ref offset, 0xFF); // call rax
|
||||||
EmitByte(code, ref offset, 0xD0);
|
EmitByte(code, ref offset, 0xD0);
|
||||||
EmitByte(code, ref offset, 0x48); // add rsp, 0x20
|
EmitByte(code, ref offset, 0x49); // mov r11, rax
|
||||||
|
EmitByte(code, ref offset, 0x89);
|
||||||
|
EmitByte(code, ref offset, 0xC3);
|
||||||
|
EmitByte(code, ref offset, 0x48); // mov rax, [rsp+0x20]
|
||||||
|
EmitByte(code, ref offset, 0x8B);
|
||||||
|
EmitByte(code, ref offset, 0x44);
|
||||||
|
EmitByte(code, ref offset, 0x24);
|
||||||
|
EmitByte(code, ref offset, 0x20);
|
||||||
|
EmitByte(code, ref offset, 0x48); // add rsp, 0x30
|
||||||
EmitByte(code, ref offset, 0x83);
|
EmitByte(code, ref offset, 0x83);
|
||||||
EmitByte(code, ref offset, 0xC4);
|
EmitByte(code, ref offset, 0xC4);
|
||||||
EmitByte(code, ref offset, 0x20);
|
EmitByte(code, ref offset, 0x30);
|
||||||
EmitByte(code, ref offset, 0x48); // mov rsp, [rax]
|
EmitByte(code, ref offset, 0x49); // mov rsp, [r11]
|
||||||
EmitByte(code, ref offset, 0x8B);
|
EmitByte(code, ref offset, 0x8B);
|
||||||
EmitByte(code, ref offset, 0x20);
|
EmitByte(code, ref offset, 0x23);
|
||||||
EmitHostNonvolatileXmmRestore(code, ref offset);
|
EmitHostNonvolatileXmmRestore(code, ref offset);
|
||||||
EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5F);
|
EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5F);
|
||||||
EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5E);
|
EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5E);
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
|||||||
ImportTraceLimit = Math.Max(0, options.ImportTraceLimit),
|
ImportTraceLimit = Math.Max(0, options.ImportTraceLimit),
|
||||||
};
|
};
|
||||||
var moduleManager = new ModuleManager();
|
var moduleManager = new ModuleManager();
|
||||||
moduleManager.RegisterFromAssembly(typeof(VideoOutExports).Assembly, Generation.Gen4 | Generation.Gen5, Aerolib.Instance);
|
|
||||||
moduleManager.RegisterFromAssembly(typeof(KernelExports).Assembly, Generation.Gen4 | Generation.Gen5, Aerolib.Instance);
|
moduleManager.RegisterFromAssembly(typeof(KernelExports).Assembly, Generation.Gen4 | Generation.Gen5, Aerolib.Instance);
|
||||||
moduleManager.Freeze();
|
moduleManager.Freeze();
|
||||||
|
|
||||||
|
|||||||
@@ -168,8 +168,7 @@ internal sealed class EmulatorProcess : IDisposable
|
|||||||
var policy1 = PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
var policy1 = PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
||||||
var policy2 =
|
var policy2 =
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF |
|
PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF |
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF |
|
PROCESS_CREATION_MITIGATION_POLICY2_USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF;
|
||||||
PROCESS_CREATION_MITIGATION_POLICY2_XTENDED_CONTROL_FLOW_GUARD_ALWAYS_OFF;
|
|
||||||
|
|
||||||
mitigationPolicies = Marshal.AllocHGlobal(sizeof(ulong) * 2);
|
mitigationPolicies = Marshal.AllocHGlobal(sizeof(ulong) * 2);
|
||||||
Marshal.WriteInt64(mitigationPolicies, unchecked((long)policy1));
|
Marshal.WriteInt64(mitigationPolicies, unchecked((long)policy1));
|
||||||
@@ -200,29 +199,10 @@ internal sealed class EmulatorProcess : IDisposable
|
|||||||
ref startupInfoEx,
|
ref startupInfoEx,
|
||||||
out var processInfo);
|
out var processInfo);
|
||||||
|
|
||||||
if (!created)
|
|
||||||
{
|
|
||||||
// Some mitigation policy bits (e.g. XFG) are not supported on
|
|
||||||
// older Windows builds. Mirror the CLI's behavior and fall back
|
|
||||||
// to launching without the mitigation attribute list.
|
|
||||||
startupInfoEx.lpAttributeList = 0;
|
|
||||||
created = CreateProcessW(
|
|
||||||
exePath,
|
|
||||||
new StringBuilder(BuildCommandLine(exePath, arguments)),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
CREATE_NO_WINDOW,
|
|
||||||
0,
|
|
||||||
currentDirectory,
|
|
||||||
ref startupInfoEx,
|
|
||||||
out processInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!created)
|
if (!created)
|
||||||
{
|
{
|
||||||
var error = Marshal.GetLastWin32Error();
|
var error = Marshal.GetLastWin32Error();
|
||||||
throw new Win32Exception(error, $"Failed to start '{exePath}' (Win32 error {error}: {new Win32Exception(error).Message}).");
|
throw new Win32Exception(error, $"Failed to start '{exePath}' with CET/CFG mitigation disabled (Win32 error {error}: {new Win32Exception(error).Message}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(processInfo.hThread);
|
CloseHandle(processInfo.hThread);
|
||||||
|
|||||||
@@ -1061,13 +1061,11 @@ public partial class MainWindow : Window
|
|||||||
arguments.Add($"--trace-imports={_settings.ImportTraceLimit}");
|
arguments.Add($"--trace-imports={_settings.ImportTraceLimit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments.Add(ebootPath);
|
|
||||||
|
|
||||||
_consoleLines.Clear();
|
_consoleLines.Clear();
|
||||||
ConsoleToggle.IsChecked = true;
|
ConsoleToggle.IsChecked = true;
|
||||||
|
|
||||||
// Mirror everything the console pane shows into a log file for the
|
// Let the CLI mirror stdout/stderr itself; it sees loader/native
|
||||||
// duration of the run, regardless of the emulator's log level.
|
// diagnostics before the GUI pipe reader can filter or batch them.
|
||||||
DropFileLog();
|
DropFileLog();
|
||||||
if (_settings.LogToFile)
|
if (_settings.LogToFile)
|
||||||
{
|
{
|
||||||
@@ -1105,18 +1103,14 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(filePath))
|
if (!string.IsNullOrEmpty(filePath))
|
||||||
{
|
{
|
||||||
try
|
arguments.Add("--log-file");
|
||||||
{
|
arguments.Add(filePath);
|
||||||
_fileLog = new StreamWriter(filePath, append: false);
|
|
||||||
AppendConsoleLine($"Log file: {filePath}", DimLineBrush);
|
AppendConsoleLine($"Log file: {filePath}", DimLineBrush);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
AppendConsoleLine($"Could not open the log file: {ex.Message}", WarningLineBrush);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arguments.Add(ebootPath);
|
||||||
|
|
||||||
AppendConsoleLine($"$ SharpEmu {string.Join(' ', arguments)}", DimLineBrush);
|
AppendConsoleLine($"$ SharpEmu {string.Join(' ', arguments)}", DimLineBrush);
|
||||||
|
|
||||||
var emulator = new EmulatorProcess();
|
var emulator = new EmulatorProcess();
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var src0 = word & 0x1FF;
|
var src0 = word & 0x1FF;
|
||||||
sizeDwords = opcode is 0x20 or 0x21 || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
|
sizeDwords = opcode is 0x20 or 0x21 or 0x2C or 0x2D || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
name = opcode switch
|
name = opcode switch
|
||||||
{
|
{
|
||||||
@@ -757,7 +757,9 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x28 => "VAddcU32",
|
0x28 => "VAddcU32",
|
||||||
0x29 => "VSubbU32",
|
0x29 => "VSubbU32",
|
||||||
0x2A => "VSubbrevU32",
|
0x2A => "VSubbrevU32",
|
||||||
0x2B => "VLdexpF32",
|
0x2B => "VFmacF32",
|
||||||
|
0x2C => "VFmamkF32",
|
||||||
|
0x2D => "VFmaakF32",
|
||||||
0x2F => "VCvtPkrtzF16F32",
|
0x2F => "VCvtPkrtzF16F32",
|
||||||
0x30 => "VCvtPkU16U32",
|
0x30 => "VCvtPkU16U32",
|
||||||
0x31 => "VCvtPkI16I32",
|
0x31 => "VCvtPkI16I32",
|
||||||
@@ -870,6 +872,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x10F => "VMinF32",
|
0x10F => "VMinF32",
|
||||||
0x110 => "VMaxF32",
|
0x110 => "VMaxF32",
|
||||||
0x11F => "VMacF32",
|
0x11F => "VMacF32",
|
||||||
|
0x12B => "VFmacF32",
|
||||||
0x12F => "VCvtPkrtzF16F32",
|
0x12F => "VCvtPkrtzF16F32",
|
||||||
0x141 => "VMadF32",
|
0x141 => "VMadF32",
|
||||||
0x143 => "VMadU32U24",
|
0x143 => "VMadU32U24",
|
||||||
@@ -897,6 +900,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x169 => "VMulLoU32",
|
0x169 => "VMulLoU32",
|
||||||
0x16A => "VMulHiU32",
|
0x16A => "VMulHiU32",
|
||||||
0x16B => "VMulLoI32",
|
0x16B => "VMulLoI32",
|
||||||
|
0x16C => "VMulHiI32",
|
||||||
0x360 => "VMadU32U16",
|
0x360 => "VMadU32U16",
|
||||||
0x361 => "VMulLoU32",
|
0x361 => "VMulLoU32",
|
||||||
0x362 => "VLdexpF32",
|
0x362 => "VLdexpF32",
|
||||||
@@ -1379,7 +1383,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
Gen5Operand.Source(word & 0x1FF, literal),
|
Gen5Operand.Source(word & 0x1FF, literal),
|
||||||
Gen5Operand.Vector((word >> 9) & 0xFF),
|
Gen5Operand.Vector((word >> 9) & 0xFF),
|
||||||
];
|
];
|
||||||
if (opcode == "VMadMkF32" && literal.HasValue)
|
if (opcode is "VMadMkF32" or "VFmamkF32" && literal.HasValue)
|
||||||
{
|
{
|
||||||
sources =
|
sources =
|
||||||
[
|
[
|
||||||
@@ -1388,7 +1392,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
sources[1],
|
sources[1],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else if (opcode == "VMadAkF32" && literal.HasValue)
|
else if (opcode is "VMadAkF32" or "VFmaakF32" && literal.HasValue)
|
||||||
{
|
{
|
||||||
sources =
|
sources =
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
case "VFmaF32":
|
case "VFmaF32":
|
||||||
case "VMadMkF32":
|
case "VMadMkF32":
|
||||||
case "VMadAkF32":
|
case "VMadAkF32":
|
||||||
|
case "VFmamkF32":
|
||||||
|
case "VFmaakF32":
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
instruction,
|
instruction,
|
||||||
Ext(
|
Ext(
|
||||||
@@ -265,6 +267,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
GetFloatSource(instruction, 2)));
|
GetFloatSource(instruction, 2)));
|
||||||
break;
|
break;
|
||||||
case "VMacF32":
|
case "VMacF32":
|
||||||
|
case "VFmacF32":
|
||||||
{
|
{
|
||||||
var addend = Bitcast(_floatType, LoadV(destination));
|
var addend = Bitcast(_floatType, LoadV(destination));
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
@@ -334,6 +337,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
result = EmitSubtractWithBorrow(instruction, reverse: true);
|
result = EmitSubtractWithBorrow(instruction, reverse: true);
|
||||||
break;
|
break;
|
||||||
case "VMulLoU32":
|
case "VMulLoU32":
|
||||||
|
case "VMulLoI32":
|
||||||
case "VMulU32U24":
|
case "VMulU32U24":
|
||||||
result = EmitIntegerBinary(instruction, SpirvOp.IMul);
|
result = EmitIntegerBinary(instruction, SpirvOp.IMul);
|
||||||
break;
|
break;
|
||||||
@@ -369,6 +373,29 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.Constant64(_ulongType, 32)));
|
_module.Constant64(_ulongType, 32)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "VMulHiI32":
|
||||||
|
{
|
||||||
|
var wideLeft = _module.AddInstruction(
|
||||||
|
SpirvOp.SConvert,
|
||||||
|
_longType,
|
||||||
|
Bitcast(_intType, GetRawSource(instruction, 0)));
|
||||||
|
var wideRight = _module.AddInstruction(
|
||||||
|
SpirvOp.SConvert,
|
||||||
|
_longType,
|
||||||
|
Bitcast(_intType, GetRawSource(instruction, 1)));
|
||||||
|
var product = _module.AddInstruction(
|
||||||
|
SpirvOp.IMul,
|
||||||
|
_longType,
|
||||||
|
wideLeft,
|
||||||
|
wideRight);
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.UConvert,
|
||||||
|
_uintType,
|
||||||
|
ShiftRightLogical64(
|
||||||
|
Bitcast(_ulongType, product),
|
||||||
|
_module.Constant64(_ulongType, 32)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "VMadU32U24":
|
case "VMadU32U24":
|
||||||
{
|
{
|
||||||
var left = BitwiseAnd(
|
var left = BitwiseAnd(
|
||||||
|
|||||||
@@ -9,6 +9,189 @@ namespace SharpEmu.Libs.Rtc;
|
|||||||
public static class RtcExports
|
public static class RtcExports
|
||||||
{
|
{
|
||||||
private const long DateTimeTicksPerMicrosecond = 10;
|
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(
|
[SysAbiExport(
|
||||||
Nid = "ZPD1YOKI+Kw",
|
Nid = "ZPD1YOKI+Kw",
|
||||||
@@ -20,30 +203,38 @@ public static class RtcExports
|
|||||||
var timeAddress = ctx[CpuRegister.Rdi];
|
var timeAddress = ctx[CpuRegister.Rdi];
|
||||||
if (timeAddress == 0)
|
if (timeAddress == 0)
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return unchecked((int)0x80B50002);
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTimeOffset.Now;
|
if (!TryWriteRtcDateTime(ctx, timeAddress, ToRtcDateTime(DateTimeOffset.Now.DateTime)))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
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(
|
[SysAbiExport(
|
||||||
Nid = "18B2NS1y9UU",
|
Nid = "18B2NS1y9UU",
|
||||||
ExportName = "sceRtcGetCurrentTick",
|
ExportName = "sceRtcGetCurrentTick",
|
||||||
@@ -54,7 +245,7 @@ public static class RtcExports
|
|||||||
var tickAddress = ctx[CpuRegister.Rdi];
|
var tickAddress = ctx[CpuRegister.Rdi];
|
||||||
if (tickAddress == 0)
|
if (tickAddress == 0)
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return unchecked((int)0x80B50002);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tickValue = unchecked((ulong)(DateTime.UtcNow.Ticks / DateTimeTicksPerMicrosecond));
|
var tickValue = unchecked((ulong)(DateTime.UtcNow.Ticks / DateTimeTicksPerMicrosecond));
|
||||||
@@ -63,7 +254,87 @@ public static class RtcExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
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;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,45 +345,220 @@ public static class RtcExports
|
|||||||
LibraryName = "libSceRtc")]
|
LibraryName = "libSceRtc")]
|
||||||
public static int RtcGetTick(CpuContext ctx)
|
public static int RtcGetTick(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var dateTimeAddress = ctx[CpuRegister.Rdi];
|
var timeAddress = ctx[CpuRegister.Rdi];
|
||||||
var tickAddress = ctx[CpuRegister.Rsi];
|
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;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong tickValue;
|
var validationResult = ValidateRtcDateTime(time);
|
||||||
try
|
if (validationResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||||
{
|
{
|
||||||
var baseDateTime = new DateTime(
|
return validationResult;
|
||||||
rtcDateTime.Year,
|
|
||||||
rtcDateTime.Month,
|
|
||||||
rtcDateTime.Day,
|
|
||||||
rtcDateTime.Hour,
|
|
||||||
rtcDateTime.Minute,
|
|
||||||
rtcDateTime.Second,
|
|
||||||
DateTimeKind.Utc);
|
|
||||||
tickValue = checked((ulong)((baseDateTime.Ticks / DateTimeTicksPerMicrosecond) + rtcDateTime.Microsecond));
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is ArgumentOutOfRangeException or OverflowException)
|
|
||||||
|
if (!TryConvertRtcDateTimeToTick(time, out var tick))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
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;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
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(
|
[SysAbiExport(
|
||||||
Nid = "ueega6v3GUw",
|
Nid = "ueega6v3GUw",
|
||||||
ExportName = "sceRtcSetTick",
|
ExportName = "sceRtcSetTick",
|
||||||
@@ -120,11 +566,11 @@ public static class RtcExports
|
|||||||
LibraryName = "libSceRtc")]
|
LibraryName = "libSceRtc")]
|
||||||
public static int RtcSetTick(CpuContext ctx)
|
public static int RtcSetTick(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var dateTimeAddress = ctx[CpuRegister.Rdi];
|
var timeAddress = ctx[CpuRegister.Rdi];
|
||||||
var tickAddress = ctx[CpuRegister.Rsi];
|
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))
|
if (!ctx.TryReadUInt64(tickAddress, out var tickValue))
|
||||||
@@ -132,40 +578,249 @@ public static class RtcExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
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;
|
if (!TryWriteRtcDateTime(ctx, timeAddress, time))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
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];
|
Span<byte> buffer = stackalloc byte[16];
|
||||||
if (!ctx.Memory.TryRead(address, buffer))
|
if (!ctx.Memory.TryRead(address, buffer))
|
||||||
{
|
{
|
||||||
rtcDateTime = default;
|
time = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rtcDateTime = new RtcDateTime(
|
time = new RtcDateTime(
|
||||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
|
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
|
||||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
|
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
|
||||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
|
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
|
||||||
@@ -176,19 +831,117 @@ public static class RtcExports
|
|||||||
return true;
|
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];
|
Span<byte> buffer = stackalloc byte[16];
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)dateTime.Year));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[0..2], dateTime.Year);
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)dateTime.Month));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[2..4], dateTime.Month);
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)dateTime.Day));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..6], dateTime.Day);
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)dateTime.Hour));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[6..8], dateTime.Hour);
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)dateTime.Minute));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[8..10], dateTime.Minute);
|
||||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)dateTime.Second));
|
BinaryPrimitives.WriteUInt16LittleEndian(buffer[10..12], dateTime.Second);
|
||||||
BinaryPrimitives.WriteUInt32LittleEndian(
|
BinaryPrimitives.WriteUInt32LittleEndian(buffer[12..16], dateTime.Microsecond);
|
||||||
rtcDateTime[12..16],
|
return ctx.Memory.TryWrite(address, buffer);
|
||||||
checked((uint)((dateTime.Ticks % TimeSpan.TicksPerSecond) / DateTimeTicksPerMicrosecond)));
|
}
|
||||||
return ctx.Memory.TryWrite(address, rtcDateTime);
|
|
||||||
|
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(
|
private readonly record struct RtcDateTime(
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private static uint _windowHeight;
|
private static uint _windowHeight;
|
||||||
private static bool _closed;
|
private static bool _closed;
|
||||||
private const string DebugUtilsExtensionName = "VK_EXT_debug_utils";
|
private const string DebugUtilsExtensionName = "VK_EXT_debug_utils";
|
||||||
|
private const uint NvidiaVendorId = 0x10DE;
|
||||||
private static bool _splashHidden;
|
private static bool _splashHidden;
|
||||||
private static long _enqueuedGuestWorkSequence;
|
private static long _enqueuedGuestWorkSequence;
|
||||||
private static long _completedGuestWorkSequence;
|
private static long _completedGuestWorkSequence;
|
||||||
@@ -1502,6 +1503,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Check(_vk.EnumeratePhysicalDevices(_instance, &deviceCount, devicePointer), "vkEnumeratePhysicalDevices");
|
Check(_vk.EnumeratePhysicalDevices(_instance, &deviceCount, devicePointer), "vkEnumeratePhysicalDevices");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hybrid laptops enumerate the iGPU first, and AMD's integrated driver
|
||||||
|
// segfaults compiling some translated shaders, so rank rather than take
|
||||||
|
// the first hit. SHARPEMU_VK_DEVICE=<substring> pins an adapter by name.
|
||||||
|
var deviceOverride = Environment.GetEnvironmentVariable("SHARPEMU_VK_DEVICE");
|
||||||
|
var bestScore = int.MinValue;
|
||||||
|
var found = false;
|
||||||
|
|
||||||
foreach (var device in devices)
|
foreach (var device in devices)
|
||||||
{
|
{
|
||||||
uint queueCount = 0;
|
uint queueCount = 0;
|
||||||
@@ -1521,15 +1529,62 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_vk.GetPhysicalDeviceProperties(device, out var properties);
|
||||||
|
var name = SilkMarshal.PtrToString((nint)properties.DeviceName) ?? string.Empty;
|
||||||
|
var score = ScorePhysicalDevice(properties, name, deviceOverride);
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Vulkan candidate: {name} ({properties.DeviceType}) score={score}");
|
||||||
|
|
||||||
|
if (score > bestScore)
|
||||||
|
{
|
||||||
|
bestScore = score;
|
||||||
_physicalDevice = device;
|
_physicalDevice = device;
|
||||||
_queueFamilyIndex = index;
|
_queueFamilyIndex = index;
|
||||||
return;
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("No Vulkan graphics/present queue was found.");
|
throw new InvalidOperationException("No Vulkan graphics/present queue was found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var selected);
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Vulkan device: {SilkMarshal.PtrToString((nint)selected.DeviceName)} ({selected.DeviceType})");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ScorePhysicalDevice(
|
||||||
|
PhysicalDeviceProperties properties,
|
||||||
|
string name,
|
||||||
|
string? deviceOverride)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(deviceOverride))
|
||||||
|
{
|
||||||
|
return name.Contains(deviceOverride, StringComparison.OrdinalIgnoreCase) ? 1000 : -1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
var score = properties.DeviceType switch
|
||||||
|
{
|
||||||
|
PhysicalDeviceType.DiscreteGpu => 300,
|
||||||
|
PhysicalDeviceType.VirtualGpu => 100,
|
||||||
|
PhysicalDeviceType.Cpu => 50,
|
||||||
|
// Last resort: only picked when nothing else can present.
|
||||||
|
PhysicalDeviceType.IntegratedGpu => -100,
|
||||||
|
_ => 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (properties.VendorID == NvidiaVendorId)
|
||||||
|
{
|
||||||
|
score += 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateDevice()
|
private void CreateDevice()
|
||||||
{
|
{
|
||||||
var priority = 1.0f;
|
var priority = 1.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user