Fix Massive Bug Preventing UE5 Titles From Booting (#406)

* Fix cross platform memcpy bug
This commit is contained in:
Spooks
2026-07-18 12:50:59 -06:00
committed by GitHub
parent 94153955b0
commit daaeb6213e
4 changed files with 173 additions and 6 deletions
+3 -3
View File
@@ -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;
}
}