mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-28 05:30:57 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b522776988 |
@@ -1,70 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (C) 2026 SharpEmu Emulator Project
|
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Contributions are always welcome!
|
|
||||||
|
|
||||||
Before opening a pull request, please keep the following in mind:
|
|
||||||
|
|
||||||
- Keep PRs small and focused on a single topic.
|
|
||||||
- Discuss large architectural changes before implementing them.
|
|
||||||
- Follow the project's existing coding style.
|
|
||||||
- Do not submit generated code unless you fully understand and can maintain it.
|
|
||||||
- Do not introduce Sony proprietary code, firmware, keys, decrypted assets, or other copyrighted PlayStation materials.
|
|
||||||
- All reverse engineering should be based on publicly available information, clean-room techniques, or your own original research.
|
|
||||||
- Game-specific hacks should be avoided whenever possible. Prefer generic implementations that improve overall compatibility.
|
|
||||||
- New features should not break existing behavior.
|
|
||||||
- Ensure the project builds successfully before submitting a PR.
|
|
||||||
|
|
||||||
If you're unsure about a design decision, feel free to open a discussion or draft PR first.
|
|
||||||
|
|
||||||
## AI-Assisted Contributions
|
|
||||||
|
|
||||||
AI-assisted development is welcome and may be used for research, reverse engineering, code generation, or documentation.
|
|
||||||
|
|
||||||
However, contributors are expected to fully understand every line of code they submit. By opening a pull request, you confirm that you are able to explain, modify, debug, and maintain the submitted code without relying on the AI that generated it.
|
|
||||||
|
|
||||||
When submitting an AI-assisted PR:
|
|
||||||
|
|
||||||
- Clearly explain **what the change does**, **why it is needed**, and **what problem it solves**, using your own words.
|
|
||||||
- Describe **how you verified the change**, including the games, applications, or test cases used.
|
|
||||||
- Avoid excessive product-level logging. Use logging only when it provides meaningful diagnostic value.
|
|
||||||
- Comments should document design decisions or implementation details in your own words. Avoid generic AI-generated comments that merely restate what the code already does.
|
|
||||||
- Be prepared to answer review questions about the implementation. "The AI generated it" is not considered a sufficient explanation.
|
|
||||||
- Large AI-generated changes without a clear understanding of the implementation are unlikely to be accepted.
|
|
||||||
- If the implementation cannot be reasonably explained during code review, the pull request may be rejected regardless of whether it works.
|
|
||||||
|
|
||||||
The quality, correctness, maintainability, and long-term ownership of the submitted code remain the responsibility of the contributor.
|
|
||||||
|
|
||||||
## Coding Style
|
|
||||||
|
|
||||||
SharpEmu follows a consistent coding style across the project. Please ensure your contributions match the existing style.
|
|
||||||
|
|
||||||
- Use **4 spaces** for indentation (no tabs).
|
|
||||||
- Use **2 spaces** for XML-based files (e.g. `.csproj`, `.props`, `.targets`, `.xml`, `yml`, GitHub workflow files where applicable).
|
|
||||||
- Respect the project's `.editorconfig`.
|
|
||||||
- Ensure every text file ends with a **single trailing newline**
|
|
||||||
- Avoid formatting-only commits unless they are the purpose of the PR.
|
|
||||||
- Keep naming, formatting, and file organization consistent with the surrounding code.
|
|
||||||
- Prefer small, focused changes over large refactors.
|
|
||||||
|
|
||||||
### REUSE Compliance
|
|
||||||
|
|
||||||
This repository follows the REUSE Specification.
|
|
||||||
|
|
||||||
Every new file must contain the appropriate SPDX license header. Pull requests that do not comply with the project's REUSE requirements will fail CI and will not be merged.
|
|
||||||
|
|
||||||
### Recommended Development Environment
|
|
||||||
|
|
||||||
The repository includes an `.editorconfig` and Visual Studio solution files.
|
|
||||||
|
|
||||||
For the best experience, we recommend using:
|
|
||||||
|
|
||||||
- Visual Studio Code with;
|
|
||||||
- C#
|
|
||||||
- C# Dev Kit
|
|
||||||
|
|
||||||
Most editors that support `.editorconfig` will automatically apply the project's formatting rules.
|
|
||||||
@@ -122,16 +122,3 @@ Provided valuable references for filesystem handling and low-level C# implementa
|
|||||||
# License
|
# License
|
||||||
|
|
||||||
- [**GPL-2.0 license**](https://github.com/par274/sharpemu/blob/main/LICENSE)
|
- [**GPL-2.0 license**](https://github.com/par274/sharpemu/blob/main/LICENSE)
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Before opening an issue or pull request, please read our contribution guidelines:
|
|
||||||
|
|
||||||
**[CONTRIBUTING.md](./CONTRIBUTING.md)**
|
|
||||||
|
|
||||||
The guide covers:
|
|
||||||
- Coding style and formatting
|
|
||||||
- AI-assisted contributions
|
|
||||||
- Pull request expectations
|
|
||||||
- Testing guidelines
|
|
||||||
- Legal and reverse engineering policy
|
|
||||||
|
|||||||
+13
-372
@@ -8,15 +8,12 @@ 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;
|
||||||
@@ -24,15 +21,11 @@ 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;
|
||||||
@@ -50,7 +43,6 @@ internal static partial class Program
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DropConsoleFileMirror();
|
|
||||||
SharpEmuLog.Shutdown();
|
SharpEmuLog.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,10 +61,6 @@ 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");
|
||||||
|
|
||||||
@@ -81,17 +69,12 @@ internal static partial class Program
|
|||||||
return childExitCode;
|
return childExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryParseArguments(args, out var ebootPath, out var runtimeOptions, out var logLevel, out var logFilePath))
|
if (!TryParseArguments(args, out var ebootPath, out var runtimeOptions, out var logLevel))
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -251,10 +234,6 @@ 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;
|
||||||
@@ -265,7 +244,7 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
if (string.Equals(arg, MitigatedChildFlag, StringComparison.Ordinal))
|
if (string.Equals(arg, MitigatedChildFlag, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
isMitigatedChild = trustedMitigatedChild;
|
isMitigatedChild = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,11 +283,9 @@ 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;
|
||||||
@@ -316,9 +293,7 @@ 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))
|
||||||
{
|
{
|
||||||
childExitCode = 5;
|
return false;
|
||||||
Console.Error.WriteLine($"[ERROR] Failed to initialize mitigation attributes: {Marshal.GetLastWin32Error()}");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startupInfoEx.lpAttributeList = attributeList;
|
startupInfoEx.lpAttributeList = attributeList;
|
||||||
@@ -326,7 +301,8 @@ 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));
|
||||||
@@ -341,31 +317,24 @@ internal static partial class Program
|
|||||||
0,
|
0,
|
||||||
0))
|
0))
|
||||||
{
|
{
|
||||||
childExitCode = 5;
|
return false;
|
||||||
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;
|
||||||
Environment.SetEnvironmentVariable(MitigatedChildEnvironment, "1");
|
if (!CreateProcessW(
|
||||||
var created = CreateProcessW(
|
|
||||||
processPath,
|
processPath,
|
||||||
cmdLineBuilder,
|
cmdLineBuilder,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
true,
|
false,
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
childExitCode = 5;
|
return false;
|
||||||
Console.Error.WriteLine($"[ERROR] Failed to launch mitigated child process: {Marshal.GetLastWin32Error()}");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -419,8 +388,6 @@ internal static partial class Program
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Environment.SetEnvironmentVariable(MitigatedChildEnvironment, previousChildEnvironment);
|
|
||||||
|
|
||||||
if (attributeList != 0)
|
if (attributeList != 0)
|
||||||
{
|
{
|
||||||
DeleteProcThreadAttributeList(attributeList);
|
DeleteProcThreadAttributeList(attributeList);
|
||||||
@@ -434,238 +401,6 @@ 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();
|
||||||
@@ -768,30 +503,27 @@ 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>] [--log-file[=<path>]] <path-to-eboot.bin>");
|
Log.Info("Usage: SharpEmu.CLI [--strict] [--trace-imports[=N]] [--cpu-engine=<native>] [--log-level=<level>] <path-to-eboot.bin>");
|
||||||
Log.Info(@"Example: SharpEmu.CLI --cpu-engine=native --trace-imports=64 --log-level=debug --log-file ""E:\Games\...\eboot.bin""");
|
Log.Info(@"Example: SharpEmu.CLI --cpu-engine=native --trace-imports=64 --log-level=debug ""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++)
|
||||||
@@ -821,7 +553,6 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logFilePath = null;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +566,6 @@ internal static partial class Program
|
|||||||
{
|
{
|
||||||
ebootPath = string.Empty;
|
ebootPath = string.Empty;
|
||||||
runtimeOptions = default;
|
runtimeOptions = default;
|
||||||
logFilePath = null;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,23 +573,6 @@ 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))
|
||||||
{
|
{
|
||||||
@@ -883,7 +596,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,27 +618,11 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,7 +634,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1040,56 +735,6 @@ 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(
|
||||||
@@ -1174,10 +819,6 @@ 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,
|
||||||
|
|||||||
@@ -38,71 +38,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecordDeferredBootstrapTrace(
|
|
||||||
long dispatchIndex,
|
|
||||||
ulong op,
|
|
||||||
ulong symbolPointer,
|
|
||||||
ulong outputPointer,
|
|
||||||
ulong returnRip)
|
|
||||||
{
|
|
||||||
lock (_deferredBootstrapTraceGate)
|
|
||||||
{
|
|
||||||
_deferredBootstrapTrace[_deferredBootstrapTraceWriteIndex] = new DeferredBootstrapTraceEntry(
|
|
||||||
dispatchIndex,
|
|
||||||
op,
|
|
||||||
symbolPointer,
|
|
||||||
outputPointer,
|
|
||||||
returnRip);
|
|
||||||
_deferredBootstrapTraceWriteIndex =
|
|
||||||
(_deferredBootstrapTraceWriteIndex + 1) % _deferredBootstrapTrace.Length;
|
|
||||||
if (_deferredBootstrapTraceCount < _deferredBootstrapTrace.Length)
|
|
||||||
{
|
|
||||||
_deferredBootstrapTraceCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrainDeferredBootstrapTraces()
|
|
||||||
{
|
|
||||||
if (!_logBootstrap)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeferredBootstrapTraceEntry[] pending;
|
|
||||||
lock (_deferredBootstrapTraceGate)
|
|
||||||
{
|
|
||||||
if (_deferredBootstrapTraceCount == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pending = new DeferredBootstrapTraceEntry[_deferredBootstrapTraceCount];
|
|
||||||
var readIndex = (_deferredBootstrapTraceWriteIndex - _deferredBootstrapTraceCount +
|
|
||||||
_deferredBootstrapTrace.Length) % _deferredBootstrapTrace.Length;
|
|
||||||
for (var i = 0; i < _deferredBootstrapTraceCount; i++)
|
|
||||||
{
|
|
||||||
pending[i] = _deferredBootstrapTrace[(readIndex + i) % _deferredBootstrapTrace.Length];
|
|
||||||
}
|
|
||||||
|
|
||||||
_deferredBootstrapTraceCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in pending)
|
|
||||||
{
|
|
||||||
var symbolText = "<unreadable>";
|
|
||||||
if (TryReadAsciiZ(entry.SymbolPointer, 256, out var sym))
|
|
||||||
{
|
|
||||||
symbolText = sym;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
|
||||||
$"[LOADER][TRACE] bootstrap_call#{entry.DispatchIndex}: op=0x{entry.Op:X16} " +
|
|
||||||
$"sym_ptr=0x{entry.SymbolPointer:X16} sym='{symbolText}' " +
|
|
||||||
$"out_ptr=0x{entry.OutputPointer:X16} ret=0x{entry.ReturnRip:X16}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DumpRecentImportTrace()
|
private void DumpRecentImportTrace()
|
||||||
{
|
{
|
||||||
if (_recentImportTraceCount == 0)
|
if (_recentImportTraceCount == 0)
|
||||||
@@ -235,15 +170,14 @@ public sealed partial class DirectExecutionBackend
|
|||||||
ulong callRip = returnRip + (ulong)i;
|
ulong callRip = returnRip + (ulong)i;
|
||||||
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
|
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
|
||||||
Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
||||||
var importEntries = _importEntries;
|
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||||
for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
|
|
||||||
{
|
{
|
||||||
if (importEntries[importIndex].Address != target)
|
if (_importEntries[importIndex].Address != target)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string nid = importEntries[importIndex].Nid;
|
string nid = _importEntries[importIndex].Nid;
|
||||||
if (_moduleManager.TryGetExport(nid, out var export))
|
if (_moduleManager.TryGetExport(nid, out var export))
|
||||||
{
|
{
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
@@ -270,14 +204,14 @@ public sealed partial class DirectExecutionBackend
|
|||||||
{
|
{
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
$"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
$"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
||||||
for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
|
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||||
{
|
{
|
||||||
if (importEntries[importIndex].Address != slotTarget)
|
if (_importEntries[importIndex].Address != slotTarget)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string nid = importEntries[importIndex].Nid;
|
string nid = _importEntries[importIndex].Nid;
|
||||||
if (_moduleManager.TryGetExport(nid, out var export))
|
if (_moduleManager.TryGetExport(nid, out var export))
|
||||||
{
|
{
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System.Linq;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using SharpEmu.Core.Cpu;
|
using SharpEmu.Core.Cpu;
|
||||||
using SharpEmu.Core.Loader;
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
namespace SharpEmu.Core.Cpu.Native;
|
namespace SharpEmu.Core.Cpu.Native;
|
||||||
@@ -111,13 +110,12 @@ public sealed partial class DirectExecutionBackend
|
|||||||
LastError = "Import dispatch called without active CPU context";
|
LastError = "Import dispatch called without active CPU context";
|
||||||
return 18446744071562199298uL;
|
return 18446744071562199298uL;
|
||||||
}
|
}
|
||||||
var importEntries = _importEntries;
|
if ((uint)importIndex >= (uint)_importEntries.Length)
|
||||||
if ((uint)importIndex >= (uint)importEntries.Length)
|
|
||||||
{
|
{
|
||||||
LastError = $"Import dispatch index out of range: {importIndex}";
|
LastError = $"Import dispatch index out of range: {importIndex}";
|
||||||
return 18446744071562199042uL;
|
return 18446744071562199042uL;
|
||||||
}
|
}
|
||||||
ImportStubEntry importStubEntry = importEntries[importIndex];
|
ImportStubEntry importStubEntry = _importEntries[importIndex];
|
||||||
int num2 = Volatile.Read(in _rawSentinelRecoveries);
|
int num2 = Volatile.Read(in _rawSentinelRecoveries);
|
||||||
if (num2 != _lastReportedRawSentinelRecoveries)
|
if (num2 != _lastReportedRawSentinelRecoveries)
|
||||||
{
|
{
|
||||||
@@ -161,14 +159,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
*(ulong*)(xmmSlot + 8));
|
*(ulong*)(xmmSlot + 8));
|
||||||
}
|
}
|
||||||
cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
||||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
NormalizeKernelDynlibDlsymArguments(cpuContext, out _, out _);
|
|
||||||
*(ulong*)argPackPtr = cpuContext[CpuRegister.Rdi];
|
|
||||||
*(ulong*)(argPackPtr + 8) = cpuContext[CpuRegister.Rsi];
|
|
||||||
*(ulong*)(argPackPtr + 16) = cpuContext[CpuRegister.Rdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong value = cpuContext[CpuRegister.Rdi];
|
ulong value = cpuContext[CpuRegister.Rdi];
|
||||||
ulong value2 = cpuContext[CpuRegister.Rsi];
|
ulong value2 = cpuContext[CpuRegister.Rsi];
|
||||||
ulong num3 = cpuContext[CpuRegister.Rdx];
|
ulong num3 = cpuContext[CpuRegister.Rdx];
|
||||||
@@ -219,6 +209,16 @@ public sealed partial class DirectExecutionBackend
|
|||||||
TraceGuestContext(
|
TraceGuestContext(
|
||||||
$"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}");
|
$"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}");
|
||||||
}
|
}
|
||||||
|
if (_logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
string symbolText = "<unreadable>";
|
||||||
|
if (TryReadAsciiZ(value2, 256, out var sym))
|
||||||
|
{
|
||||||
|
symbolText = sym;
|
||||||
|
}
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] bootstrap_call#{num}: op=0x{value:X16} sym_ptr=0x{value2:X16} sym='{symbolText}' out_ptr=0x{num3:X16} ret=0x{num7:X16}");
|
||||||
|
}
|
||||||
if (!isGuestWorker &&
|
if (!isGuestWorker &&
|
||||||
!ActiveForcedGuestExit &&
|
!ActiveForcedGuestExit &&
|
||||||
ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) &&
|
ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) &&
|
||||||
@@ -383,11 +383,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
{
|
{
|
||||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
if (_logBootstrap)
|
|
||||||
{
|
|
||||||
RecordDeferredBootstrapTrace(num, value, value2, num3, num7);
|
|
||||||
}
|
|
||||||
|
|
||||||
orbisGen2Result = DispatchBootstrapBridge();
|
orbisGen2Result = DispatchBootstrapBridge();
|
||||||
}
|
}
|
||||||
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) ||
|
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) ||
|
||||||
@@ -550,7 +545,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
cpuContext.GetXmmRegister(0, out var returnXmm0Low, out var returnXmm0High);
|
cpuContext.GetXmmRegister(0, out var returnXmm0Low, out var returnXmm0High);
|
||||||
*(ulong*)(argPackPtr - 0x80) = returnXmm0Low;
|
*(ulong*)(argPackPtr - 0x80) = returnXmm0Low;
|
||||||
*(ulong*)(argPackPtr - 0x80 + 8) = returnXmm0High;
|
*(ulong*)(argPackPtr - 0x80 + 8) = returnXmm0High;
|
||||||
DrainDeferredBootstrapTraces();
|
|
||||||
return cpuContext[CpuRegister.Rax];
|
return cpuContext[CpuRegister.Rax];
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -559,7 +553,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
Console.Error.WriteLine($"[LOADER][ERROR] {LastError}");
|
Console.Error.WriteLine($"[LOADER][ERROR] {LastError}");
|
||||||
Console.Error.WriteLine($"[LOADER][ERROR] {ex.StackTrace}");
|
Console.Error.WriteLine($"[LOADER][ERROR] {ex.StackTrace}");
|
||||||
cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||||
DrainDeferredBootstrapTraces();
|
|
||||||
return 18446744071562199298uL;
|
return 18446744071562199298uL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1318,483 +1311,28 @@ public sealed partial class DirectExecutionBackend
|
|||||||
{
|
{
|
||||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
ulong symbolNameAddress = cpuContext[CpuRegister.Rsi];
|
||||||
NormalizeKernelDynlibDlsymArguments(cpuContext, out var symbolNameAddress, out var outputAddress);
|
ulong outputAddress = cpuContext[CpuRegister.Rdx];
|
||||||
try
|
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName))
|
||||||
{
|
{
|
||||||
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName) ||
|
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||||
!TryResolveDlsymGuestAddress(symbolName, out var resolvedAddress) ||
|
|
||||||
outputAddress == 0 ||
|
|
||||||
!TryWriteUInt64Compat(outputAddress, resolvedAddress))
|
|
||||||
{
|
|
||||||
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuContext[CpuRegister.Rax] = 0uL;
|
|
||||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress) &&
|
||||||
private static void NormalizeKernelDynlibDlsymArguments(
|
!TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress))
|
||||||
CpuContext cpuContext,
|
|
||||||
out ulong symbolNameAddress,
|
|
||||||
out ulong outputAddress)
|
|
||||||
{
|
|
||||||
var handle = cpuContext[CpuRegister.Rdi];
|
|
||||||
symbolNameAddress = cpuContext[CpuRegister.Rsi];
|
|
||||||
outputAddress = cpuContext[CpuRegister.Rdx];
|
|
||||||
|
|
||||||
// Standalone bootstrap loaders sometimes call through the bridge with
|
|
||||||
// (symbol_ptr, handle, out) while sceKernelDlsym is (handle, symbol_ptr, out).
|
|
||||||
// Heuristic only: valid when RSI looks like a small handle and RDI is a guest pointer.
|
|
||||||
if (symbolNameAddress < 0x10000 &&
|
|
||||||
IsPlausibleDynlibSymbolPointer(handle))
|
|
||||||
{
|
|
||||||
symbolNameAddress = handle;
|
|
||||||
handle = cpuContext[CpuRegister.Rsi];
|
|
||||||
cpuContext[CpuRegister.Rdi] = handle;
|
|
||||||
cpuContext[CpuRegister.Rsi] = symbolNameAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPlausibleDynlibSymbolPointer(ulong address)
|
|
||||||
{
|
|
||||||
return address >= 0x10000 && address < 0x0000_8000_0000_0000UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OrbisGen2Result CompleteKernelDynlibDlsymFailure(CpuContext cpuContext, ulong outputAddress)
|
|
||||||
{
|
|
||||||
if (outputAddress != 0)
|
|
||||||
{
|
|
||||||
_ = TryWriteUInt64Compat(outputAddress, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuContext[CpuRegister.Rax] = ulong.MaxValue;
|
|
||||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetLazyDlsymStubState()
|
|
||||||
{
|
|
||||||
lock (_lazyDlsymStubGate)
|
|
||||||
{
|
|
||||||
_lazyDlsymStubCache.Clear();
|
|
||||||
_lazyImportStubPoolMapped = false;
|
|
||||||
_lazyImportStubPoolBase = 0;
|
|
||||||
_lazyImportStubNextSlot = 0;
|
|
||||||
_lazyImportStubPoolLimit = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryResolveDlsymGuestAddress(string symbolName, out ulong guestAddress)
|
|
||||||
{
|
|
||||||
guestAddress = 0;
|
|
||||||
if (string.IsNullOrWhiteSpace(symbolName))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tier 0: ELF runtime symbols and aliases.
|
|
||||||
if (TryResolveRuntimeSymbolAddress(symbolName, out guestAddress) ||
|
|
||||||
TryResolveRuntimeSymbolAlias(symbolName, out guestAddress))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tier 1: existing import stub entries (duplicate prevention).
|
|
||||||
if (TryFindImportStubGuestAddress(symbolName, out guestAddress))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasAerolibSymbol = Aerolib.Instance.TryGetByExportName(symbolName, out var hleSymbol);
|
|
||||||
if (hasAerolibSymbol)
|
|
||||||
{
|
|
||||||
if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryResolveRuntimeSymbolAddress(hleSymbol.Nid, out guestAddress) ||
|
|
||||||
TryResolveRuntimeSymbolAddress(hleSymbol.ExportName, out guestAddress) ||
|
|
||||||
TryFindImportStubGuestAddress(hleSymbol.Nid, out guestAddress) ||
|
|
||||||
TryFindImportStubGuestAddress(hleSymbol.ExportName, out guestAddress))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Aerolib.Instance.TryGetByNid(symbolName, out hleSymbol))
|
|
||||||
{
|
|
||||||
if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryFindImportStubGuestAddress(hleSymbol.Nid, out guestAddress) ||
|
|
||||||
TryFindImportStubGuestAddress(hleSymbol.ExportName, out guestAddress))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasAerolibSymbol = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tier 3: lazy materialization for callable HLE symbols missing from ELF imports.
|
|
||||||
if (!TryResolveLazyDispatchTarget(symbolName, hasAerolibSymbol, in hleSymbol, out var dispatchNid, out var export))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryGetOrCreateLazyImportStub(
|
|
||||||
dispatchNid,
|
|
||||||
symbolName,
|
|
||||||
hasAerolibSymbol ? hleSymbol : null,
|
|
||||||
export,
|
|
||||||
out guestAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryFindImportStubGuestAddress(string identifier, out ulong guestAddress)
|
|
||||||
{
|
|
||||||
guestAddress = 0;
|
|
||||||
if (string.IsNullOrWhiteSpace(identifier))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var importEntries = _importEntries;
|
|
||||||
for (var i = 0; i < importEntries.Length; i++)
|
|
||||||
{
|
|
||||||
var entry = importEntries[i];
|
|
||||||
if (!ImportStubEntryMatchesIdentifier(entry, identifier))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.Address >= 0x10000)
|
|
||||||
{
|
|
||||||
guestAddress = entry.Address;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ImportStubEntryMatchesIdentifier(in ImportStubEntry entry, string identifier)
|
|
||||||
{
|
|
||||||
if (string.Equals(entry.Nid, identifier, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.Export is not { } export)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(export.Name, identifier, StringComparison.Ordinal) ||
|
|
||||||
string.Equals(export.Nid, identifier, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsKernelDynlibDlsymIdentifier(string identifier)
|
|
||||||
{
|
|
||||||
return string.Equals(identifier, "sceKernelDlsym", StringComparison.Ordinal) ||
|
|
||||||
string.Equals(identifier, KernelDynlibDlsymAerolibNid, StringComparison.Ordinal) ||
|
|
||||||
string.Equals(identifier, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryResolveLazyDispatchTarget(
|
|
||||||
string symbolName,
|
|
||||||
bool hasAerolibSymbol,
|
|
||||||
in SysAbiSymbol hleSymbol,
|
|
||||||
out string dispatchNid,
|
|
||||||
out ExportedFunction? export)
|
|
||||||
{
|
|
||||||
export = null;
|
|
||||||
dispatchNid = string.Empty;
|
|
||||||
|
|
||||||
if (IsKernelDynlibDlsymIdentifier(symbolName))
|
|
||||||
{
|
|
||||||
dispatchNid = KernelDynlibDlsymAerolibNid;
|
|
||||||
_ = _moduleManager.TryGetExport(dispatchNid, out export);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasAerolibSymbol)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_moduleManager.TryGetExport(hleSymbol.Nid, out export))
|
|
||||||
{
|
|
||||||
dispatchNid = hleSymbol.Nid;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetOrCreateLazyImportStub(
|
|
||||||
string dispatchNid,
|
|
||||||
string symbolName,
|
|
||||||
SysAbiSymbol? hleSymbol,
|
|
||||||
ExportedFunction? export,
|
|
||||||
out ulong guestAddress)
|
|
||||||
{
|
|
||||||
guestAddress = 0;
|
|
||||||
if (string.IsNullOrWhiteSpace(dispatchNid))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lazyDlsymStubGate)
|
|
||||||
{
|
|
||||||
if (TryGetCachedLazyDlsymAddress(dispatchNid, symbolName, out guestAddress))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EnsureLazyImportStubPoolMapped())
|
|
||||||
{
|
|
||||||
LogLazyImportStubMaterializationFailure(
|
|
||||||
"import stub region unresolved",
|
|
||||||
dispatchNid,
|
|
||||||
symbolName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryAllocateLazyImportStubSlot(out guestAddress))
|
|
||||||
{
|
|
||||||
LogLazyImportStubMaterializationFailure(
|
|
||||||
"lazy import stub pool exhausted",
|
|
||||||
dispatchNid,
|
|
||||||
symbolName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var previousEntries = _importEntries;
|
|
||||||
var importIndex = previousEntries.Length;
|
|
||||||
var newEntries = new ImportStubEntry[importIndex + 1];
|
|
||||||
if (importIndex > 0)
|
|
||||||
{
|
|
||||||
Array.Copy(previousEntries, newEntries, importIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
newEntries[importIndex] = new ImportStubEntry(guestAddress, dispatchNid, export);
|
|
||||||
|
|
||||||
var hostTrampoline = CreateImportHandlerTrampoline(importIndex);
|
|
||||||
if (hostTrampoline == 0 ||
|
|
||||||
!PatchImportStub((nint)(long)guestAddress, hostTrampoline))
|
|
||||||
{
|
|
||||||
guestAddress = 0;
|
|
||||||
LogLazyImportStubMaterializationFailure(
|
|
||||||
"failed to patch lazy import stub trampoline",
|
|
||||||
dispatchNid,
|
|
||||||
symbolName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_importEntries = newEntries;
|
|
||||||
|
|
||||||
RegisterDlsymRuntimeSymbolKeys(symbolName, dispatchNid, hleSymbol, guestAddress);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetCachedLazyDlsymAddress(string dispatchNid, string symbolName, out ulong guestAddress)
|
|
||||||
{
|
|
||||||
if (_lazyDlsymStubCache.TryGetValue(dispatchNid, out guestAddress) ||
|
|
||||||
_lazyDlsymStubCache.TryGetValue(symbolName, out guestAddress))
|
|
||||||
{
|
|
||||||
return guestAddress >= 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterDlsymRuntimeSymbolKeys(
|
|
||||||
string symbolName,
|
|
||||||
string dispatchNid,
|
|
||||||
SysAbiSymbol? hleSymbol,
|
|
||||||
ulong guestAddress)
|
|
||||||
{
|
|
||||||
RegisterDlsymRuntimeSymbolKey(symbolName, guestAddress);
|
|
||||||
RegisterDlsymRuntimeSymbolKey(dispatchNid, guestAddress);
|
|
||||||
|
|
||||||
if (IsKernelDynlibDlsymIdentifier(symbolName) || IsKernelDynlibDlsymIdentifier(dispatchNid))
|
|
||||||
{
|
|
||||||
RegisterDlsymRuntimeSymbolKey(RuntimeStubNids.KernelDynlibDlsym, guestAddress);
|
|
||||||
RegisterDlsymRuntimeSymbolKey(KernelDynlibDlsymAerolibNid, guestAddress);
|
|
||||||
RegisterDlsymRuntimeSymbolKey("sceKernelDlsym", guestAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hleSymbol.HasValue)
|
|
||||||
{
|
|
||||||
RegisterDlsymRuntimeSymbolKey(hleSymbol.Value.Nid, guestAddress);
|
|
||||||
RegisterDlsymRuntimeSymbolKey(hleSymbol.Value.ExportName, guestAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterDlsymRuntimeSymbolKey(string key, ulong guestAddress)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(key) || !IsRuntimeSymbolAddressUsable(guestAddress))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_runtimeSymbolsByName[key] = guestAddress;
|
|
||||||
_lazyDlsymStubCache[key] = guestAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogLazyImportStubMaterializationFailure(string reason, string dispatchNid, string symbolName)
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][WARN] Lazy import stub materialization failed ({reason}): " +
|
$"[LOADER][WARN] sceKernelDlsym failed: handle=0x{cpuContext[CpuRegister.Rdi]:X} symbol='{symbolName}'");
|
||||||
$"nid={dispatchNid} symbol='{symbolName}'");
|
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||||
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
if (outputAddress == 0L || !TryWriteUInt64Compat(outputAddress, resolvedAddress))
|
||||||
private unsafe bool TryResolveImportStubRegionBounds(
|
|
||||||
ImportStubEntry[] importEntries,
|
|
||||||
out ulong regionBase,
|
|
||||||
out ulong regionLimit)
|
|
||||||
{
|
{
|
||||||
regionBase = 0;
|
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||||
regionLimit = 0;
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
|
||||||
for (var candidateIndex = 0; candidateIndex < 64; candidateIndex++)
|
|
||||||
{
|
|
||||||
var candidateBase = ImportStubRegionCanonicalBase -
|
|
||||||
(ulong)candidateIndex * ImportStubRegionAddressStride;
|
|
||||||
if (VirtualQuery((void*)candidateBase, out var memoryInfo, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 ||
|
|
||||||
memoryInfo.RegionSize == 0 ||
|
|
||||||
memoryInfo.State != 4096)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
cpuContext[CpuRegister.Rax] = 0uL;
|
||||||
var candidateLimit = candidateBase + memoryInfo.RegionSize;
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
var hasStub = false;
|
|
||||||
for (var i = 0; i < importEntries.Length; i++)
|
|
||||||
{
|
|
||||||
var entryAddress = importEntries[i].Address;
|
|
||||||
if (entryAddress < candidateBase || entryAddress >= candidateLimit)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((entryAddress - candidateBase) % LazyImportStubSlotSize != 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasStub = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasStub)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
regionBase = candidateBase;
|
|
||||||
regionLimit = candidateLimit;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong maxStubEnd = 0;
|
|
||||||
for (var i = 0; i < importEntries.Length; i++)
|
|
||||||
{
|
|
||||||
var entryAddress = importEntries[i].Address;
|
|
||||||
if (entryAddress < ImportStubRegionCanonicalBase)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((entryAddress - ImportStubRegionCanonicalBase) % LazyImportStubSlotSize != 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryEnd = entryAddress + LazyImportStubSlotSize;
|
|
||||||
if (entryEnd > maxStubEnd)
|
|
||||||
{
|
|
||||||
maxStubEnd = entryEnd;
|
|
||||||
regionBase = ImportStubRegionCanonicalBase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regionBase == 0 || maxStubEnd <= regionBase)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spanBytes = maxStubEnd - regionBase;
|
|
||||||
regionLimit = regionBase + AlignUp(spanBytes, ImportStubRegionPageSize);
|
|
||||||
return regionLimit > regionBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool EnsureLazyImportStubPoolMapped()
|
|
||||||
{
|
|
||||||
if (_lazyImportStubPoolMapped && _lazyImportStubPoolBase != 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var importEntries = _importEntries;
|
|
||||||
if (!TryResolveImportStubRegionBounds(importEntries, out var importStubRegionBase, out var importStubRegionLimit))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong nextSlot = importStubRegionBase;
|
|
||||||
for (var i = 0; i < importEntries.Length; i++)
|
|
||||||
{
|
|
||||||
var entryAddress = importEntries[i].Address;
|
|
||||||
if (entryAddress < importStubRegionBase || entryAddress >= importStubRegionLimit)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryEnd = entryAddress + LazyImportStubSlotSize;
|
|
||||||
if (entryEnd > nextSlot)
|
|
||||||
{
|
|
||||||
nextSlot = entryEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextSlot >= importStubRegionLimit)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lazyImportStubPoolBase = importStubRegionBase;
|
|
||||||
_lazyImportStubNextSlot = nextSlot;
|
|
||||||
_lazyImportStubPoolLimit = importStubRegionLimit;
|
|
||||||
_lazyImportStubPoolMapped = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryAllocateLazyImportStubSlot(out ulong guestAddress)
|
|
||||||
{
|
|
||||||
guestAddress = 0;
|
|
||||||
if (!_lazyImportStubPoolMapped ||
|
|
||||||
_lazyImportStubNextSlot < _lazyImportStubPoolBase ||
|
|
||||||
_lazyImportStubNextSlot + LazyImportStubSlotSize > _lazyImportStubPoolLimit)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
guestAddress = _lazyImportStubNextSlot;
|
|
||||||
_lazyImportStubNextSlot += LazyImportStubSlotSize;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveRuntimeSymbolAlias(string symbolName, out ulong address)
|
private bool TryResolveRuntimeSymbolAlias(string symbolName, out ulong address)
|
||||||
@@ -1861,20 +1399,29 @@ public sealed partial class DirectExecutionBackend
|
|||||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ulong bridgeHandle = cpuContext[CpuRegister.Rdi];
|
||||||
|
ulong symbolNameAddress = cpuContext[CpuRegister.Rsi];
|
||||||
ulong outputAddress = cpuContext[CpuRegister.Rdx];
|
ulong outputAddress = cpuContext[CpuRegister.Rdx];
|
||||||
try
|
_ = TryReadAsciiZ(symbolNameAddress, 512, out var symbolName);
|
||||||
{
|
|
||||||
OrbisGen2Result result = DispatchKernelDynlibDlsym();
|
OrbisGen2Result result = DispatchKernelDynlibDlsym();
|
||||||
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
if (_logBootstrap)
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] bootstrap_dispatch: handle=0x{bridgeHandle:X16} symbol='{symbolName}' out=0x{outputAddress:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cpuContext[CpuRegister.Rax] == 0uL)
|
||||||
|
{
|
||||||
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][WARN] bootstrap_bridge unresolved: handle=0x{bridgeHandle:X} symbol='{symbolName}' out=0x{outputAddress:X16}");
|
||||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1915,7 +1462,6 @@ public sealed partial class DirectExecutionBackend
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<byte> list = new List<byte>(Math.Min(maxLength, 256));
|
List<byte> list = new List<byte>(Math.Min(maxLength, 256));
|
||||||
Span<byte> destination = stackalloc byte[1];
|
Span<byte> destination = stackalloc byte[1];
|
||||||
for (int i = 0; i < maxLength; i++)
|
for (int i = 0; i < maxLength; i++)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -49,13 +48,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
ulong Arg1,
|
ulong Arg1,
|
||||||
ulong Arg2);
|
ulong Arg2);
|
||||||
|
|
||||||
private readonly record struct DeferredBootstrapTraceEntry(
|
|
||||||
long DispatchIndex,
|
|
||||||
ulong Op,
|
|
||||||
ulong SymbolPointer,
|
|
||||||
ulong OutputPointer,
|
|
||||||
ulong ReturnRip);
|
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
private struct EXCEPTION_POINTERS
|
private struct EXCEPTION_POINTERS
|
||||||
{
|
{
|
||||||
@@ -240,31 +232,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private KeyValuePair<string, ulong>[] _runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>();
|
private KeyValuePair<string, ulong>[] _runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, ulong> _runtimeSymbolsByName =
|
private readonly Dictionary<string, ulong> _runtimeSymbolsByName = new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||||
new(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
// Keep in sync with SelfLoader import-stub mapping constants.
|
|
||||||
private const ulong ImportStubRegionCanonicalBase = 0x0000_7000_0000_0000UL;
|
|
||||||
|
|
||||||
private const ulong ImportStubRegionAddressStride = 0x0000_0000_0100_0000UL;
|
|
||||||
|
|
||||||
private const ulong LazyImportStubSlotSize = 0x10;
|
|
||||||
|
|
||||||
private const ulong ImportStubRegionPageSize = 0x1000UL;
|
|
||||||
|
|
||||||
private const string KernelDynlibDlsymAerolibNid = "LwG8g3niqwA";
|
|
||||||
|
|
||||||
private readonly object _lazyDlsymStubGate = new();
|
|
||||||
|
|
||||||
private readonly Dictionary<string, ulong> _lazyDlsymStubCache = new(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
private ulong _lazyImportStubPoolBase;
|
|
||||||
|
|
||||||
private ulong _lazyImportStubNextSlot;
|
|
||||||
|
|
||||||
private ulong _lazyImportStubPoolLimit;
|
|
||||||
|
|
||||||
private bool _lazyImportStubPoolMapped;
|
|
||||||
|
|
||||||
private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64];
|
private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64];
|
||||||
|
|
||||||
@@ -272,14 +240,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private int _recentImportTraceWriteIndex;
|
private int _recentImportTraceWriteIndex;
|
||||||
|
|
||||||
private readonly DeferredBootstrapTraceEntry[] _deferredBootstrapTrace = new DeferredBootstrapTraceEntry[32];
|
|
||||||
|
|
||||||
private int _deferredBootstrapTraceCount;
|
|
||||||
|
|
||||||
private int _deferredBootstrapTraceWriteIndex;
|
|
||||||
|
|
||||||
private readonly object _deferredBootstrapTraceGate = new();
|
|
||||||
|
|
||||||
private readonly string[] _distinctImportNidHistory = new string[128];
|
private readonly string[] _distinctImportNidHistory = new string[128];
|
||||||
|
|
||||||
private int _distinctImportNidHistoryCount;
|
private int _distinctImportNidHistoryCount;
|
||||||
@@ -895,14 +855,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
result = OrbisGen2Result.ORBIS_GEN2_OK;
|
result = OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
LastError = null;
|
LastError = null;
|
||||||
InitializeRuntimeSymbolIndex(runtimeSymbols);
|
InitializeRuntimeSymbolIndex(runtimeSymbols);
|
||||||
ResetLazyDlsymStubState();
|
|
||||||
_recentImportTraceCount = 0;
|
_recentImportTraceCount = 0;
|
||||||
_recentImportTraceWriteIndex = 0;
|
_recentImportTraceWriteIndex = 0;
|
||||||
lock (_deferredBootstrapTraceGate)
|
|
||||||
{
|
|
||||||
_deferredBootstrapTraceCount = 0;
|
|
||||||
_deferredBootstrapTraceWriteIndex = 0;
|
|
||||||
}
|
|
||||||
_distinctImportNidHistoryCount = 0;
|
_distinctImportNidHistoryCount = 0;
|
||||||
_distinctImportNidHistoryWriteIndex = 0;
|
_distinctImportNidHistoryWriteIndex = 0;
|
||||||
_lastDistinctImportNid = string.Empty;
|
_lastDistinctImportNid = string.Empty;
|
||||||
@@ -976,7 +930,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DrainDeferredBootstrapTraces();
|
|
||||||
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
||||||
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
||||||
}
|
}
|
||||||
@@ -4503,14 +4456,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
ulong rsp = cpuContext[CpuRegister.Rsp];
|
ulong rsp = cpuContext[CpuRegister.Rsp];
|
||||||
Console.Error.WriteLine($"[LOADER][ERROR] Stall snapshot: rip=0x{cpuContext.Rip:X16} rsp=0x{rsp:X16} rbp=0x{cpuContext[CpuRegister.Rbp]:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16} rbx=0x{cpuContext[CpuRegister.Rbx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16}");
|
Console.Error.WriteLine($"[LOADER][ERROR] Stall snapshot: rip=0x{cpuContext.Rip:X16} rsp=0x{rsp:X16} rbp=0x{cpuContext[CpuRegister.Rbp]:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16} rbx=0x{cpuContext[CpuRegister.Rbx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16}");
|
||||||
ulong num = cpuContext.Rip & 0xFFFFFFFFFFFFFFF0uL;
|
ulong num = cpuContext.Rip & 0xFFFFFFFFFFFFFFF0uL;
|
||||||
var importEntries = _importEntries;
|
for (int i = 0; i < _importEntries.Length; i++)
|
||||||
for (int i = 0; i < importEntries.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (importEntries[i].Address != num)
|
if (_importEntries[i].Address != num)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string text = importEntries[i].Nid;
|
string text = _importEntries[i].Nid;
|
||||||
if (_moduleManager.TryGetExport(text, out ExportedFunction export))
|
if (_moduleManager.TryGetExport(text, out ExportedFunction export))
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][ERROR] Stall import-stub: rip=0x{num:X16} nid={text} -> {export.LibraryName}:{export.Name}");
|
Console.Error.WriteLine($"[LOADER][ERROR] Stall import-stub: rip=0x{num:X16} nid={text} -> {export.LibraryName}:{export.Name}");
|
||||||
@@ -4701,7 +4653,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
ClearImportHandlerTrampolines();
|
ClearImportHandlerTrampolines();
|
||||||
_importEntries = Array.Empty<ImportStubEntry>();
|
_importEntries = Array.Empty<ImportStubEntry>();
|
||||||
_runtimeSymbolsByName.Clear();
|
_runtimeSymbolsByName.Clear();
|
||||||
ResetLazyDlsymStubState();
|
|
||||||
_importNidHashCache.Clear();
|
_importNidHashCache.Clear();
|
||||||
StopStallWatchdog();
|
StopStallWatchdog();
|
||||||
if (_exceptionHandler != 0)
|
if (_exceptionHandler != 0)
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ 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,7 +168,8 @@ 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));
|
||||||
@@ -199,10 +200,29 @@ 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}' with CET/CFG mitigation disabled (Win32 error {error}: {new Win32Exception(error).Message}).");
|
throw new Win32Exception(error, $"Failed to start '{exePath}' (Win32 error {error}: {new Win32Exception(error).Message}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(processInfo.hThread);
|
CloseHandle(processInfo.hThread);
|
||||||
|
|||||||
@@ -1061,11 +1061,13 @@ 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;
|
||||||
|
|
||||||
// Let the CLI mirror stdout/stderr itself; it sees loader/native
|
// Mirror everything the console pane shows into a log file for the
|
||||||
// diagnostics before the GUI pipe reader can filter or batch them.
|
// duration of the run, regardless of the emulator's log level.
|
||||||
DropFileLog();
|
DropFileLog();
|
||||||
if (_settings.LogToFile)
|
if (_settings.LogToFile)
|
||||||
{
|
{
|
||||||
@@ -1103,13 +1105,17 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(filePath))
|
if (!string.IsNullOrEmpty(filePath))
|
||||||
{
|
{
|
||||||
arguments.Add("--log-file");
|
try
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var src0 = word & 0x1FF;
|
var src0 = word & 0x1FF;
|
||||||
sizeDwords = opcode is 0x20 or 0x21 or 0x2C or 0x2D || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
|
sizeDwords = opcode is 0x20 or 0x21 || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
name = opcode switch
|
name = opcode switch
|
||||||
{
|
{
|
||||||
@@ -757,9 +757,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x28 => "VAddcU32",
|
0x28 => "VAddcU32",
|
||||||
0x29 => "VSubbU32",
|
0x29 => "VSubbU32",
|
||||||
0x2A => "VSubbrevU32",
|
0x2A => "VSubbrevU32",
|
||||||
0x2B => "VFmacF32",
|
0x2B => "VLdexpF32",
|
||||||
0x2C => "VFmamkF32",
|
|
||||||
0x2D => "VFmaakF32",
|
|
||||||
0x2F => "VCvtPkrtzF16F32",
|
0x2F => "VCvtPkrtzF16F32",
|
||||||
0x30 => "VCvtPkU16U32",
|
0x30 => "VCvtPkU16U32",
|
||||||
0x31 => "VCvtPkI16I32",
|
0x31 => "VCvtPkI16I32",
|
||||||
@@ -872,7 +870,6 @@ 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",
|
||||||
@@ -1382,7 +1379,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 is "VMadMkF32" or "VFmamkF32" && literal.HasValue)
|
if (opcode == "VMadMkF32" && literal.HasValue)
|
||||||
{
|
{
|
||||||
sources =
|
sources =
|
||||||
[
|
[
|
||||||
@@ -1391,7 +1388,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
sources[1],
|
sources[1],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else if (opcode is "VMadAkF32" or "VFmaakF32" && literal.HasValue)
|
else if (opcode == "VMadAkF32" && literal.HasValue)
|
||||||
{
|
{
|
||||||
sources =
|
sources =
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -255,8 +255,6 @@ 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(
|
||||||
@@ -267,7 +265,6 @@ 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(
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ 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;
|
||||||
@@ -1503,13 +1502,6 @@ 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;
|
||||||
@@ -1529,62 +1521,15 @@ 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;
|
||||||
found = true;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
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