mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-23 03:16:22 +08:00
Fix Massive Bug Preventing UE5 Titles From Booting (#406)
* Fix cross platform memcpy bug
This commit is contained in:
@@ -4,9 +4,9 @@
|
|||||||
"net10.0": {
|
"net10.0": {
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.3, )",
|
"requested": "[10.0.8, )",
|
||||||
"resolved": "10.0.3",
|
"resolved": "10.0.8",
|
||||||
"contentHash": "0B6nZyCHWXnvmlB559oduOspVdNOnpNXPjhpWVMovLPAsDVG7A4jJR9rzECf67JUzxP8/ee/wA8clwIzJcWNFA=="
|
"contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw=="
|
||||||
},
|
},
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
"Avalonia.Angle.Windows.Natives": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
|||||||
@@ -1291,6 +1291,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private unsafe bool TryCreateNativeImportIntrinsic(string nid, out nint address)
|
private unsafe bool TryCreateNativeImportIntrinsic(string nid, out nint address)
|
||||||
{
|
{
|
||||||
|
if (IsHlePreferredNid(nid))
|
||||||
|
{
|
||||||
|
address = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (nid == "1jfXLRVzisc" &&
|
if (nid == "1jfXLRVzisc" &&
|
||||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal))
|
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -1466,8 +1472,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
"Q3VBxCXhUHs" =>
|
"Q3VBxCXhUHs" =>
|
||||||
[
|
[
|
||||||
0x48, 0x89, 0xF8,
|
0x48, 0x89, 0xF8,
|
||||||
0x48, 0x89, 0xD1,
|
0x48, 0x85, 0xD2,
|
||||||
0xF3, 0xA4,
|
0x74, 0x11,
|
||||||
|
0x44, 0x8A, 0x06,
|
||||||
|
0x44, 0x88, 0x07,
|
||||||
|
0x48, 0xFF, 0xC6,
|
||||||
|
0x48, 0xFF, 0xC7,
|
||||||
|
0x48, 0xFF, 0xCA,
|
||||||
|
0x75, 0xEF,
|
||||||
0xC3,
|
0xC3,
|
||||||
],
|
],
|
||||||
"8zTFvBIAIN8" =>
|
"8zTFvBIAIN8" =>
|
||||||
@@ -1601,7 +1613,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private static bool IsHlePreferredNid(string nid)
|
private static bool IsHlePreferredNid(string nid)
|
||||||
{
|
{
|
||||||
return string.Equals(nid, "QrZZdJ8XsX0", StringComparison.Ordinal);
|
return string.Equals(nid, "QrZZdJ8XsX0", StringComparison.Ordinal) ||
|
||||||
|
string.Equals(nid, "Q3VBxCXhUHs", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsLibcLibrary(string libraryName)
|
private static bool IsLibcLibrary(string libraryName)
|
||||||
|
|||||||
@@ -1111,6 +1111,7 @@ public static partial class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx[CpuRegister.Rax] = destination;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.Core.Cpu.Native;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.Cpu;
|
||||||
|
|
||||||
|
public sealed class MemcpyHleRoutingTests
|
||||||
|
{
|
||||||
|
private const string MemcpyNid = "Q3VBxCXhUHs";
|
||||||
|
private const string MemsetNid = "QrZZdJ8XsX0";
|
||||||
|
private const string RdtscNid = "-2IRUCO--PM";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsHlePreferredNid_PrefersHleForMemcpy_OnEveryPlatform()
|
||||||
|
{
|
||||||
|
Assert.True(
|
||||||
|
InvokeIsHlePreferredNid(MemcpyNid),
|
||||||
|
$"memcpy ({MemcpyNid}) must route through HLE on every platform. It was previously " +
|
||||||
|
"gated behind OperatingSystem.IsWindows(), which left Linux and macOS on the LLE " +
|
||||||
|
"intrinsic stub and faulted in guest code. Do not reintroduce an OS condition here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsHlePreferredNid_PrefersHleForMemset()
|
||||||
|
{
|
||||||
|
Assert.True(
|
||||||
|
InvokeIsHlePreferredNid(MemsetNid),
|
||||||
|
$"memset ({MemsetNid}) must route through HLE on every platform.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryCreateNativeImportIntrinsic_DoesNotClaimMemcpy()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimed = InvokeTryCreateNativeImportIntrinsic(MemcpyNid, out var address);
|
||||||
|
|
||||||
|
Assert.False(
|
||||||
|
claimed,
|
||||||
|
$"memcpy ({MemcpyNid}) must fall through to the HLE trampoline. SetupImportStubs tries " +
|
||||||
|
"the intrinsic stub before the trampoline, so without an IsHlePreferredNid guard here " +
|
||||||
|
"the intrinsic claims memcpy and the HLE routing never takes effect.");
|
||||||
|
Assert.Equal(0, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryCreateNativeImportIntrinsic_StillClaimsNonHleNids()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimed = InvokeTryCreateNativeImportIntrinsic(RdtscNid, out var address);
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
claimed,
|
||||||
|
$"rdtsc ({RdtscNid}) has no HLE handler and must still receive an intrinsic stub. If " +
|
||||||
|
"this fails the memcpy assertions above may be passing vacuously.");
|
||||||
|
Assert.NotEqual(0, address);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
Assert.True(HostMemory.Free((void*)address, 0, HostMemory.MEM_RELEASE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsHlePreferredNid_DoesNotBranchOnHostOperatingSystem()
|
||||||
|
{
|
||||||
|
var method = typeof(DirectExecutionBackend).GetMethod(
|
||||||
|
"IsHlePreferredNid",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
var callees = ResolveCallees(method);
|
||||||
|
|
||||||
|
Assert.DoesNotContain(callees, static name =>
|
||||||
|
name is "IsWindows" or "IsLinux" or "IsMacOS" or "IsOSPlatform" or "IsFreeBSD");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string> ResolveCallees(MethodBase method)
|
||||||
|
{
|
||||||
|
var il = method.GetMethodBody()?.GetILAsByteArray();
|
||||||
|
Assert.NotNull(il);
|
||||||
|
|
||||||
|
var module = method.Module;
|
||||||
|
var generic = method.DeclaringType?.GetGenericArguments();
|
||||||
|
var callees = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
for (var i = 0; i + 4 < il.Length; i++)
|
||||||
|
{
|
||||||
|
if (il[i] is not (0x28 or 0x6F))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = BitConverter.ToInt32(il, i + 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var callee = module.ResolveMethod(token, generic, null);
|
||||||
|
if (callee?.Name is { } name)
|
||||||
|
{
|
||||||
|
callees.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InvokeIsHlePreferredNid(string nid)
|
||||||
|
{
|
||||||
|
var method = typeof(DirectExecutionBackend).GetMethod(
|
||||||
|
"IsHlePreferredNid",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
Assert.NotNull(method);
|
||||||
|
return (bool)method.Invoke(null, [nid])!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InvokeTryCreateNativeImportIntrinsic(string nid, out nint address)
|
||||||
|
{
|
||||||
|
var backend = (DirectExecutionBackend)RuntimeHelpers.GetUninitializedObject(
|
||||||
|
typeof(DirectExecutionBackend));
|
||||||
|
var trampolineList = typeof(DirectExecutionBackend).GetField(
|
||||||
|
"_importHandlerTrampolines",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.NotNull(trampolineList);
|
||||||
|
trampolineList.SetValue(backend, new List<nint>());
|
||||||
|
|
||||||
|
var method = typeof(DirectExecutionBackend).GetMethod(
|
||||||
|
"TryCreateNativeImportIntrinsic",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object?[] args = [nid, null];
|
||||||
|
var claimed = (bool)method.Invoke(backend, args)!;
|
||||||
|
address = (nint)args[1]!;
|
||||||
|
return claimed;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user