mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 04:39:17 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dad85b87c3 | |||
| 464422f23e | |||
| 5afa5e1ffa | |||
| 5512da9e12 | |||
| 2a52ccfe6a | |||
| 08b315b5fc | |||
| 5d204afd10 |
@@ -9,5 +9,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Iced" Version="1.21.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -8,6 +8,62 @@
|
||||
"resolved": "10.0.3",
|
||||
"contentHash": "0B6nZyCHWXnvmlB559oduOspVdNOnpNXPjhpWVMovLPAsDVG7A4jJR9rzECf67JUzxP8/ee/wA8clwIzJcWNFA=="
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
@@ -23,7 +79,10 @@
|
||||
"sharpemu.libs": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.0, )"
|
||||
"SharpEmu.HLE": "[1.0.0, )",
|
||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
||||
"Silk.NET.Windowing": "[2.23.0, )"
|
||||
}
|
||||
},
|
||||
"sharpemu.logging": {
|
||||
@@ -34,10 +93,57 @@
|
||||
"requested": "[1.21.0, )",
|
||||
"resolved": "1.21.0",
|
||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
||||
},
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net10.0/linux-x64": {},
|
||||
"net10.0/osx-arm64": {},
|
||||
"net10.0/win-x64": {}
|
||||
"net10.0/linux-x64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
},
|
||||
"net10.0/osx-arm64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
},
|
||||
"net10.0/win-x64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
private const ulong StackSize = 0x0020_0000UL;
|
||||
private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL;
|
||||
private const ulong TlsSize = 0x0001_0000UL;
|
||||
private const ulong TlsPrefixSize = 0x0000_1000UL;
|
||||
private const ulong BootstrapStubBaseAddress = 0x7FFD_F000_0000UL;
|
||||
private const ulong BootstrapPayloadBaseAddress = 0x7FFD_E000_0000UL;
|
||||
private const ulong DynlibFallbackStubBaseAddress = 0x7FFD_D000_0000UL;
|
||||
@@ -342,11 +343,12 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var candidateBase = TlsBaseAddress - ((ulong)i * tlsStride);
|
||||
var mappedBase = candidateBase - TlsPrefixSize;
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
TlsSize,
|
||||
mappedBase,
|
||||
TlsSize + TlsPrefixSize,
|
||||
fileOffset: 0,
|
||||
fileData: ReadOnlySpan<byte>.Empty,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
|
||||
@@ -363,9 +365,11 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
|
||||
private static bool InitializeTls(CpuContext context, ulong tlsBase)
|
||||
{
|
||||
return context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||
return context.TryWriteUInt64(tlsBase - 0xF0, 0) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x10, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x60, tlsBase);
|
||||
}
|
||||
|
||||
private static bool InitializeGuestFrameChainSentinel(CpuContext context)
|
||||
@@ -420,6 +424,13 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
var entryStackPointer = entryParamsAddress - sizeof(ulong);
|
||||
if (!context.TryWriteUInt64(entryStackPointer, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context[CpuRegister.Rsp] = entryStackPointer;
|
||||
context[CpuRegister.Rdi] = entryParamsAddress;
|
||||
context[CpuRegister.Rsi] = programExitHandlerAddress;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
|
||||
@@ -97,12 +97,13 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private void ProbeReturnRip(ulong returnRip, long dispatchIndex)
|
||||
{
|
||||
if (_cpuContext == null || returnRip == 0)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null || returnRip == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Span<byte> destination = stackalloc byte[128];
|
||||
if (!_cpuContext.Memory.TryRead(returnRip, destination))
|
||||
if (!cpuContext.Memory.TryRead(returnRip, destination))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip probe: unreadable @0x{returnRip:X16}");
|
||||
return;
|
||||
@@ -113,7 +114,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
int num = BitConverter.ToInt32(destination.Slice(2, 4));
|
||||
ulong num2 = returnRip + 6 + (ulong)num;
|
||||
if (_cpuContext.TryReadUInt64(num2, out var value2))
|
||||
if (cpuContext.TryReadUInt64(num2, out var value2))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip slot: [0x{num2:X16}] = 0x{value2:X16}");
|
||||
}
|
||||
@@ -122,7 +123,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
int num3 = BitConverter.ToInt32(destination.Slice(3, 4));
|
||||
ulong num4 = returnRip + 7 + (ulong)num3;
|
||||
if (_cpuContext.TryReadUInt64(num4, out var value3))
|
||||
if (cpuContext.TryReadUInt64(num4, out var value3))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip mov-slot: [0x{num4:X16}] = 0x{value3:X16}");
|
||||
}
|
||||
@@ -134,12 +135,82 @@ public sealed partial class DirectExecutionBackend
|
||||
int num5 = BitConverter.ToInt32(destination.Slice(i + 2, 4));
|
||||
ulong num6 = returnRip + (ulong)i;
|
||||
ulong num7 = num6 + 6 + (ulong)num5;
|
||||
if (_cpuContext.TryReadUInt64(num7, out var value4))
|
||||
if (cpuContext.TryReadUInt64(num7, out var value4))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} near-indirect @{num6:X16}: slot=0x{num7:X16} val=0x{value4:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Span<byte> targetBytes = stackalloc byte[32];
|
||||
for (int i = 0; i + 5 <= destination.Length; i++)
|
||||
{
|
||||
if (destination[i] != 0xE8)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int rel32 = BitConverter.ToInt32(destination.Slice(i + 1, 4));
|
||||
ulong callRip = returnRip + (ulong)i;
|
||||
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (_importEntries[importIndex].Address != target)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call import: index={importIndex} nid={nid}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpuContext.Memory.TryRead(target, targetBytes))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call target bytes @0x{target:X16}: " +
|
||||
BitConverter.ToString(targetBytes.ToArray()).Replace("-", " "));
|
||||
if (targetBytes[0] == 0xFF && targetBytes[1] == 0x25)
|
||||
{
|
||||
int slotRel32 = BitConverter.ToInt32(targetBytes.Slice(2, 4));
|
||||
ulong slot = unchecked((ulong)((long)(target + 6) + slotRel32));
|
||||
if (cpuContext.TryReadUInt64(slot, out var slotTarget))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (_importEntries[importIndex].Address != slotTarget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT import: index={importIndex} nid={nid}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUnresolvedSentinel(ulong value)
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu.Disasm;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
@@ -61,7 +62,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
if (_vectoredHandlerDepth > 0)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][TRACE] Nested VEH exception; passing through.");
|
||||
LogNestedVectoredException(exceptionInfo);
|
||||
Console.Error.Flush();
|
||||
return 0;
|
||||
}
|
||||
@@ -84,10 +85,18 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong rip = ReadCtxU64(contextRecord, 248);
|
||||
ulong rsp = ReadCtxU64(contextRecord, 152);
|
||||
|
||||
if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord))
|
||||
if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (IsBenignHostDebugException(exceptionCode))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (exceptionCode == MSVC_CPP_EXCEPTION)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (exceptionCode)
|
||||
{
|
||||
@@ -278,6 +287,37 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsBenignHostDebugException(uint exceptionCode)
|
||||
{
|
||||
return exceptionCode is DBG_PRINTEXCEPTION_C or DBG_PRINTEXCEPTION_WIDE_C or MS_VC_THREADNAME_EXCEPTION;
|
||||
}
|
||||
|
||||
private unsafe static void LogNestedVectoredException(void* exceptionInfo)
|
||||
{
|
||||
int count = Interlocked.Increment(ref _nestedVehTraceCount);
|
||||
if (count > 16 && count % 128 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EXCEPTION_POINTERS* pointers = (EXCEPTION_POINTERS*)exceptionInfo;
|
||||
EXCEPTION_RECORD* record = pointers->ExceptionRecord;
|
||||
void* contextRecord = pointers->ContextRecord;
|
||||
ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, 248) : 0;
|
||||
ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, 152) : 0;
|
||||
ulong accessType = record->NumberParameters >= 1 ? *record->ExceptionInformation : 0;
|
||||
ulong target = record->NumberParameters >= 2 ? record->ExceptionInformation[1] : 0;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Nested VEH exception#{count}: code=0x{record->ExceptionCode:X8} ex=0x{(ulong)record->ExceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} type={accessType} target=0x{target:X16}; passing through.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Nested VEH exception#{count}; passing through.");
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void LogAccessViolationTrace(ulong exceptionAddress, EXCEPTION_RECORD* exceptionRecord)
|
||||
{
|
||||
ulong accessType = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
|
||||
@@ -828,7 +868,7 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe static bool TryHandleLazyCommittedPage(EXCEPTION_RECORD* exceptionRecord)
|
||||
private unsafe bool TryHandleLazyCommittedPage(EXCEPTION_RECORD* exceptionRecord, ulong rip, ulong rsp)
|
||||
{
|
||||
if (exceptionRecord->NumberParameters < 2)
|
||||
{
|
||||
@@ -845,6 +885,10 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!IsGuestOwnedLazyCommitAddress(faultAddress, out var owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (VirtualQuery((void*)faultAddress, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
@@ -852,7 +896,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL;
|
||||
uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-query: fault=0x{faultAddress:X16} state=0x{mbi.State:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.AllocationProtect:X08} prot=0x{mbi.Protect:X08}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-query: fault=0x{faultAddress:X16} owner={owner} rip=0x{rip:X16} rsp=0x{rsp:X16} state=0x{mbi.State:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.AllocationProtect:X08} prot=0x{mbi.Protect:X08}");
|
||||
|
||||
bool committed = false;
|
||||
ulong committedBase = 0;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
@@ -74,7 +75,8 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
long num = Interlocked.Increment(ref _importDispatchCount);
|
||||
MarkExecutionProgress();
|
||||
if (_cpuContext == null)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null)
|
||||
{
|
||||
LastError = "Import dispatch called without active CPU context";
|
||||
return 18446744071562199298uL;
|
||||
@@ -91,32 +93,32 @@ public sealed partial class DirectExecutionBackend
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Raw sentinel recoveries: {num2} (last import index={importIndex})");
|
||||
_lastReportedRawSentinelRecoveries = num2;
|
||||
}
|
||||
_cpuContext.Rip = importStubEntry.Address;
|
||||
_cpuContext[CpuRegister.Rdi] = *(ulong*)argPackPtr;
|
||||
_cpuContext[CpuRegister.Rsi] = *(ulong*)(argPackPtr + 8);
|
||||
_cpuContext[CpuRegister.Rdx] = *(ulong*)(argPackPtr + 16);
|
||||
_cpuContext[CpuRegister.Rcx] = *(ulong*)(argPackPtr + 24);
|
||||
_cpuContext[CpuRegister.R8] = *(ulong*)(argPackPtr + 32);
|
||||
_cpuContext[CpuRegister.R9] = *(ulong*)(argPackPtr + 40);
|
||||
_cpuContext[CpuRegister.Rbx] = *(ulong*)(argPackPtr + 48);
|
||||
_cpuContext[CpuRegister.Rbp] = *(ulong*)(argPackPtr + 56);
|
||||
_cpuContext[CpuRegister.R12] = *(ulong*)(argPackPtr + 64);
|
||||
_cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72);
|
||||
_cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80);
|
||||
_cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88);
|
||||
_cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
||||
ulong value = _cpuContext[CpuRegister.Rdi];
|
||||
ulong value2 = _cpuContext[CpuRegister.Rsi];
|
||||
ulong num3 = _cpuContext[CpuRegister.Rdx];
|
||||
ulong num4 = _cpuContext[CpuRegister.Rcx];
|
||||
ulong num5 = _cpuContext[CpuRegister.R8];
|
||||
ulong num6 = _cpuContext[CpuRegister.R9];
|
||||
ulong value3 = _cpuContext[CpuRegister.Rbx];
|
||||
ulong value4 = _cpuContext[CpuRegister.Rbp];
|
||||
ulong value5 = _cpuContext[CpuRegister.R12];
|
||||
ulong value6 = _cpuContext[CpuRegister.R13];
|
||||
ulong value7 = _cpuContext[CpuRegister.R14];
|
||||
ulong value8 = _cpuContext[CpuRegister.R15];
|
||||
cpuContext.Rip = importStubEntry.Address;
|
||||
cpuContext[CpuRegister.Rdi] = *(ulong*)argPackPtr;
|
||||
cpuContext[CpuRegister.Rsi] = *(ulong*)(argPackPtr + 8);
|
||||
cpuContext[CpuRegister.Rdx] = *(ulong*)(argPackPtr + 16);
|
||||
cpuContext[CpuRegister.Rcx] = *(ulong*)(argPackPtr + 24);
|
||||
cpuContext[CpuRegister.R8] = *(ulong*)(argPackPtr + 32);
|
||||
cpuContext[CpuRegister.R9] = *(ulong*)(argPackPtr + 40);
|
||||
cpuContext[CpuRegister.Rbx] = *(ulong*)(argPackPtr + 48);
|
||||
cpuContext[CpuRegister.Rbp] = *(ulong*)(argPackPtr + 56);
|
||||
cpuContext[CpuRegister.R12] = *(ulong*)(argPackPtr + 64);
|
||||
cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72);
|
||||
cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80);
|
||||
cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88);
|
||||
cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
||||
ulong value = cpuContext[CpuRegister.Rdi];
|
||||
ulong value2 = cpuContext[CpuRegister.Rsi];
|
||||
ulong num3 = cpuContext[CpuRegister.Rdx];
|
||||
ulong num4 = cpuContext[CpuRegister.Rcx];
|
||||
ulong num5 = cpuContext[CpuRegister.R8];
|
||||
ulong num6 = cpuContext[CpuRegister.R9];
|
||||
ulong value3 = cpuContext[CpuRegister.Rbx];
|
||||
ulong value4 = cpuContext[CpuRegister.Rbp];
|
||||
ulong value5 = cpuContext[CpuRegister.R12];
|
||||
ulong value6 = cpuContext[CpuRegister.R13];
|
||||
ulong value7 = cpuContext[CpuRegister.R14];
|
||||
ulong value8 = cpuContext[CpuRegister.R15];
|
||||
ulong num7 = *(ulong*)(argPackPtr + 96);
|
||||
if (!IsLikelyReturnAddress(num7))
|
||||
{
|
||||
@@ -133,6 +135,13 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
TrackDistinctImportNid(importStubEntry.Nid);
|
||||
var probeImportReturn = Environment.GetEnvironmentVariable("SHARPEMU_PROBE_IMPORT_RET");
|
||||
if (!string.IsNullOrWhiteSpace(probeImportReturn) &&
|
||||
(string.Equals(probeImportReturn, "*", StringComparison.Ordinal) ||
|
||||
string.Equals(probeImportReturn, importStubEntry.Nid, StringComparison.Ordinal)))
|
||||
{
|
||||
ProbeReturnRip(num7, num);
|
||||
}
|
||||
TrackStrlenPrelude(importStubEntry.Nid, num, num7);
|
||||
bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
|
||||
if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
@@ -145,9 +154,9 @@ public sealed partial class DirectExecutionBackend
|
||||
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 (!_forcedGuestExit && ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && TryForceGuestExitToHostStub(argPackPtr, num, num7, importStubEntry.Nid))
|
||||
if (!ActiveForcedGuestExit && ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && TryForceGuestExitToHostStub(argPackPtr, num, num7, importStubEntry.Nid))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 1uL;
|
||||
cpuContext[CpuRegister.Rax] = 1uL;
|
||||
return 1uL;
|
||||
}
|
||||
bool flag0 = ShouldSuppressStrlenTrace(importStubEntry.Nid);
|
||||
@@ -209,33 +218,41 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
if (!flag0)
|
||||
{
|
||||
RecordRecentImportTrace($"#{num} nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16}");
|
||||
RecordRecentImportTrace($"#{num} nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "8zTFvBIAIN8" && num <= 256)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] memset#{num}: dst=0x{_cpuContext[CpuRegister.Rdi]:X16} val=0x{_cpuContext[CpuRegister.Rsi] & 0xFF:X2} len=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] memset#{num}: dst=0x{cpuContext[CpuRegister.Rdi]:X16} val=0x{cpuContext[CpuRegister.Rsi] & 0xFF:X2} len=0x{cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "tsvEmnenz48" && num <= 64)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] __cxa_atexit#{num}: func=0x{_cpuContext[CpuRegister.Rdi]:X16} arg=0x{_cpuContext[CpuRegister.Rsi]:X16} dso=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] __cxa_atexit#{num}: func=0x{cpuContext[CpuRegister.Rdi]:X16} arg=0x{cpuContext[CpuRegister.Rsi]:X16} dso=0x{cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "bzQExy189ZI" || importStubEntry.Nid == "8G2LB+A3rzg")
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {importStubEntry.Nid}#{num}: rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {importStubEntry.Nid}#{num}: rdi=0x{cpuContext[CpuRegister.Rdi]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (flag || flag2 || flag3)
|
||||
if (flag6 || flag || flag2 || flag3)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportCtx#{num}: nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16} rcx=0x{_cpuContext[CpuRegister.Rcx]:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportCtx#{num}: nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportNV#{num}: rbx=0x{value3:X16} rbp=0x{value4:X16} r12=0x{value5:X16} r13=0x{value6:X16} r14=0x{value7:X16} r15=0x{value8:X16}");
|
||||
if (flag3)
|
||||
{
|
||||
ulong num9 = _cpuContext[CpuRegister.Rsp];
|
||||
if (_cpuContext.TryReadUInt64(num9, out var value9) && _cpuContext.TryReadUInt64(num9 + 8, out var value10) && _cpuContext.TryReadUInt64(num9 + 16, out var value11) && _cpuContext.TryReadUInt64(num9 + 24, out var value12) && _cpuContext.TryReadUInt64(num9 + 32, out var value13) && _cpuContext.TryReadUInt64(num9 + 40, out var value14) && _cpuContext.TryReadUInt64(num9 + 48, out var value15) && _cpuContext.TryReadUInt64(num9 + 56, out var value16) && _cpuContext.TryReadUInt64(num9 + 64, out var value17))
|
||||
ulong num9 = cpuContext[CpuRegister.Rsp];
|
||||
if (cpuContext.TryReadUInt64(num9, out var value9) && cpuContext.TryReadUInt64(num9 + 8, out var value10) && cpuContext.TryReadUInt64(num9 + 16, out var value11) && cpuContext.TryReadUInt64(num9 + 24, out var value12) && cpuContext.TryReadUInt64(num9 + 32, out var value13) && cpuContext.TryReadUInt64(num9 + 40, out var value14) && cpuContext.TryReadUInt64(num9 + 48, out var value15) && cpuContext.TryReadUInt64(num9 + 56, out var value16) && cpuContext.TryReadUInt64(num9 + 64, out var value17))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportStackHead#{num}: rsp=0x{num9:X16} [0]=0x{value9:X16} [20]=0x{value13:X16} [40]=0x{value17:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportStack#{num}: rsp=0x{num9:X16} [0]=0x{value9:X16} [8]=0x{value10:X16} [10]=0x{value11:X16} [18]=0x{value12:X16} [20]=0x{value13:X16} [28]=0x{value14:X16} [30]=0x{value15:X16} [38]=0x{value16:X16} [40]=0x{value17:X16}");
|
||||
}
|
||||
}
|
||||
if (flag6 && string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FRAMES"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
TraceImportFrameChain(cpuContext, num);
|
||||
}
|
||||
if (flag6 && string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_RECENT"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
DumpRecentImportTrace();
|
||||
}
|
||||
if (flag3)
|
||||
{
|
||||
Console.Error.Flush();
|
||||
@@ -243,6 +260,16 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
if (importStubEntry.Nid == "Ou3iL1abvng")
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STACK_CHK"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
var savedGuardAddress = value4 >= 0x10 ? value4 - 0x10 : 0;
|
||||
var guardKnown = TryReadUInt64Compat(value3, out var guardValue);
|
||||
var savedKnown = TryReadUInt64Compat(savedGuardAddress, out var savedGuardValue);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] stack_chk_diag#{num}: ret=0x{num7:X16} guard_ptr=0x{value3:X16} " +
|
||||
$"guard={(guardKnown ? $"0x{guardValue:X16}" : "?")} saved@0x{savedGuardAddress:X16}={(savedKnown ? $"0x{savedGuardValue:X16}" : "?")} " +
|
||||
$"rbp=0x{value4:X16} rsp=0x{((ulong)argPackPtr + 96uL):X16}");
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] array = new byte[64];
|
||||
@@ -258,17 +285,31 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
OrbisGen2Result orbisGen2Result;
|
||||
bool dispatchResolved = true;
|
||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(num7, (ulong)argPackPtr + 104uL);
|
||||
try
|
||||
{
|
||||
orbisGen2Result = DispatchBootstrapBridge();
|
||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchBootstrapBridge();
|
||||
}
|
||||
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchKernelDynlibDlsym();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, cpuContext, out orbisGen2Result);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal))
|
||||
finally
|
||||
{
|
||||
orbisGen2Result = DispatchKernelDynlibDlsym();
|
||||
GuestThreadExecution.RestoreImportCallFrame(previousImportCallFrame);
|
||||
}
|
||||
else
|
||||
if (dispatchResolved &&
|
||||
orbisGen2Result == OrbisGen2Result.ORBIS_GEN2_OK &&
|
||||
string.Equals(importStubEntry.Nid, "BohYr-F7-is", StringComparison.Ordinal))
|
||||
{
|
||||
dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, _cpuContext, out orbisGen2Result);
|
||||
RegisterPrtLazyCommitRange(value2, num3);
|
||||
}
|
||||
if (!dispatchResolved)
|
||||
{
|
||||
@@ -287,59 +328,86 @@ public sealed partial class DirectExecutionBackend
|
||||
ExportedFunction export2;
|
||||
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
|
||||
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
|
||||
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
|
||||
}
|
||||
}
|
||||
else if (orbisGen2Result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid}) " +
|
||||
$"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
_cpuContext[CpuRegister.Rbx] = value3;
|
||||
_cpuContext[CpuRegister.Rbp] = value4;
|
||||
_cpuContext[CpuRegister.R12] = value5;
|
||||
_cpuContext[CpuRegister.R13] = value6;
|
||||
_cpuContext[CpuRegister.R14] = value7;
|
||||
_cpuContext[CpuRegister.R15] = value8;
|
||||
_cpuContext[CpuRegister.Rdi] = value;
|
||||
_cpuContext[CpuRegister.Rsi] = value2;
|
||||
cpuContext[CpuRegister.Rbx] = value3;
|
||||
cpuContext[CpuRegister.Rbp] = value4;
|
||||
cpuContext[CpuRegister.R12] = value5;
|
||||
cpuContext[CpuRegister.R13] = value6;
|
||||
cpuContext[CpuRegister.R14] = value7;
|
||||
cpuContext[CpuRegister.R15] = value8;
|
||||
cpuContext[CpuRegister.Rdi] = value;
|
||||
cpuContext[CpuRegister.Rsi] = value2;
|
||||
if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitStatus, out var exitReason))
|
||||
{
|
||||
if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, exitStatus))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = unchecked((ulong)exitStatus);
|
||||
cpuContext[CpuRegister.Rax] = unchecked((ulong)exitStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
LastError = $"Failed to complete guest entry after {importStubEntry.Nid}: missing host return sentinel";
|
||||
_cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
}
|
||||
}
|
||||
if (GuestThreadExecution.TryConsumeCurrentThreadBlock(out var blockReason) &&
|
||||
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, blockReason))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 0uL;
|
||||
cpuContext[CpuRegister.Rax] = 0uL;
|
||||
}
|
||||
if (flag || flag2 || flag3)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{cpuContext[CpuRegister.Rax]:X16}");
|
||||
if (flag3)
|
||||
{
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
return _cpuContext[CpuRegister.Rax];
|
||||
return cpuContext[CpuRegister.Rax];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LastError = $"HLE dispatch error for {importStubEntry.Nid}: {ex.GetType().Name}: {ex.Message}";
|
||||
_cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
return 18446744071562199298uL;
|
||||
}
|
||||
}
|
||||
|
||||
private void TraceImportFrameChain(CpuContext context, long dispatchIndex)
|
||||
{
|
||||
var frame = context[CpuRegister.Rbp];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (!context.TryReadUInt64(frame, out var next) ||
|
||||
!context.TryReadUInt64(frame + sizeof(ulong), out var returnRip))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var symbol = TryFormatNearestRuntimeSymbol(returnRip, out var formatted)
|
||||
? $" [{formatted}]"
|
||||
: string.Empty;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ImportFrame#{dispatchIndex}.{i}: rbp=0x{frame:X16} ret=0x{returnRip:X16}{symbol} next=0x{next:X16}");
|
||||
if (next <= frame || next - frame > 0x100000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
frame = next;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryForceGuestExitToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid)
|
||||
{
|
||||
ulong num = _entryReturnSentinelRip;
|
||||
ulong num = ActiveEntryReturnSentinelRip;
|
||||
if (num < 65536)
|
||||
{
|
||||
return false;
|
||||
@@ -352,7 +420,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_forcedGuestExit = true;
|
||||
ActiveForcedGuestExit = true;
|
||||
LastError = $"Detected repeating import loop at import#{dispatchIndex} ({nid}) and forced guest exit.";
|
||||
Console.Error.WriteLine($"[LOADER][ERROR] Import-loop guard fired at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} -> host_exit=0x{num:X16}");
|
||||
DumpRecentImportTrace();
|
||||
@@ -361,7 +429,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status)
|
||||
{
|
||||
ulong hostExit = _entryReturnSentinelRip;
|
||||
ulong hostExit = ActiveEntryReturnSentinelRip;
|
||||
if (hostExit < 65536)
|
||||
{
|
||||
return false;
|
||||
@@ -381,7 +449,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason)
|
||||
{
|
||||
ulong hostExit = _entryReturnSentinelRip;
|
||||
ulong hostExit = ActiveEntryReturnSentinelRip;
|
||||
if (hostExit < 65536)
|
||||
{
|
||||
return false;
|
||||
@@ -395,10 +463,10 @@ public sealed partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
|
||||
_guestThreadYieldRequested = true;
|
||||
_guestThreadYieldReason = string.IsNullOrWhiteSpace(reason) ? nid : reason;
|
||||
ActiveGuestThreadYieldRequested = true;
|
||||
ActiveGuestThreadYieldReason = string.IsNullOrWhiteSpace(reason) ? nid : reason;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={_guestThreadYieldReason}");
|
||||
$"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={ActiveGuestThreadYieldReason}");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -424,10 +492,35 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
_importLoopPatternHits--;
|
||||
}
|
||||
if (_importLoopPatternHits == 0)
|
||||
{
|
||||
_importLoopPatternStartTimestamp = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (_importLoopPatternStartTimestamp == 0)
|
||||
{
|
||||
_importLoopPatternStartTimestamp = Stopwatch.GetTimestamp();
|
||||
}
|
||||
_importLoopPatternHits++;
|
||||
return _importLoopPatternHits >= 6;
|
||||
var guardSeconds = GetImportLoopGuardSeconds();
|
||||
if (guardSeconds <= 0 || _importLoopPatternHits < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var elapsedTicks = Stopwatch.GetTimestamp() - _importLoopPatternStartTimestamp;
|
||||
return elapsedTicks >= (long)(guardSeconds * Stopwatch.Frequency);
|
||||
}
|
||||
|
||||
private static int GetImportLoopGuardSeconds()
|
||||
{
|
||||
if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_IMPORT_LOOP_GUARD_SECONDS"), out var seconds))
|
||||
{
|
||||
return Math.Max(0, seconds);
|
||||
}
|
||||
|
||||
return DefaultImportLoopGuardSeconds;
|
||||
}
|
||||
|
||||
private ulong BuildImportLoopSignature(ulong nidHash, ulong returnRip, ulong arg0, ulong arg1)
|
||||
@@ -645,41 +738,43 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private OrbisGen2Result DispatchKernelDynlibDlsym()
|
||||
{
|
||||
if (_cpuContext == null)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
ulong symbolNameAddress = _cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = _cpuContext[CpuRegister.Rdx];
|
||||
ulong symbolNameAddress = cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = cpuContext[CpuRegister.Rdx];
|
||||
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
if (outputAddress == 0L || !TryWriteUInt64Compat(outputAddress, resolvedAddress))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
_cpuContext[CpuRegister.Rax] = 0uL;
|
||||
cpuContext[CpuRegister.Rax] = 0uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private OrbisGen2Result DispatchBootstrapBridge()
|
||||
{
|
||||
if (_cpuContext == null)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ulong bridgeHandle = _cpuContext[CpuRegister.Rdi];
|
||||
ulong symbolNameAddress = _cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = _cpuContext[CpuRegister.Rdx];
|
||||
ulong bridgeHandle = cpuContext[CpuRegister.Rdi];
|
||||
ulong symbolNameAddress = cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = cpuContext[CpuRegister.Rdx];
|
||||
_ = TryReadAsciiZ(symbolNameAddress, 512, out var symbolName);
|
||||
|
||||
OrbisGen2Result result = DispatchKernelDynlibDlsym();
|
||||
@@ -691,10 +786,10 @@ public sealed partial class DirectExecutionBackend
|
||||
if (logBootstrap)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] bootstrap_dispatch: handle=0x{bridgeHandle:X16} symbol='{symbolName}' out=0x{outputAddress:X16} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||
$"[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)
|
||||
if (cpuContext[CpuRegister.Rax] == 0uL)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -737,7 +832,7 @@ public sealed partial class DirectExecutionBackend
|
||||
private bool TryReadAsciiZ(ulong address, int maxLength, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
if (_cpuContext == null || address == 0L || maxLength <= 0)
|
||||
if (ActiveCpuContext == null || address == 0L || maxLength <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -762,11 +857,12 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private bool TryReadByteCompat(ulong address, Span<byte> destination)
|
||||
{
|
||||
if (_cpuContext == null || destination.Length == 0)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null || destination.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_cpuContext.Memory.TryRead(address, destination))
|
||||
if (cpuContext.Memory.TryRead(address, destination))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -781,13 +877,38 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryWriteUInt64Compat(ulong address, ulong value)
|
||||
private bool TryReadUInt64Compat(ulong address, out ulong value)
|
||||
{
|
||||
if (_cpuContext == null || address == 0L)
|
||||
value = 0;
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null || address == 0L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_cpuContext.TryWriteUInt64(address, value))
|
||||
if (cpuContext.TryReadUInt64(address, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
value = unchecked((ulong)Marshal.ReadInt64((nint)address));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryWriteUInt64Compat(ulong address, ulong value)
|
||||
{
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null || address == 0L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (cpuContext.TryWriteUInt64(address, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -804,7 +925,8 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private void TryBypassStackChkFailTrap(long dispatchIndex, ulong returnRip)
|
||||
{
|
||||
if (_cpuContext == null || returnRip < 32)
|
||||
var cpuContext = ActiveCpuContext;
|
||||
if (cpuContext == null || returnRip < 32)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -818,8 +940,8 @@ public sealed partial class DirectExecutionBackend
|
||||
return;
|
||||
}
|
||||
ulong value = returnRip - 21;
|
||||
ulong address = _cpuContext[CpuRegister.Rsp];
|
||||
if (_cpuContext.TryWriteUInt64(address, value))
|
||||
ulong address = cpuContext[CpuRegister.Rsp];
|
||||
if (cpuContext.TryWriteUInt64(address, value))
|
||||
{
|
||||
if (_stackChkBypassSites.Add(num) && TryPatchStackChkFailBranch(num))
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory
|
||||
public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory, IGuestMemoryAllocator
|
||||
{
|
||||
private readonly ICpuMemory _inner;
|
||||
|
||||
@@ -39,4 +39,15 @@ public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
|
||||
{
|
||||
if (_inner is IGuestMemoryAllocator allocator)
|
||||
{
|
||||
return allocator.TryAllocateGuestMemory(size, alignment, out address);
|
||||
}
|
||||
|
||||
address = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ public sealed class SelfImage
|
||||
IReadOnlyList<ulong>? initializerFunctions = null,
|
||||
ulong initFunctionEntryPoint = 0,
|
||||
ulong imageBase = 0,
|
||||
ulong procParamAddress = 0)
|
||||
ulong procParamAddress = 0,
|
||||
string? title = null,
|
||||
string? titleId = null,
|
||||
string? version = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(programHeaders);
|
||||
ArgumentNullException.ThrowIfNull(mappedRegions);
|
||||
@@ -38,6 +41,9 @@ public sealed class SelfImage
|
||||
InitFunctionEntryPoint = initFunctionEntryPoint;
|
||||
_imageBase = imageBase;
|
||||
ProcParamAddress = procParamAddress;
|
||||
Title = title;
|
||||
TitleId = titleId;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public bool IsSelf { get; }
|
||||
@@ -63,4 +69,10 @@ public sealed class SelfImage
|
||||
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
|
||||
|
||||
public ulong ProcParamAddress { get; }
|
||||
|
||||
public string? Title { get; }
|
||||
|
||||
public string? TitleId { get; }
|
||||
|
||||
public string? Version { get; }
|
||||
}
|
||||
|
||||
@@ -144,10 +144,9 @@ public sealed class SelfLoader : ISelfLoader
|
||||
throw new InvalidDataException("Input image is empty.");
|
||||
}
|
||||
|
||||
if (readParamJson)
|
||||
{
|
||||
TryLoadParamJson(fs, mountRoot);
|
||||
}
|
||||
var applicationInfo = readParamJson
|
||||
? TryLoadParamJson(fs, mountRoot)
|
||||
: default;
|
||||
|
||||
if (clearVirtualMemory)
|
||||
{
|
||||
@@ -270,15 +269,20 @@ public sealed class SelfLoader : ISelfLoader
|
||||
initializerFunctions,
|
||||
initFunctionEntryPoint,
|
||||
imageBase,
|
||||
procParamAddress);
|
||||
procParamAddress,
|
||||
applicationInfo.Title,
|
||||
applicationInfo.TitleId,
|
||||
applicationInfo.Version);
|
||||
}
|
||||
|
||||
private static void TryLoadParamJson(IFileSystem? fs, string? mountRoot)
|
||||
private static (string? Title, string? TitleId, string? Version) TryLoadParamJson(
|
||||
IFileSystem? fs,
|
||||
string? mountRoot)
|
||||
{
|
||||
if (fs == null)
|
||||
{
|
||||
Console.WriteLine("[LOADER] param.json not found (no filesystem provided).");
|
||||
return;
|
||||
return default;
|
||||
}
|
||||
|
||||
string[] possiblePaths = string.IsNullOrEmpty(mountRoot)
|
||||
@@ -298,12 +302,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
if (foundPath == null)
|
||||
{
|
||||
Console.WriteLine("[LOADER] param.json not found (no root path / unknown layout).");
|
||||
return;
|
||||
return default;
|
||||
}
|
||||
|
||||
var (title, titleId, ver) = Ps5ParamJsonReader.TryReadPs5Param(fs, foundPath);
|
||||
var applicationInfo = Ps5ParamJsonReader.TryReadPs5Param(fs, foundPath);
|
||||
Console.WriteLine($"[LOADER] Loading param.json at {foundPath}");
|
||||
Console.WriteLine($"[LOADER] Title: {title ?? "(unknown)"}, TitleId: {titleId ?? "(unknown)"}, Version: {ver ?? "(unknown)"}");
|
||||
Console.WriteLine(
|
||||
$"[LOADER] Title: {applicationInfo.Title ?? "(unknown)"}, " +
|
||||
$"TitleId: {applicationInfo.TitleId ?? "(unknown)"}, " +
|
||||
$"Version: {applicationInfo.Version ?? "(unknown)"}");
|
||||
return applicationInfo;
|
||||
}
|
||||
|
||||
private static LoadContext ParseLayout(ReadOnlySpan<byte> imageData)
|
||||
|
||||
@@ -7,13 +7,17 @@ using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Memory;
|
||||
|
||||
public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IDisposable
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly object _guestAllocationGate = new();
|
||||
private readonly List<MemoryRegion> _regions = new();
|
||||
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
||||
private bool _disposed;
|
||||
private const ulong PageSize = 0x1000;
|
||||
private const ulong GuestAllocationArenaAddress = 0x00006000_0000_0000;
|
||||
private const ulong GuestAllocationArenaSize = 0x0100_0000;
|
||||
private const ulong GuestAllocationArenaStartOffset = PageSize;
|
||||
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
|
||||
private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB
|
||||
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
|
||||
@@ -29,6 +33,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
private const uint PAGE_READWRITE = 0x04;
|
||||
private const uint PAGE_READONLY = 0x02;
|
||||
|
||||
private ulong _guestAllocationArenaBase;
|
||||
private ulong _guestAllocationOffset;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
|
||||
|
||||
@@ -215,16 +222,61 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return actualAddress;
|
||||
}
|
||||
|
||||
public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
|
||||
{
|
||||
address = 0;
|
||||
if (size == 0 || alignment == 0 || (alignment & (alignment - 1)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_guestAllocationGate)
|
||||
{
|
||||
if (_guestAllocationArenaBase == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_guestAllocationArenaBase = AllocateAt(
|
||||
GuestAllocationArenaAddress,
|
||||
GuestAllocationArenaSize,
|
||||
executable: false,
|
||||
allowAlternative: true);
|
||||
_guestAllocationOffset = GuestAllocationArenaStartOffset;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var alignedOffset = AlignUp(_guestAllocationOffset, alignment);
|
||||
if (alignedOffset > GuestAllocationArenaSize || size > GuestAllocationArenaSize - alignedOffset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
address = _guestAllocationArenaBase + alignedOffset;
|
||||
_guestAllocationOffset = alignedOffset + size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_gate)
|
||||
lock (_guestAllocationGate)
|
||||
{
|
||||
foreach (var region in _regions)
|
||||
lock (_gate)
|
||||
{
|
||||
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
|
||||
foreach (var region in _regions)
|
||||
{
|
||||
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
|
||||
}
|
||||
_regions.Clear();
|
||||
_pageProtections.Clear();
|
||||
}
|
||||
_regions.Clear();
|
||||
_pageProtections.Clear();
|
||||
|
||||
_guestAllocationArenaBase = 0;
|
||||
_guestAllocationOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
LastMilestoneLog = null;
|
||||
KernelModuleRegistry.Reset();
|
||||
var image = LoadImage(normalizedEbootPath);
|
||||
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version);
|
||||
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
||||
Console.Error.WriteLine($"[RUNTIME] Entry: 0x{image.EntryPoint:X16}");
|
||||
|
||||
@@ -8,17 +8,105 @@
|
||||
"resolved": "1.21.0",
|
||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.hle": {
|
||||
"type": "Project"
|
||||
},
|
||||
"sharpemu.libs": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.0, )"
|
||||
"SharpEmu.HLE": "[1.0.0, )",
|
||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
||||
"Silk.NET.Windowing": "[2.23.0, )"
|
||||
}
|
||||
},
|
||||
"sharpemu.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,49 @@ public interface IGuestThreadScheduler
|
||||
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
||||
|
||||
void Pump(CpuContext callerContext, string reason);
|
||||
|
||||
bool TryCallGuestFunction(
|
||||
CpuContext callerContext,
|
||||
ulong entryPoint,
|
||||
ulong arg0,
|
||||
ulong arg1,
|
||||
ulong stackAddress,
|
||||
ulong stackSize,
|
||||
string reason,
|
||||
out string? error);
|
||||
|
||||
bool TryCallGuestContinuation(
|
||||
CpuContext callerContext,
|
||||
GuestCpuContinuation continuation,
|
||||
string reason,
|
||||
out string? error);
|
||||
}
|
||||
|
||||
public readonly record struct GuestImportCallFrame(
|
||||
bool IsValid,
|
||||
ulong ReturnRip,
|
||||
ulong ResumeRsp);
|
||||
|
||||
public readonly record struct GuestCpuContinuation(
|
||||
ulong Rip,
|
||||
ulong Rsp,
|
||||
ulong Rflags,
|
||||
ulong FsBase,
|
||||
ulong GsBase,
|
||||
ulong Rax,
|
||||
ulong Rcx,
|
||||
ulong Rdx,
|
||||
ulong Rbx,
|
||||
ulong Rbp,
|
||||
ulong Rsi,
|
||||
ulong Rdi,
|
||||
ulong R8,
|
||||
ulong R9,
|
||||
ulong R12,
|
||||
ulong R13,
|
||||
ulong R14,
|
||||
ulong R15);
|
||||
|
||||
public static class GuestThreadExecution
|
||||
{
|
||||
[ThreadStatic]
|
||||
@@ -34,6 +75,15 @@ public static class GuestThreadExecution
|
||||
[ThreadStatic]
|
||||
private static string? _pendingEntryExitReason;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool _hasCurrentImportCallFrame;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _currentImportReturnRip;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _currentImportResumeRsp;
|
||||
|
||||
public static IGuestThreadScheduler? Scheduler { get; set; }
|
||||
|
||||
public static bool IsGuestThread => _currentGuestThreadHandle != 0;
|
||||
@@ -48,6 +98,9 @@ public static class GuestThreadExecution
|
||||
_pendingEntryExit = false;
|
||||
_pendingEntryExitStatus = 0;
|
||||
_pendingEntryExitReason = null;
|
||||
_hasCurrentImportCallFrame = false;
|
||||
_currentImportReturnRip = 0;
|
||||
_currentImportResumeRsp = 0;
|
||||
return previous;
|
||||
}
|
||||
|
||||
@@ -58,6 +111,9 @@ public static class GuestThreadExecution
|
||||
_pendingEntryExit = false;
|
||||
_pendingEntryExitStatus = 0;
|
||||
_pendingEntryExitReason = null;
|
||||
_hasCurrentImportCallFrame = false;
|
||||
_currentImportReturnRip = 0;
|
||||
_currentImportResumeRsp = 0;
|
||||
}
|
||||
|
||||
public static bool RequestCurrentThreadBlock(string reason)
|
||||
@@ -104,4 +160,35 @@ public static class GuestThreadExecution
|
||||
_pendingEntryExitReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static GuestImportCallFrame EnterImportCallFrame(ulong returnRip, ulong resumeRsp)
|
||||
{
|
||||
var previous = new GuestImportCallFrame(
|
||||
_hasCurrentImportCallFrame,
|
||||
_currentImportReturnRip,
|
||||
_currentImportResumeRsp);
|
||||
_hasCurrentImportCallFrame = true;
|
||||
_currentImportReturnRip = returnRip;
|
||||
_currentImportResumeRsp = resumeRsp;
|
||||
return previous;
|
||||
}
|
||||
|
||||
public static void RestoreImportCallFrame(GuestImportCallFrame previous)
|
||||
{
|
||||
_hasCurrentImportCallFrame = previous.IsValid;
|
||||
_currentImportReturnRip = previous.ReturnRip;
|
||||
_currentImportResumeRsp = previous.ResumeRsp;
|
||||
}
|
||||
|
||||
public static bool TryGetCurrentImportCallFrame(out GuestImportCallFrame frame)
|
||||
{
|
||||
if (!_hasCurrentImportCallFrame)
|
||||
{
|
||||
frame = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = new GuestImportCallFrame(true, _currentImportReturnRip, _currentImportResumeRsp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE;
|
||||
|
||||
public interface IGuestMemoryAllocator
|
||||
{
|
||||
bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address);
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using System.Buffers.Binary;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
|
||||
@@ -16,7 +17,9 @@ public static class AgcExports
|
||||
private const uint ItIndexBufferSize = 0x13;
|
||||
private const uint ItIndexBase = 0x26;
|
||||
private const uint ItIndexType = 0x2A;
|
||||
private const uint ItNumInstances = 0x2F;
|
||||
private const uint ItDrawIndexOffset2 = 0x35;
|
||||
private const uint ItEventWrite = 0x46;
|
||||
private const uint ItSetShReg = 0x76;
|
||||
private const uint RZero = 0x00;
|
||||
private const uint RDrawIndexAuto = 0x04;
|
||||
@@ -25,11 +28,16 @@ public static class AgcExports
|
||||
private const uint RShRegsIndirect = 0x11;
|
||||
private const uint RCxRegsIndirect = 0x12;
|
||||
private const uint RUcRegsIndirect = 0x13;
|
||||
private const uint RAcquireMem = 0x14;
|
||||
private const uint RFlip = 0x17;
|
||||
private const uint SpiShaderPgmLoPs = 0x8;
|
||||
private const uint SpiShaderPgmHiPs = 0x9;
|
||||
private const uint SpiShaderPgmLoEs = 0xC8;
|
||||
private const uint SpiShaderPgmHiEs = 0xC9;
|
||||
private const uint SpiPsInputEna = 0x1B3;
|
||||
private const uint SpiPsInputAddr = 0x1B4;
|
||||
private const uint ComputePgmLo = 0x20C;
|
||||
private const uint ComputePgmHi = 0x20D;
|
||||
private const uint SpiPsInputCntl0 = 0x191;
|
||||
private const uint VgtPrimitiveType = 0x242;
|
||||
private const uint PsTextureUserDataRegister = 0xC;
|
||||
@@ -37,6 +45,10 @@ public static class AgcExports
|
||||
private const uint Gen5TextureType2D = 9;
|
||||
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
||||
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
||||
private const uint RegisterDefaultsVersion7 = 7;
|
||||
private const uint RegisterDefaultsVersion8 = 8;
|
||||
private const int RegisterDefaultsSize = 0x40;
|
||||
private const int RegisterDefaultBlockSize = 16 * 8;
|
||||
|
||||
private const ulong ShaderUserDataOffset = 0x08;
|
||||
private const ulong ShaderCodeOffset = 0x10;
|
||||
@@ -58,9 +70,86 @@ public static class AgcExports
|
||||
private const ulong ShaderSpecialVgtGsOutPrimTypeOffset = 0x20;
|
||||
private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28;
|
||||
private const uint CbSetShRegisterRangeMarker = 0x6875000D;
|
||||
private static readonly object _submitTraceGate = new();
|
||||
private static readonly HashSet<uint> _tracedDcbSizes = new();
|
||||
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
|
||||
private static readonly object _softwarePresenterGate = new();
|
||||
private static readonly Dictionary<(ulong Source, ulong Destination), ulong> _softwarePresenterFingerprints = new();
|
||||
private static int _submitTraceCaptured;
|
||||
private static readonly object _registerDefaultsGate = new();
|
||||
private static readonly ConditionalWeakTable<object, RegisterDefaultsAllocation> _registerDefaultsAllocations = new();
|
||||
|
||||
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
|
||||
[
|
||||
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
|
||||
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
|
||||
new(0, 12, 0x6DE4C312, [new(0x203, 0)]),
|
||||
new(0, 72, 0x38E92C91,
|
||||
[
|
||||
new(0x318, 0),
|
||||
new(0x31B, 0),
|
||||
new(0x31C, 0),
|
||||
new(0x31D, 0),
|
||||
new(0x31E, 0x48),
|
||||
new(0x31F, 0),
|
||||
new(0x321, 0),
|
||||
new(0x323, 0),
|
||||
new(0x324, 0),
|
||||
new(0x325, 0),
|
||||
new(0x390, 0),
|
||||
new(0x398, 0),
|
||||
new(0x3A0, 0),
|
||||
new(0x3A8, 0),
|
||||
new(0x3B0, 0),
|
||||
new(0x3B8, 0x0006C000),
|
||||
]),
|
||||
new(0, 73, 0x0B177B43, [new(0x00C, 0), new(0x00D, 0x40004000)]),
|
||||
new(0, 74, 0x48531062, [new(0x191, 0)]),
|
||||
new(0, 76, 0x7690AF6F,
|
||||
[
|
||||
new(0x10F, 0x4E7E0000),
|
||||
new(0x111, 0x4E7E0000),
|
||||
new(0x113, 0x4E7E0000),
|
||||
new(0x110, 0),
|
||||
new(0x112, 0),
|
||||
new(0x114, 0),
|
||||
new(0x094, 0x80000000),
|
||||
new(0x095, 0x40004000),
|
||||
new(0x0B4, 0),
|
||||
new(0x0B5, 0),
|
||||
]),
|
||||
new(1, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]),
|
||||
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
|
||||
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
|
||||
new(2, 3, 0x105971C2, [new(0x25B, 0)]),
|
||||
new(2, 7, 0x40D49AD1, [new(0x262, 0)]),
|
||||
new(2, 12, 0x9EBFAB10, [new(0x242, 0)]),
|
||||
];
|
||||
|
||||
private static readonly RegisterDefaultGroup[] InternalRegisterDefaults =
|
||||
[
|
||||
new(0, 0, 0x8FB4EDB5, [new(0x00E, 0)]),
|
||||
new(0, 1, 0xB994AD29, [new(0x2AF, 0)]),
|
||||
new(0, 2, 0xD427322F, [new(0x314, 0)]),
|
||||
new(0, 3, 0xF58FEA31, [new(0x1B5, 0)]),
|
||||
new(1, 0, 0x6AC156EF, [new(0x216, 0)]),
|
||||
new(1, 1, 0x6AC15610, [new(0x217, 0)]),
|
||||
new(1, 2, 0x6AC15009, [new(0x219, 0)]),
|
||||
new(1, 3, 0x6AC153BA, [new(0x21A, 0)]),
|
||||
new(1, 4, 0xBE7DCD73, [new(0x27D, 0)]),
|
||||
new(1, 5, 0x0C4B1438, [new(0x22A, 0)]),
|
||||
new(1, 6, 0xDB00D71A, [new(0x204, 0)]),
|
||||
new(1, 7, 0xDB00D249, [new(0x205, 0)]),
|
||||
new(1, 8, 0xDB00EC60, [new(0x206, 0)]),
|
||||
new(1, 9, 0x0C4D6FE4, [new(0x080, 0)]),
|
||||
new(1, 10, 0x0C4A80EF, [new(0x100, 0)]),
|
||||
new(1, 11, 0x0DD283E7, [new(0x006, 0)]),
|
||||
new(1, 12, 0xC620E68C, [new(0x081, 0)]),
|
||||
new(1, 13, 0xC67EFACF, [new(0x101, 0)]),
|
||||
new(1, 14, 0xD9E6D9F7, [new(0x001, 0)]),
|
||||
new(2, 0, 0x31F34B9F, [new(0x24F, 0)]),
|
||||
new(2, 1, 0xAC0F9E76, [new(0x80003FFF, 0)]),
|
||||
new(2, 2, 0x929FD95D, [new(0x250, 0)]),
|
||||
];
|
||||
|
||||
private readonly record struct TextureDescriptor(
|
||||
ulong Address,
|
||||
@@ -70,6 +159,57 @@ public static class AgcExports
|
||||
uint TileMode,
|
||||
uint Type);
|
||||
|
||||
private sealed class SubmittedDcbState
|
||||
{
|
||||
public Dictionary<uint, uint> CxRegisters { get; } = new();
|
||||
public Dictionary<uint, uint> ShRegisters { get; } = new();
|
||||
public Dictionary<uint, uint> UcRegisters { get; } = new();
|
||||
}
|
||||
|
||||
private readonly record struct RegisterDefaultValue(uint Offset, uint Value);
|
||||
|
||||
private readonly record struct RegisterDefaultGroup(
|
||||
uint Space,
|
||||
uint Index,
|
||||
uint Type,
|
||||
RegisterDefaultValue[] Registers);
|
||||
|
||||
private sealed record RegisterDefaultsAllocation(ulong Primary, ulong Internal);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "23LRUSvYu1M",
|
||||
ExportName = "sceAgcInit",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int Init(CpuContext ctx)
|
||||
{
|
||||
var stateAddress = ctx[CpuRegister.Rdi];
|
||||
var version = (uint)ctx[CpuRegister.Rsi];
|
||||
if (stateAddress == 0 || !IsSupportedRegisterDefaultsVersion(version))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.init state=0x{stateAddress:X16} version={version}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "2JtWUUiYBXs",
|
||||
ExportName = "sceAgcGetRegisterDefaults2",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int GetRegisterDefaults2(CpuContext ctx) =>
|
||||
ReturnRegisterDefaults(ctx, internalDefaults: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "wRbq6ZjNop4",
|
||||
ExportName = "sceAgcGetRegisterDefaults2Internal",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int GetRegisterDefaults2Internal(CpuContext ctx) =>
|
||||
ReturnRegisterDefaults(ctx, internalDefaults: true);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "f3dg2CSgRKY",
|
||||
ExportName = "sceAgcCreateShader",
|
||||
@@ -80,7 +220,7 @@ public static class AgcExports
|
||||
var destinationAddress = ctx[CpuRegister.Rdi];
|
||||
var headerAddress = ctx[CpuRegister.Rsi];
|
||||
var codeAddress = ctx[CpuRegister.Rdx];
|
||||
if (destinationAddress == 0 || headerAddress == 0 || codeAddress == 0)
|
||||
if (headerAddress == 0 || codeAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
@@ -128,7 +268,8 @@ public static class AgcExports
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(destinationAddress, headerAddress))
|
||||
if (destinationAddress != 0 &&
|
||||
!ctx.TryWriteUInt64(destinationAddress, headerAddress))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
@@ -430,6 +571,136 @@ public static class AgcExports
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "tSBxhAPyytQ",
|
||||
ExportName = "sceAgcDcbSetNumInstances",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbSetNumInstances(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var instanceCount = (uint)ctx[CpuRegister.Rsi];
|
||||
if (commandBufferAddress == 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
|
||||
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItNumInstances, 0)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 4, instanceCount))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.dcb_set_num_instances buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={instanceCount}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Yw0jKSqop+E",
|
||||
ExportName = "sceAgcDcbDrawIndexAuto",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbDrawIndexAuto(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||
var modifier = ctx[CpuRegister.Rdx];
|
||||
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 7, out var commandAddress) ||
|
||||
!TryWriteUInt32(ctx, commandAddress, Pm4(7, ItNop, RDrawIndexAuto)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 4, indexCount) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 8, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 16, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 20, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 24, 0))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.dcb_draw_index_auto buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={indexCount}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "aJf+j5yntiU",
|
||||
ExportName = "sceAgcDcbEventWrite",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbEventWrite(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var eventType = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
|
||||
var eventAddress = ctx[CpuRegister.Rdx];
|
||||
if (commandBufferAddress == 0 || eventType > 0x3F || eventAddress != 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
|
||||
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItEventWrite, 0)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 4, eventType))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.dcb_event_write buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} type={eventType}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "57labkp+rSQ",
|
||||
ExportName = "sceAgcDcbAcquireMem",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbAcquireMem(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var engine = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
|
||||
var cbDbOp = (uint)ctx[CpuRegister.Rdx];
|
||||
var gcrControl = (uint)ctx[CpuRegister.Rcx];
|
||||
var baseAddress = ctx[CpuRegister.R8];
|
||||
var sizeBytes = ctx[CpuRegister.R9];
|
||||
if (!TryReadUInt32(ctx, ctx[CpuRegister.Rsp] + sizeof(ulong), out var pollCycles))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
var noSize = sizeBytes == ulong.MaxValue;
|
||||
if (commandBufferAddress == 0 ||
|
||||
engine > 1 ||
|
||||
(!noSize && (sizeBytes & 0xFF) != 0) ||
|
||||
(!noSize && (sizeBytes >> 40) != 0) ||
|
||||
(baseAddress & 0xFF) != 0 ||
|
||||
(baseAddress >> 40) != 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
|
||||
!TryWriteUInt32(ctx, commandAddress, Pm4(8, ItNop, RAcquireMem)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 4, (engine << 31) | cbDbOp) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 8, noSize ? 0 : (uint)(sizeBytes >> 8)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 16, (uint)(baseAddress >> 8)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 20, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 24, pollCycles / 40) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 28, gcrControl))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_acquire_mem buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
|
||||
$"engine={engine} cbdb=0x{cbDbOp:X8} gcr=0x{gcrControl:X8} base=0x{baseAddress:X16} size=0x{sizeBytes:X16}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "l4fM9K-Lyks",
|
||||
ExportName = "sceAgcDcbSetIndexBuffer",
|
||||
@@ -567,10 +838,20 @@ public static class AgcExports
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
||||
var tracePackets =
|
||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) &&
|
||||
Interlocked.CompareExchange(ref _submitTraceCaptured, 1, 0) == 0;
|
||||
var tracePackets = false;
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
tracePackets = _tracedDcbSizes.Add(dwordCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (tracePackets)
|
||||
{
|
||||
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
||||
}
|
||||
|
||||
ParseSubmittedDcb(ctx, commandAddress, dwordCount, tracePackets);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -596,6 +877,8 @@ public static class AgcExports
|
||||
}
|
||||
|
||||
TextureDescriptor? presenterTexture = null;
|
||||
GuestDrawKind guestDrawKind = GuestDrawKind.None;
|
||||
var state = new SubmittedDcbState();
|
||||
var sawIndexedDraw = false;
|
||||
var offset = 0u;
|
||||
while (offset < dwordCount)
|
||||
@@ -625,12 +908,25 @@ public static class AgcExports
|
||||
presenterTexture = texture;
|
||||
}
|
||||
|
||||
ApplySubmittedRegisters(ctx, state, currentAddress, length, op, register);
|
||||
|
||||
if (op == ItDrawIndexOffset2 &&
|
||||
length >= 5 &&
|
||||
TryReadUInt32(ctx, currentAddress + 4, out var indexCount) &&
|
||||
indexCount != 0)
|
||||
{
|
||||
sawIndexedDraw = true;
|
||||
TryTranslateGuestDraw(ctx, state, indexCount, ref guestDrawKind);
|
||||
}
|
||||
|
||||
if (op == ItNop &&
|
||||
register == RDrawIndexAuto &&
|
||||
length >= 2 &&
|
||||
TryReadUInt32(ctx, currentAddress + 4, out var autoIndexCount) &&
|
||||
autoIndexCount != 0)
|
||||
{
|
||||
sawIndexedDraw = true;
|
||||
TryTranslateGuestDraw(ctx, state, autoIndexCount, ref guestDrawKind);
|
||||
}
|
||||
|
||||
if (op == ItNop && register == RFlip && length >= 6)
|
||||
@@ -654,6 +950,18 @@ public static class AgcExports
|
||||
unchecked((int)videoOutHandle),
|
||||
displayBufferIndex);
|
||||
}
|
||||
else if (sawIndexedDraw &&
|
||||
guestDrawKind != GuestDrawKind.None &&
|
||||
VideoOutExports.TryGetDisplayBufferInfo(
|
||||
unchecked((int)videoOutHandle),
|
||||
displayBufferIndex,
|
||||
out var displayBuffer))
|
||||
{
|
||||
VulkanVideoPresenter.SubmitGuestDraw(
|
||||
guestDrawKind,
|
||||
displayBuffer.Width,
|
||||
displayBuffer.Height);
|
||||
}
|
||||
|
||||
_ = VideoOutExports.SubmitFlipFromAgc(ctx, unchecked((int)videoOutHandle), displayBufferIndex, unchecked((int)flipMode), flipArg);
|
||||
}
|
||||
@@ -662,6 +970,120 @@ public static class AgcExports
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySubmittedRegisters(
|
||||
CpuContext ctx,
|
||||
SubmittedDcbState state,
|
||||
ulong packetAddress,
|
||||
uint packetLength,
|
||||
uint op,
|
||||
uint register)
|
||||
{
|
||||
if (op == ItSetShReg)
|
||||
{
|
||||
if (packetLength < 3 ||
|
||||
!TryReadUInt32(ctx, packetAddress + sizeof(uint), out var startRegister))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint index = 0; index < packetLength - 2; index++)
|
||||
{
|
||||
if (!TryReadUInt32(
|
||||
ctx,
|
||||
packetAddress + 8 + ((ulong)index * sizeof(uint)),
|
||||
out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.ShRegisters[startRegister + index] = value;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (op != ItNop ||
|
||||
register is not (RCxRegsIndirect or RShRegsIndirect or RUcRegsIndirect) ||
|
||||
packetLength < 4 ||
|
||||
!TryReadUInt32(ctx, packetAddress + sizeof(uint), out var registerCount) ||
|
||||
!TryReadUInt64(ctx, packetAddress + 8, out var registersAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var destination = register switch
|
||||
{
|
||||
RCxRegsIndirect => state.CxRegisters,
|
||||
RShRegsIndirect => state.ShRegisters,
|
||||
_ => state.UcRegisters,
|
||||
};
|
||||
for (uint index = 0; index < registerCount; index++)
|
||||
{
|
||||
var entryAddress = registersAddress + ((ulong)index * 8);
|
||||
if (!TryReadUInt32(ctx, entryAddress, out var registerOffset) ||
|
||||
!TryReadUInt32(ctx, entryAddress + sizeof(uint), out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (registerOffset != 0)
|
||||
{
|
||||
destination[registerOffset] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryTranslateGuestDraw(
|
||||
CpuContext ctx,
|
||||
SubmittedDcbState state,
|
||||
uint vertexCount,
|
||||
ref GuestDrawKind drawKind)
|
||||
{
|
||||
if (drawKind != GuestDrawKind.None ||
|
||||
vertexCount != 3 ||
|
||||
!TryGetShaderAddress(state.ShRegisters, SpiShaderPgmLoEs, SpiShaderPgmHiEs, out var exportShaderAddress) ||
|
||||
!TryGetShaderAddress(state.ShRegisters, SpiShaderPgmLoPs, SpiShaderPgmHiPs, out var pixelShaderAddress) ||
|
||||
!state.CxRegisters.TryGetValue(SpiPsInputEna, out var psInputEna) ||
|
||||
!state.CxRegisters.TryGetValue(SpiPsInputAddr, out var psInputAddr) ||
|
||||
!Gen5ShaderTranslator.TryTranslate(
|
||||
ctx,
|
||||
exportShaderAddress,
|
||||
pixelShaderAddress,
|
||||
psInputEna,
|
||||
psInputAddr,
|
||||
out drawKind))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
if (_tracedShaderTranslations.Add((exportShaderAddress, pixelShaderAddress, drawKind)))
|
||||
{
|
||||
TraceAgc(
|
||||
$"agc.shader_translated kind={drawKind} es=0x{exportShaderAddress:X16} " +
|
||||
$"ps=0x{pixelShaderAddress:X16} vertices={vertexCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetShaderAddress(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
uint loRegister,
|
||||
uint hiRegister,
|
||||
out ulong address)
|
||||
{
|
||||
address = 0;
|
||||
if (!registers.TryGetValue(loRegister, out var lo) ||
|
||||
!registers.TryGetValue(hiRegister, out var hi))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
address = ((ulong)hi << 40) | ((ulong)lo << 8);
|
||||
return address != 0;
|
||||
}
|
||||
|
||||
private static bool TryReadTextureDescriptor(
|
||||
CpuContext ctx,
|
||||
ulong packetAddress,
|
||||
@@ -802,6 +1224,7 @@ public static class AgcExports
|
||||
_softwarePresenterFingerprints[fingerprintKey] = fingerprint;
|
||||
}
|
||||
|
||||
VideoOutExports.SubmitHostRgbaFrame(sourceBytes, source.Width, source.Height);
|
||||
TraceAgc(
|
||||
$"agc.software_presenter src=0x{source.Address:X16} {source.Width}x{source.Height} fmt={source.Format} " +
|
||||
$"dst=0x{destination.Address:X16} {destination.Width}x{destination.Height} fingerprint=0x{fingerprint:X16}");
|
||||
@@ -895,8 +1318,20 @@ public static class AgcExports
|
||||
return false;
|
||||
}
|
||||
|
||||
var expectedLo = shaderType == 2 ? SpiShaderPgmLoEs : shaderType == 1 ? SpiShaderPgmLoPs : 0;
|
||||
var expectedHi = shaderType == 2 ? SpiShaderPgmHiEs : shaderType == 1 ? SpiShaderPgmHiPs : 0;
|
||||
var expectedLo = shaderType switch
|
||||
{
|
||||
0 => ComputePgmLo,
|
||||
1 => SpiShaderPgmLoPs,
|
||||
2 => SpiShaderPgmLoEs,
|
||||
_ => 0u,
|
||||
};
|
||||
var expectedHi = shaderType switch
|
||||
{
|
||||
0 => ComputePgmHi,
|
||||
1 => SpiShaderPgmHiPs,
|
||||
2 => SpiShaderPgmHiEs,
|
||||
_ => 0u,
|
||||
};
|
||||
if (expectedLo == 0 || loRegister != expectedLo || hiRegister != expectedHi)
|
||||
{
|
||||
TraceCreateShader(0, headerAddress, codeAddress, $"unexpected-registers type={shaderType} lo=0x{loRegister:X8} hi=0x{hiRegister:X8}");
|
||||
@@ -1031,6 +1466,150 @@ public static class AgcExports
|
||||
return ctx.TryWriteUInt64(fieldAddress, fieldAddress + relativeAddress);
|
||||
}
|
||||
|
||||
private static int ReturnRegisterDefaults(CpuContext ctx, bool internalDefaults)
|
||||
{
|
||||
var version = (uint)ctx[CpuRegister.Rdi];
|
||||
if (!IsSupportedRegisterDefaultsVersion(version))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryGetRegisterDefaultsAllocation(ctx, out var allocation))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
var address = internalDefaults ? allocation.Internal : allocation.Primary;
|
||||
TraceAgc($"agc.get_register_defaults internal={internalDefaults} version={version} address=0x{address:X16}");
|
||||
return ReturnPointer(ctx, address);
|
||||
}
|
||||
|
||||
private static bool IsSupportedRegisterDefaultsVersion(uint version)
|
||||
{
|
||||
return version is RegisterDefaultsVersion7 or RegisterDefaultsVersion8;
|
||||
}
|
||||
|
||||
private static bool TryGetRegisterDefaultsAllocation(
|
||||
CpuContext ctx,
|
||||
out RegisterDefaultsAllocation allocation)
|
||||
{
|
||||
lock (_registerDefaultsGate)
|
||||
{
|
||||
if (_registerDefaultsAllocations.TryGetValue(ctx.Memory, out allocation!))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryBuildRegisterDefaults(
|
||||
ctx,
|
||||
PrimaryRegisterDefaults,
|
||||
cxTableLength: 78,
|
||||
shTableLength: 29,
|
||||
ucTableLength: 20,
|
||||
out var primaryAddress) ||
|
||||
!TryBuildRegisterDefaults(
|
||||
ctx,
|
||||
InternalRegisterDefaults,
|
||||
cxTableLength: 4,
|
||||
shTableLength: 15,
|
||||
ucTableLength: 3,
|
||||
out var internalAddress))
|
||||
{
|
||||
allocation = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
allocation = new RegisterDefaultsAllocation(primaryAddress, internalAddress);
|
||||
_registerDefaultsAllocations.Add(ctx.Memory, allocation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryBuildRegisterDefaults(
|
||||
CpuContext ctx,
|
||||
RegisterDefaultGroup[] groups,
|
||||
int cxTableLength,
|
||||
int shTableLength,
|
||||
int ucTableLength,
|
||||
out ulong address)
|
||||
{
|
||||
var cxTableOffset = AlignUp(RegisterDefaultsSize, sizeof(ulong));
|
||||
var shTableOffset = cxTableOffset + (cxTableLength * sizeof(ulong));
|
||||
var ucTableOffset = shTableOffset + (shTableLength * sizeof(ulong));
|
||||
var typesOffset = AlignUp(ucTableOffset + (ucTableLength * sizeof(ulong)), sizeof(uint));
|
||||
var registerBlocksOffset = AlignUp(typesOffset + (groups.Length * 3 * sizeof(uint)), sizeof(ulong));
|
||||
var blobLength = registerBlocksOffset + (groups.Length * RegisterDefaultBlockSize);
|
||||
|
||||
if (!KernelMemoryCompatExports.TryAllocateHleData(ctx, (ulong)blobLength, 0x1000, out address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var blob = new byte[blobLength];
|
||||
WriteBlobUInt64(blob, 0x00, address + (ulong)cxTableOffset);
|
||||
WriteBlobUInt64(blob, 0x08, address + (ulong)shTableOffset);
|
||||
WriteBlobUInt64(blob, 0x10, address + (ulong)ucTableOffset);
|
||||
WriteBlobUInt64(blob, 0x30, address + (ulong)typesOffset);
|
||||
WriteBlobUInt32(blob, 0x38, (uint)groups.Length);
|
||||
|
||||
for (var groupIndex = 0; groupIndex < groups.Length; groupIndex++)
|
||||
{
|
||||
var group = groups[groupIndex];
|
||||
if (group.Registers.Length > 16)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var tableOffset = group.Space switch
|
||||
{
|
||||
0 => cxTableOffset,
|
||||
1 => shTableOffset,
|
||||
2 => ucTableOffset,
|
||||
_ => -1,
|
||||
};
|
||||
var tableLength = group.Space switch
|
||||
{
|
||||
0 => cxTableLength,
|
||||
1 => shTableLength,
|
||||
2 => ucTableLength,
|
||||
_ => 0,
|
||||
};
|
||||
if (tableOffset < 0 || group.Index >= tableLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var registerBlockOffset = registerBlocksOffset + (groupIndex * RegisterDefaultBlockSize);
|
||||
WriteBlobUInt64(
|
||||
blob,
|
||||
tableOffset + ((int)group.Index * sizeof(ulong)),
|
||||
address + (ulong)registerBlockOffset);
|
||||
|
||||
var typeEntryOffset = typesOffset + (groupIndex * 3 * sizeof(uint));
|
||||
WriteBlobUInt32(blob, typeEntryOffset, group.Type);
|
||||
WriteBlobUInt32(blob, typeEntryOffset + sizeof(uint), (group.Index * 4) + group.Space);
|
||||
|
||||
for (var registerIndex = 0; registerIndex < group.Registers.Length; registerIndex++)
|
||||
{
|
||||
var register = group.Registers[registerIndex];
|
||||
var registerOffset = registerBlockOffset + (registerIndex * 2 * sizeof(uint));
|
||||
WriteBlobUInt32(blob, registerOffset, register.Offset);
|
||||
WriteBlobUInt32(blob, registerOffset + sizeof(uint), register.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Memory.TryWrite(address, blob);
|
||||
}
|
||||
|
||||
private static int AlignUp(int value, int alignment) =>
|
||||
(value + alignment - 1) & -alignment;
|
||||
|
||||
private static void WriteBlobUInt32(Span<byte> blob, int offset, uint value) =>
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(blob[offset..], value);
|
||||
|
||||
private static void WriteBlobUInt64(Span<byte> blob, int offset, ulong value) =>
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(blob[offset..], value);
|
||||
|
||||
private static int ReturnPointer(CpuContext ctx, ulong pointer)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = pointer;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
|
||||
internal static class Gen5ShaderTranslator
|
||||
{
|
||||
private static readonly uint[] FullscreenBarycentricEs =
|
||||
[
|
||||
0xBFA00001, 0x7E000000, 0x7E000000, 0x7E000000,
|
||||
0x93EBFF03, 0x00080008, 0x8F6A8C6B, 0x8700FF03,
|
||||
0x000000FF, 0x887C6A00, 0xBF900009, 0x81EA6BC0,
|
||||
0x90FE6AC1, 0xF8000941, 0x00000000, 0x81EA00C0,
|
||||
0xBF8CFF0F, 0x90FE6AC1, 0x36040A81, 0x2C060A81,
|
||||
0x7E000280, 0x7E0202F2, 0xD7460002, 0x03050302,
|
||||
0xD7460003, 0x03050303, 0x7E040B02, 0x7E060B03,
|
||||
0xF80008CF, 0x01000302, 0xBF810000,
|
||||
];
|
||||
|
||||
private static readonly uint[] FullscreenBarycentricPs =
|
||||
[
|
||||
0xD52F0000, 0x00000200,
|
||||
0xD52F0001, 0x00000602,
|
||||
0xF8001C0F, 0x00000100,
|
||||
0xBF810000,
|
||||
];
|
||||
|
||||
public static bool TryTranslate(
|
||||
CpuContext ctx,
|
||||
ulong exportShaderAddress,
|
||||
ulong pixelShaderAddress,
|
||||
uint psInputEna,
|
||||
uint psInputAddr,
|
||||
out GuestDrawKind drawKind)
|
||||
{
|
||||
drawKind = GuestDrawKind.None;
|
||||
if (exportShaderAddress == 0 ||
|
||||
pixelShaderAddress == 0 ||
|
||||
psInputEna != 0x00000002 ||
|
||||
psInputAddr != 0x00000002 ||
|
||||
!MatchesProgram(ctx, exportShaderAddress, FullscreenBarycentricEs) ||
|
||||
!MatchesProgram(ctx, pixelShaderAddress, FullscreenBarycentricPs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawKind = GuestDrawKind.FullscreenBarycentric;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MatchesProgram(CpuContext ctx, ulong address, ReadOnlySpan<uint> expected)
|
||||
{
|
||||
var bytes = new byte[expected.Length * sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var index = 0; index < expected.Length; index++)
|
||||
{
|
||||
if (BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(index * sizeof(uint))) != expected[index])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.CommonDialog;
|
||||
|
||||
public static class CommonDialogExports
|
||||
{
|
||||
private const int AlreadySystemInitialized = unchecked((int)0x80B80002);
|
||||
private static int _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uoUpLGNkygk",
|
||||
ExportName = "sceCommonDialogInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceCommonDialog")]
|
||||
public static int CommonDialogInitialize(CpuContext ctx)
|
||||
{
|
||||
var result = Interlocked.Exchange(ref _initialized, 1) == 0
|
||||
? 0
|
||||
: AlreadySystemInitialized;
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "BQ3tey0JmQM",
|
||||
ExportName = "sceCommonDialogIsUsed",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceCommonDialog")]
|
||||
public static int CommonDialogIsUsed(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,961 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Fiber;
|
||||
|
||||
public static class FiberExports
|
||||
{
|
||||
private const int MaxNameLength = 31;
|
||||
private const int FiberInfoSize = 128;
|
||||
private const int FiberContextMinimumSize = 512;
|
||||
private const uint FiberSignature0 = 0xDEF1649C;
|
||||
private const uint FiberSignature1 = 0xB37592A0;
|
||||
private const uint FiberOptSignature = 0xBB40E64D;
|
||||
private const ulong FiberStackSignature = 0x7149F2CA7149F2CAUL;
|
||||
private const ulong FiberStackSizeCheck = 0xDEADBEEFDEADBEEFUL;
|
||||
private const uint FiberStateRun = 1;
|
||||
private const uint FiberStateIdle = 2;
|
||||
private const uint FiberStateTerminated = 3;
|
||||
private const uint FiberFlagContextSizeCheck = 0x10;
|
||||
|
||||
private const int FiberErrorNull = unchecked((int)0x80590001);
|
||||
private const int FiberErrorAlignment = unchecked((int)0x80590002);
|
||||
private const int FiberErrorRange = unchecked((int)0x80590003);
|
||||
private const int FiberErrorInvalid = unchecked((int)0x80590004);
|
||||
private const int FiberErrorPermission = unchecked((int)0x80590005);
|
||||
private const int FiberErrorState = unchecked((int)0x80590006);
|
||||
|
||||
private const int FiberMagicStartOffset = 0;
|
||||
private const int FiberStateOffset = 4;
|
||||
private const int FiberEntryOffset = 8;
|
||||
private const int FiberArgOnInitializeOffset = 16;
|
||||
private const int FiberContextAddressOffset = 24;
|
||||
private const int FiberContextSizeOffset = 32;
|
||||
private const int FiberNameOffset = 40;
|
||||
private const int FiberContextPointerOffset = 72;
|
||||
private const int FiberFlagsOffset = 80;
|
||||
private const int FiberContextStartOffset = 88;
|
||||
private const int FiberContextEndOffset = 96;
|
||||
private const int FiberMagicEndOffset = 104;
|
||||
|
||||
private static int _contextSizeCheck;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _currentFiberAddress;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool _fiberReturnRequested;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _fiberReturnArgument;
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, FiberContinuation> _continuations = new();
|
||||
private static readonly ConcurrentDictionary<ulong, FiberStackRange> _stackRanges = new();
|
||||
private static readonly ConcurrentDictionary<ulong, FiberRunSession> _runSessions = new();
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hVYD7Ou2pCQ",
|
||||
ExportName = "_sceFiberInitializeImpl",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberInitialize(CpuContext ctx)
|
||||
{
|
||||
var optParam = ReadStackArg64(ctx, 0);
|
||||
return FiberInitializeCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdx],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
ctx[CpuRegister.R9],
|
||||
optParam,
|
||||
flags: 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "7+OJIpko9RY",
|
||||
ExportName = "_sceFiberInitializeWithInternalOptionImpl",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberInitializeWithInternalOption(CpuContext ctx)
|
||||
{
|
||||
var optParam = ReadStackArg64(ctx, 0);
|
||||
var flags = unchecked((uint)ReadStackArg64(ctx, 1));
|
||||
return FiberInitializeCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdx],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
ctx[CpuRegister.R9],
|
||||
optParam,
|
||||
flags);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "asjUJJ+aa8s",
|
||||
ExportName = "sceFiberOptParamInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberOptParamInitialize(CpuContext ctx)
|
||||
{
|
||||
var optParam = ctx[CpuRegister.Rdi];
|
||||
if (optParam == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if ((optParam & 7) != 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorAlignment);
|
||||
}
|
||||
|
||||
return TryWriteUInt32(ctx, optParam, FiberOptSignature)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JeNX5F-NzQU",
|
||||
ExportName = "sceFiberFinalize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberFinalize(CpuContext ctx)
|
||||
{
|
||||
var fiber = ctx[CpuRegister.Rdi];
|
||||
if (!TryValidateFiber(ctx, fiber, out var error))
|
||||
{
|
||||
return SetReturn(ctx, error);
|
||||
}
|
||||
|
||||
if (!TryReadUInt32(ctx, fiber + FiberStateOffset, out var state))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (state != FiberStateIdle)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorState);
|
||||
}
|
||||
|
||||
_continuations.TryRemove(fiber, out _);
|
||||
_stackRanges.TryRemove(fiber, out _);
|
||||
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateTerminated);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "a0LLrZWac0M",
|
||||
ExportName = "sceFiberRun",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberRun(CpuContext ctx)
|
||||
{
|
||||
return FiberRunCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdx],
|
||||
attachContextAddress: 0,
|
||||
attachContextSize: 0,
|
||||
reason: "sceFiberRun",
|
||||
isSwitch: false);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "avfGJ94g36Q",
|
||||
ExportName = "_sceFiberAttachContextAndRun",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberAttachContextAndRun(CpuContext ctx)
|
||||
{
|
||||
return FiberRunCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
attachContextAddress: ctx[CpuRegister.Rsi],
|
||||
attachContextSize: ctx[CpuRegister.Rdx],
|
||||
reason: "_sceFiberAttachContextAndRun",
|
||||
isSwitch: false);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "PFT2S-tJ7Uk",
|
||||
ExportName = "sceFiberSwitch",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberSwitch(CpuContext ctx)
|
||||
{
|
||||
return FiberRunCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdx],
|
||||
attachContextAddress: 0,
|
||||
attachContextSize: 0,
|
||||
reason: "sceFiberSwitch",
|
||||
isSwitch: true);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ZqhZFuzKT6U",
|
||||
ExportName = "_sceFiberAttachContextAndSwitch",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberAttachContextAndSwitch(CpuContext ctx)
|
||||
{
|
||||
return FiberRunCore(
|
||||
ctx,
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
attachContextAddress: ctx[CpuRegister.Rsi],
|
||||
attachContextSize: ctx[CpuRegister.Rdx],
|
||||
reason: "_sceFiberAttachContextAndSwitch",
|
||||
isSwitch: true);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "B0ZX2hx9DMw",
|
||||
ExportName = "sceFiberReturnToThread",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberReturnToThread(CpuContext ctx)
|
||||
{
|
||||
var fiberAddress = _currentFiberAddress;
|
||||
var inferredFiber = false;
|
||||
if (fiberAddress == 0 && TryFindFiberByStack(ctx, out fiberAddress))
|
||||
{
|
||||
inferredFiber = true;
|
||||
}
|
||||
|
||||
if (fiberAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
var argOnRunAddress = ctx[CpuRegister.Rsi];
|
||||
if (argOnRunAddress != 0 && !TryWriteUInt64(ctx, argOnRunAddress, 0))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
_fiberReturnRequested = true;
|
||||
_fiberReturnArgument = ctx[CpuRegister.Rdi];
|
||||
if (_runSessions.TryGetValue(fiberAddress, out var session))
|
||||
{
|
||||
session.SetReturn(ctx[CpuRegister.Rdi]);
|
||||
}
|
||||
|
||||
if (GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame))
|
||||
{
|
||||
_continuations[fiberAddress] = new FiberContinuation(
|
||||
CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp),
|
||||
argOnRunAddress);
|
||||
TraceFiber($"yield{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} resume=0x{frame.ReturnRip:X16} rsp=0x{frame.ResumeRsp:X16} arg_out=0x{argOnRunAddress:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceFiber($"yield-no-frame{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} arg_out=0x{argOnRunAddress:X16}");
|
||||
}
|
||||
|
||||
GuestThreadExecution.RequestCurrentEntryExit("sceFiberReturnToThread", 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "p+zLIOg27zU",
|
||||
ExportName = "sceFiberGetSelf",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberGetSelf(CpuContext ctx)
|
||||
{
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
if (outAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if (_currentFiberAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
return TryWriteUInt64(ctx, outAddress, _currentFiberAddress)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uq2Y5BFz0PE",
|
||||
ExportName = "sceFiberGetInfo",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberGetInfo(CpuContext ctx)
|
||||
{
|
||||
var fiber = ctx[CpuRegister.Rdi];
|
||||
var info = ctx[CpuRegister.Rsi];
|
||||
if (info == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if (!TryValidateFiber(ctx, fiber, out var error))
|
||||
{
|
||||
return SetReturn(ctx, error);
|
||||
}
|
||||
|
||||
if (!TryReadUInt64(ctx, info, out var size) || size != FiberInfoSize)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (!TryReadFiberFields(ctx, fiber, out var fields))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (!TryWriteUInt64(ctx, info + 8, fields.Entry) ||
|
||||
!TryWriteUInt64(ctx, info + 16, fields.ArgOnInitialize) ||
|
||||
!TryWriteUInt64(ctx, info + 24, fields.ContextAddress) ||
|
||||
!TryWriteUInt64(ctx, info + 32, fields.ContextSize) ||
|
||||
!TryWriteName(ctx, info + 40, fields.Name) ||
|
||||
!TryWriteUInt64(ctx, info + 72, ulong.MaxValue))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JzyT91ucGDc",
|
||||
ExportName = "sceFiberRename",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberRename(CpuContext ctx)
|
||||
{
|
||||
var fiber = ctx[CpuRegister.Rdi];
|
||||
var nameAddress = ctx[CpuRegister.Rsi];
|
||||
if (!TryValidateFiber(ctx, fiber, out var error))
|
||||
{
|
||||
return SetReturn(ctx, error);
|
||||
}
|
||||
|
||||
if (nameAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxNameLength + 1, out var name))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
return TryWriteName(ctx, fiber + FiberNameOffset, name)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Lcqty+QNWFc",
|
||||
ExportName = "sceFiberStartContextSizeCheck",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberStartContextSizeCheck(CpuContext ctx)
|
||||
{
|
||||
if (ctx[CpuRegister.Rdi] != 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
return Interlocked.Exchange(ref _contextSizeCheck, 1) == 0
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorState);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Kj4nXMpnM8Y",
|
||||
ExportName = "sceFiberStopContextSizeCheck",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberStopContextSizeCheck(CpuContext ctx)
|
||||
{
|
||||
return Interlocked.Exchange(ref _contextSizeCheck, 0) == 1
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorState);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "0dy4JtMUcMQ",
|
||||
ExportName = "_sceFiberGetThreadFramePointerAddress",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceFiber")]
|
||||
public static int FiberGetThreadFramePointerAddress(CpuContext ctx)
|
||||
{
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
if (outAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if (_currentFiberAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
return TryWriteUInt64(ctx, outAddress, ctx[CpuRegister.Rbp])
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
private static int FiberInitializeCore(
|
||||
CpuContext ctx,
|
||||
ulong fiber,
|
||||
ulong nameAddress,
|
||||
ulong entry,
|
||||
ulong argOnInitialize,
|
||||
ulong contextAddress,
|
||||
ulong contextSize,
|
||||
ulong optParam,
|
||||
uint flags)
|
||||
{
|
||||
if (fiber == 0 || nameAddress == 0 || entry == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
if ((fiber & 7) != 0 ||
|
||||
(contextAddress & 15) != 0 ||
|
||||
(optParam & 7) != 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorAlignment);
|
||||
}
|
||||
|
||||
if (contextSize != 0 && contextSize < FiberContextMinimumSize)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorRange);
|
||||
}
|
||||
|
||||
if ((contextSize & 15) != 0 ||
|
||||
(contextAddress == 0 && contextSize != 0) ||
|
||||
(contextAddress != 0 && contextSize == 0))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (optParam != 0 &&
|
||||
(!TryReadUInt32(ctx, optParam, out var optMagic) || optMagic != FiberOptSignature))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxNameLength + 1, out var name))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (Volatile.Read(ref _contextSizeCheck) != 0)
|
||||
{
|
||||
flags |= FiberFlagContextSizeCheck;
|
||||
}
|
||||
|
||||
if (!TryWriteUInt32(ctx, fiber + FiberMagicStartOffset, FiberSignature0) ||
|
||||
!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberEntryOffset, entry) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberArgOnInitializeOffset, argOnInitialize) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextAddressOffset, contextAddress) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextSizeOffset, contextSize) ||
|
||||
!TryWriteName(ctx, fiber + FiberNameOffset, name) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextPointerOffset, 0) ||
|
||||
!TryWriteUInt32(ctx, fiber + FiberFlagsOffset, flags) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextStartOffset, contextAddress) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextEndOffset, contextAddress == 0 ? 0 : contextAddress + contextSize) ||
|
||||
!TryWriteUInt32(ctx, fiber + FiberMagicEndOffset, FiberSignature1))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (contextAddress != 0)
|
||||
{
|
||||
if (!TryWriteUInt64(ctx, contextAddress, FiberStackSignature))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if ((flags & FiberFlagContextSizeCheck) != 0)
|
||||
{
|
||||
FillContextSizeCheck(ctx, contextAddress + sizeof(ulong), contextSize - sizeof(ulong));
|
||||
}
|
||||
}
|
||||
|
||||
if (contextAddress != 0 && contextSize != 0)
|
||||
{
|
||||
_stackRanges[fiber] = new FiberStackRange(contextAddress, contextSize);
|
||||
}
|
||||
|
||||
TraceFiber($"init fiber=0x{fiber:X16} entry=0x{entry:X16} ctx=0x{contextAddress:X16} size=0x{contextSize:X} name='{name}'");
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static int FiberRunCore(
|
||||
CpuContext ctx,
|
||||
ulong fiber,
|
||||
ulong argOnRun,
|
||||
ulong outArgumentAddress,
|
||||
ulong attachContextAddress,
|
||||
ulong attachContextSize,
|
||||
string reason,
|
||||
bool isSwitch)
|
||||
{
|
||||
if (!TryValidateFiber(ctx, fiber, out var error))
|
||||
{
|
||||
return SetReturn(ctx, error);
|
||||
}
|
||||
|
||||
if (!TryReadFiberFields(ctx, fiber, out var fields))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (attachContextAddress != 0 || attachContextSize != 0)
|
||||
{
|
||||
var attachResult = AttachContext(ctx, fiber, attachContextAddress, attachContextSize, ref fields);
|
||||
if (attachResult != 0)
|
||||
{
|
||||
return SetReturn(ctx, attachResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.State != FiberStateIdle)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorState);
|
||||
}
|
||||
|
||||
var previousFiber = _currentFiberAddress;
|
||||
var switchingFromFiber = isSwitch && previousFiber != 0 && previousFiber != fiber;
|
||||
if (isSwitch && previousFiber == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
var scheduler = GuestThreadExecution.Scheduler;
|
||||
if (scheduler is null)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
if (!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateRun))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
if (switchingFromFiber && !TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateIdle))
|
||||
{
|
||||
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle);
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
var previousReturnRequested = _fiberReturnRequested;
|
||||
var previousReturnArgument = _fiberReturnArgument;
|
||||
var session = new FiberRunSession();
|
||||
_runSessions[fiber] = session;
|
||||
_currentFiberAddress = fiber;
|
||||
_fiberReturnRequested = false;
|
||||
_fiberReturnArgument = 0;
|
||||
|
||||
var hasContinuation = _continuations.TryGetValue(fiber, out var continuation);
|
||||
bool callbackOk;
|
||||
string? callbackError;
|
||||
if (hasContinuation)
|
||||
{
|
||||
if (continuation.ArgOnRunAddress != 0 &&
|
||||
!TryWriteUInt64(ctx, continuation.ArgOnRunAddress, argOnRun))
|
||||
{
|
||||
callbackOk = false;
|
||||
callbackError = $"failed to write resumed argOnRun to 0x{continuation.ArgOnRunAddress:X16}";
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackOk = scheduler.TryCallGuestContinuation(
|
||||
ctx,
|
||||
continuation.Context,
|
||||
reason,
|
||||
out callbackError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackOk = scheduler.TryCallGuestFunction(
|
||||
ctx,
|
||||
fields.Entry,
|
||||
fields.ArgOnInitialize,
|
||||
argOnRun,
|
||||
fields.ContextAddress,
|
||||
fields.ContextSize,
|
||||
reason,
|
||||
out callbackError);
|
||||
}
|
||||
|
||||
var returnRequested = _fiberReturnRequested;
|
||||
var returnArgument = _fiberReturnArgument;
|
||||
if (!returnRequested && session.TryGetReturn(out var sessionReturnArgument))
|
||||
{
|
||||
returnRequested = true;
|
||||
returnArgument = sessionReturnArgument;
|
||||
}
|
||||
|
||||
if (!returnRequested)
|
||||
{
|
||||
_continuations.TryRemove(fiber, out _);
|
||||
}
|
||||
_runSessions.TryRemove(fiber, out _);
|
||||
|
||||
_currentFiberAddress = previousFiber;
|
||||
_fiberReturnRequested = previousReturnRequested;
|
||||
_fiberReturnArgument = previousReturnArgument;
|
||||
|
||||
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle);
|
||||
if (switchingFromFiber)
|
||||
{
|
||||
_ = TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateRun);
|
||||
}
|
||||
|
||||
if (!callbackOk)
|
||||
{
|
||||
TraceFiber($"run-failed fiber=0x{fiber:X16} entry=0x{fields.Entry:X16} error={callbackError}");
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP);
|
||||
}
|
||||
|
||||
if (outArgumentAddress != 0 && !TryWriteUInt64(ctx, outArgumentAddress, returnArgument))
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
||||
TraceFiber($"run fiber=0x{fiber:X16} entry=0x{fields.Entry:X16} resume={hasContinuation} arg=0x{argOnRun:X16} ret=0x{returnArgument:X16}");
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static GuestCpuContinuation CaptureContinuation(CpuContext ctx, ulong resumeRip, ulong resumeRsp) =>
|
||||
new(
|
||||
resumeRip,
|
||||
resumeRsp,
|
||||
ctx.Rflags == 0 ? 0x202UL : ctx.Rflags,
|
||||
ctx.FsBase,
|
||||
ctx.GsBase,
|
||||
0,
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.Rdx],
|
||||
ctx[CpuRegister.Rbx],
|
||||
ctx[CpuRegister.Rbp],
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdi],
|
||||
ctx[CpuRegister.R8],
|
||||
ctx[CpuRegister.R9],
|
||||
ctx[CpuRegister.R12],
|
||||
ctx[CpuRegister.R13],
|
||||
ctx[CpuRegister.R14],
|
||||
ctx[CpuRegister.R15]);
|
||||
|
||||
private static int AttachContext(
|
||||
CpuContext ctx,
|
||||
ulong fiber,
|
||||
ulong contextAddress,
|
||||
ulong contextSize,
|
||||
ref FiberFields fields)
|
||||
{
|
||||
if ((contextAddress & 15) != 0)
|
||||
{
|
||||
return FiberErrorAlignment;
|
||||
}
|
||||
|
||||
if (contextSize != 0 && contextSize < FiberContextMinimumSize)
|
||||
{
|
||||
return FiberErrorRange;
|
||||
}
|
||||
|
||||
if ((contextSize & 15) != 0 ||
|
||||
contextAddress == 0 ||
|
||||
contextSize == 0 ||
|
||||
fields.ContextAddress != 0)
|
||||
{
|
||||
return FiberErrorInvalid;
|
||||
}
|
||||
|
||||
if (!TryWriteUInt64(ctx, fiber + FiberContextAddressOffset, contextAddress) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextSizeOffset, contextSize) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextStartOffset, contextAddress) ||
|
||||
!TryWriteUInt64(ctx, fiber + FiberContextEndOffset, contextAddress + contextSize) ||
|
||||
!TryWriteUInt64(ctx, contextAddress, FiberStackSignature))
|
||||
{
|
||||
return FiberErrorInvalid;
|
||||
}
|
||||
|
||||
fields = fields with
|
||||
{
|
||||
ContextAddress = contextAddress,
|
||||
ContextSize = contextSize,
|
||||
};
|
||||
_stackRanges[fiber] = new FiberStackRange(contextAddress, contextSize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool TryFindFiberByStack(CpuContext ctx, out ulong fiber)
|
||||
{
|
||||
if (TryFindFiberByStackAddress(ctx[CpuRegister.Rsp], out fiber))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryFindFiberByStackAddress(ctx[CpuRegister.Rbp], out fiber);
|
||||
}
|
||||
|
||||
private static bool TryFindFiberByStackAddress(ulong address, out ulong fiber)
|
||||
{
|
||||
if (address != 0)
|
||||
{
|
||||
foreach (var (candidate, range) in _stackRanges)
|
||||
{
|
||||
if (range.Contains(address))
|
||||
{
|
||||
fiber = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fiber = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryValidateFiber(CpuContext ctx, ulong fiber, out int error)
|
||||
{
|
||||
if (fiber == 0)
|
||||
{
|
||||
error = FiberErrorNull;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((fiber & 7) != 0)
|
||||
{
|
||||
error = FiberErrorAlignment;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryReadUInt32(ctx, fiber + FiberMagicStartOffset, out var magicStart) ||
|
||||
!TryReadUInt32(ctx, fiber + FiberMagicEndOffset, out var magicEnd) ||
|
||||
magicStart != FiberSignature0 ||
|
||||
magicEnd != FiberSignature1)
|
||||
{
|
||||
error = FiberErrorInvalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadFiberFields(CpuContext ctx, ulong fiber, out FiberFields fields)
|
||||
{
|
||||
fields = default;
|
||||
if (!TryReadUInt32(ctx, fiber + FiberStateOffset, out var state) ||
|
||||
!TryReadUInt64(ctx, fiber + FiberEntryOffset, out var entry) ||
|
||||
!TryReadUInt64(ctx, fiber + FiberArgOnInitializeOffset, out var argOnInitialize) ||
|
||||
!TryReadUInt64(ctx, fiber + FiberContextAddressOffset, out var contextAddress) ||
|
||||
!TryReadUInt64(ctx, fiber + FiberContextSizeOffset, out var contextSize) ||
|
||||
!TryReadUInt32(ctx, fiber + FiberFlagsOffset, out var flags) ||
|
||||
!TryReadInlineName(ctx, fiber + FiberNameOffset, out var name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fields = new FiberFields(
|
||||
state,
|
||||
entry,
|
||||
argOnInitialize,
|
||||
contextAddress,
|
||||
contextSize,
|
||||
flags,
|
||||
name);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void FillContextSizeCheck(CpuContext ctx, ulong address, ulong size)
|
||||
{
|
||||
Span<byte> value = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(value, FiberStackSizeCheck);
|
||||
var end = address + size;
|
||||
for (var current = address; current + sizeof(ulong) <= end; current += sizeof(ulong))
|
||||
{
|
||||
_ = ctx.Memory.TryWrite(current, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ReadStackArg64(CpuContext ctx, int index)
|
||||
{
|
||||
if (ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong) + ((ulong)index * sizeof(ulong)), out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt64(CpuContext ctx, ulong address, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryWriteName(CpuContext ctx, ulong address, string name)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[MaxNameLength + 1];
|
||||
var bytes = Encoding.UTF8.GetBytes(name);
|
||||
bytes.AsSpan(0, Math.Min(bytes.Length, MaxNameLength)).CopyTo(buffer);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryReadInlineName(CpuContext ctx, ulong address, out string value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[MaxNameLength + 1];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = buffer.IndexOf((byte)0);
|
||||
if (length < 0)
|
||||
{
|
||||
length = buffer.Length;
|
||||
}
|
||||
|
||||
value = Encoding.UTF8.GetString(buffer[..length]);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int capacity, out string value)
|
||||
{
|
||||
var bytes = new byte[capacity];
|
||||
for (var index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
Span<byte> current = stackalloc byte[1];
|
||||
if (!ctx.Memory.TryRead(address + (ulong)index, current))
|
||||
{
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current[0] == 0)
|
||||
{
|
||||
value = Encoding.UTF8.GetString(bytes, 0, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes[index] = current[0];
|
||||
}
|
||||
|
||||
value = Encoding.UTF8.GetString(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceFiber(string message)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] fiber.{message}");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct FiberFields(
|
||||
uint State,
|
||||
ulong Entry,
|
||||
ulong ArgOnInitialize,
|
||||
ulong ContextAddress,
|
||||
ulong ContextSize,
|
||||
uint Flags,
|
||||
string Name);
|
||||
|
||||
private readonly record struct FiberContinuation(
|
||||
GuestCpuContinuation Context,
|
||||
ulong ArgOnRunAddress);
|
||||
|
||||
private sealed class FiberRunSession
|
||||
{
|
||||
private int _returnRequested;
|
||||
|
||||
private ulong _returnArgument;
|
||||
|
||||
public void SetReturn(ulong argument)
|
||||
{
|
||||
_returnArgument = argument;
|
||||
Volatile.Write(ref _returnRequested, 1);
|
||||
}
|
||||
|
||||
public bool TryGetReturn(out ulong argument)
|
||||
{
|
||||
if (Volatile.Read(ref _returnRequested) == 0)
|
||||
{
|
||||
argument = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
argument = _returnArgument;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct FiberStackRange(ulong Start, ulong Size)
|
||||
{
|
||||
public bool Contains(ulong address) =>
|
||||
Size != 0 && address >= Start && address < Start + Size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.GameUpdate;
|
||||
|
||||
public static class GameUpdateExports
|
||||
{
|
||||
private static int _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YJtKLttI9fM",
|
||||
ExportName = "sceGameUpdateInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceGameUpdate")]
|
||||
public static int GameUpdateInitialize(CpuContext ctx)
|
||||
{
|
||||
Interlocked.Exchange(ref _initialized, 1);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Json;
|
||||
|
||||
public static class JsonExports
|
||||
{
|
||||
[SysAbiExport(
|
||||
Nid = "-hJRce8wn1U",
|
||||
ExportName = "_ZN3sce4Json12MemAllocatorC2Ev",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceJson")]
|
||||
public static int MemAllocatorConstructor(CpuContext ctx)
|
||||
{
|
||||
var thisAddress = ctx[CpuRegister.Rdi];
|
||||
TraceJson("MemAllocator.ctor", thisAddress, 0);
|
||||
ctx[CpuRegister.Rax] = thisAddress;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "OcAgPxcq5Vk",
|
||||
ExportName = "_ZN3sce4Json12MemAllocatorD2Ev",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceJson")]
|
||||
public static int MemAllocatorDestructor(CpuContext ctx)
|
||||
{
|
||||
TraceJson("MemAllocator.dtor", ctx[CpuRegister.Rdi], 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "cK6bYHf-Q5E",
|
||||
ExportName = "_ZN3sce4Json11InitializerC1Ev",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceJson")]
|
||||
public static int InitializerConstructor(CpuContext ctx)
|
||||
{
|
||||
var thisAddress = ctx[CpuRegister.Rdi];
|
||||
TraceJson("Initializer.ctor", thisAddress, 0);
|
||||
ctx[CpuRegister.Rax] = thisAddress;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "RujUxbr3haM",
|
||||
ExportName = "_ZN3sce4Json11InitializerD1Ev",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceJson")]
|
||||
public static int InitializerDestructor(CpuContext ctx)
|
||||
{
|
||||
TraceJson("Initializer.dtor", ctx[CpuRegister.Rdi], 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Cxwy7wHq4J0",
|
||||
ExportName = "_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceJson")]
|
||||
public static int InitializerInitialize(CpuContext ctx)
|
||||
{
|
||||
var thisAddress = ctx[CpuRegister.Rdi];
|
||||
var initParameterAddress = ctx[CpuRegister.Rsi];
|
||||
if (thisAddress == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
TraceJson("Initializer.initialize", thisAddress, initParameterAddress);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static void TraceJson(string operation, ulong thisAddress, ulong argument)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_JSON"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] json.{operation} this=0x{thisAddress:X16} arg=0x{argument:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelEventFlagCompatExports
|
||||
{
|
||||
private const int MaxEventFlagNameLength = 31;
|
||||
private const int HostWaitPumpMilliseconds = 1;
|
||||
private const uint AttrThreadFifo = 0x01;
|
||||
private const uint AttrThreadPriority = 0x02;
|
||||
private const uint AttrSingle = 0x10;
|
||||
private const uint AttrMulti = 0x20;
|
||||
private const uint WaitAnd = 0x01;
|
||||
private const uint WaitOr = 0x02;
|
||||
private const uint ClearAll = 0x10;
|
||||
private const uint ClearPattern = 0x20;
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
|
||||
private static long _nextEventFlagHandle = 1;
|
||||
|
||||
private sealed class EventFlagState
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required uint Attributes { get; init; }
|
||||
public ulong Bits { get; set; }
|
||||
public int WaitingThreads { get; set; }
|
||||
public object Gate { get; } = new();
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "BpFoboUJoZU",
|
||||
ExportName = "sceKernelCreateEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCreateEventFlag(CpuContext ctx)
|
||||
{
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
var nameAddress = ctx[CpuRegister.Rsi];
|
||||
var attributes = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var initialPattern = ctx[CpuRegister.Rcx];
|
||||
var optionAddress = ctx[CpuRegister.R8];
|
||||
|
||||
if (outAddress == 0 ||
|
||||
nameAddress == 0 ||
|
||||
optionAddress != 0 ||
|
||||
!IsValidAttributes(attributes))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxEventFlagNameLength + 1, out var name))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (Encoding.UTF8.GetByteCount(name) > MaxEventFlagNameLength)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
var handle = unchecked((ulong)Interlocked.Increment(ref _nextEventFlagHandle));
|
||||
_eventFlags[handle] = new EventFlagState
|
||||
{
|
||||
Name = name,
|
||||
Attributes = attributes,
|
||||
Bits = initialPattern,
|
||||
};
|
||||
|
||||
if (!ctx.TryWriteUInt64(outAddress, handle))
|
||||
{
|
||||
_eventFlags.TryRemove(handle, out _);
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceEventFlag($"create handle=0x{handle:X16} name='{name}' attr=0x{attributes:X2} bits=0x{initialPattern:X16}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "8mql9OcQnd4",
|
||||
ExportName = "sceKernelDeleteEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
if (!_eventFlags.TryRemove(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
lock (state.Gate)
|
||||
{
|
||||
Monitor.PulseAll(state.Gate);
|
||||
}
|
||||
|
||||
TraceEventFlag($"delete handle=0x{handle:X16} name='{state.Name}'");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "IOnSvHzqu6A",
|
||||
ExportName = "sceKernelSetEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelSetEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var pattern = ctx[CpuRegister.Rsi];
|
||||
if (!_eventFlags.TryGetValue(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
lock (state.Gate)
|
||||
{
|
||||
state.Bits |= pattern;
|
||||
Monitor.PulseAll(state.Gate);
|
||||
TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16}");
|
||||
}
|
||||
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "7uhBFWRAS60",
|
||||
ExportName = "sceKernelClearEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelClearEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var pattern = ctx[CpuRegister.Rsi];
|
||||
if (!_eventFlags.TryGetValue(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
lock (state.Gate)
|
||||
{
|
||||
state.Bits &= pattern;
|
||||
TraceEventFlag($"clear handle=0x{handle:X16} mask=0x{pattern:X16} bits=0x{state.Bits:X16}");
|
||||
}
|
||||
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "9lvj5DjHZiA",
|
||||
ExportName = "sceKernelPollEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelPollEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var pattern = ctx[CpuRegister.Rsi];
|
||||
var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var resultAddress = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (!_eventFlags.TryGetValue(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (pattern == 0 || !IsValidWaitMode(waitMode))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
lock (state.Gate)
|
||||
{
|
||||
if (!TryWriteResultPattern(ctx, resultAddress, state.Bits))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!IsSatisfied(state.Bits, pattern, waitMode))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
|
||||
}
|
||||
|
||||
ApplyClearMode(state, pattern, waitMode);
|
||||
TraceEventFlag($"poll handle=0x{handle:X16} pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JTvBflhYazQ",
|
||||
ExportName = "sceKernelWaitEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelWaitEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var pattern = ctx[CpuRegister.Rsi];
|
||||
var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var resultAddress = ctx[CpuRegister.Rcx];
|
||||
var timeoutAddress = ctx[CpuRegister.R8];
|
||||
|
||||
if (!_eventFlags.TryGetValue(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (pattern == 0 || !IsValidWaitMode(waitMode))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
uint timeoutUsec = 0;
|
||||
if (timeoutAddress != 0 && !TryReadUInt32(ctx, timeoutAddress, out timeoutUsec))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
Monitor.Enter(state.Gate);
|
||||
try
|
||||
{
|
||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var immediateWaitResult))
|
||||
{
|
||||
return SetReturn(ctx, immediateWaitResult);
|
||||
}
|
||||
|
||||
if (timeoutAddress != 0)
|
||||
{
|
||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
|
||||
TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} timeout={timeoutUsec}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||
}
|
||||
|
||||
if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEventFlag"))
|
||||
{
|
||||
var scheduler = GuestThreadExecution.Scheduler;
|
||||
if (scheduler is null)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||
}
|
||||
|
||||
state.WaitingThreads++;
|
||||
TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}");
|
||||
var releaseWaiter = true;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Monitor.Exit(state.Gate);
|
||||
try
|
||||
{
|
||||
scheduler.Pump(ctx, "sceKernelWaitEventFlag");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Enter(state.Gate);
|
||||
}
|
||||
|
||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var pumpedWaitResult))
|
||||
{
|
||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||
releaseWaiter = false;
|
||||
TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}");
|
||||
return SetReturn(ctx, pumpedWaitResult);
|
||||
}
|
||||
|
||||
Monitor.Wait(state.Gate, HostWaitPumpMilliseconds);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (releaseWaiter)
|
||||
{
|
||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.WaitingThreads++;
|
||||
TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(state.Gate);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "PZku4ZrXJqg",
|
||||
ExportName = "sceKernelCancelEventFlag",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCancelEventFlag(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var setPattern = ctx[CpuRegister.Rsi];
|
||||
var waiterCountAddress = ctx[CpuRegister.Rdx];
|
||||
if (!_eventFlags.TryGetValue(handle, out var state))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
lock (state.Gate)
|
||||
{
|
||||
if (waiterCountAddress != 0 &&
|
||||
!TryWriteUInt32(ctx, waiterCountAddress, unchecked((uint)state.WaitingThreads)))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
state.Bits = setPattern;
|
||||
state.WaitingThreads = 0;
|
||||
Monitor.PulseAll(state.Gate);
|
||||
TraceEventFlag($"cancel handle=0x{handle:X16} bits=0x{setPattern:X16}");
|
||||
}
|
||||
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
private static bool IsValidAttributes(uint attributes)
|
||||
{
|
||||
var queueMode = attributes & 0x0F;
|
||||
var threadMode = attributes & 0xF0;
|
||||
return (queueMode is 0 or AttrThreadFifo or AttrThreadPriority) &&
|
||||
(threadMode is 0 or AttrSingle or AttrMulti) &&
|
||||
(attributes & ~0x33u) == 0;
|
||||
}
|
||||
|
||||
private static bool IsValidWaitMode(uint waitMode)
|
||||
{
|
||||
var condition = waitMode & 0x0F;
|
||||
var clearMode = waitMode & 0xF0;
|
||||
return condition is WaitAnd or WaitOr &&
|
||||
clearMode is 0 or ClearAll or ClearPattern &&
|
||||
(waitMode & ~0x33u) == 0;
|
||||
}
|
||||
|
||||
private static bool IsSatisfied(ulong bits, ulong pattern, uint waitMode) =>
|
||||
(waitMode & 0x0F) == WaitAnd
|
||||
? (bits & pattern) == pattern
|
||||
: (bits & pattern) != 0;
|
||||
|
||||
private static void ApplyClearMode(EventFlagState state, ulong pattern, uint waitMode)
|
||||
{
|
||||
switch (waitMode & 0xF0)
|
||||
{
|
||||
case ClearAll:
|
||||
state.Bits = 0;
|
||||
break;
|
||||
case ClearPattern:
|
||||
state.Bits &= ~pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCompleteSatisfiedWait(
|
||||
CpuContext ctx,
|
||||
EventFlagState state,
|
||||
ulong pattern,
|
||||
uint waitMode,
|
||||
out OrbisGen2Result result)
|
||||
{
|
||||
result = OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
if (!IsSatisfied(state.Bits, pattern, waitMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryWriteResultPattern(ctx, ctx[CpuRegister.Rcx], state.Bits))
|
||||
{
|
||||
result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
return true;
|
||||
}
|
||||
|
||||
ApplyClearMode(state, pattern, waitMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) =>
|
||||
address == 0 || ctx.TryWriteUInt64(address, bits);
|
||||
|
||||
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int capacity, out string value)
|
||||
{
|
||||
var bytes = new byte[capacity];
|
||||
for (var index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
Span<byte> current = stackalloc byte[1];
|
||||
if (!ctx.Memory.TryRead(address + (ulong)index, current))
|
||||
{
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current[0] == 0)
|
||||
{
|
||||
value = Encoding.UTF8.GetString(bytes, 0, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes[index] = current[0];
|
||||
}
|
||||
|
||||
value = Encoding.UTF8.GetString(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
|
||||
{
|
||||
var value = (int)result;
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void TraceEventFlag(string message)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public static class KernelEventQueueCompatExports
|
||||
|
||||
private static readonly object _eventQueueGate = new();
|
||||
private static readonly HashSet<ulong> _eventQueues = new();
|
||||
private static readonly Dictionary<ulong, Queue<KernelQueuedEvent>> _pendingEvents = new();
|
||||
private static readonly Dictionary<ulong, LinkedList<KernelQueuedEvent>> _pendingEvents = new();
|
||||
private static long _nextEventQueueHandle = 1;
|
||||
|
||||
public readonly record struct KernelQueuedEvent(
|
||||
@@ -41,7 +41,7 @@ public static class KernelEventQueueCompatExports
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
_eventQueues.Add(handle);
|
||||
_pendingEvents[handle] = new Queue<KernelQueuedEvent>();
|
||||
_pendingEvents[handle] = new LinkedList<KernelQueuedEvent>();
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(outAddress, handle))
|
||||
@@ -225,11 +225,70 @@ public static class KernelEventQueueCompatExports
|
||||
|
||||
if (!_pendingEvents.TryGetValue(handle, out var queue))
|
||||
{
|
||||
queue = new Queue<KernelQueuedEvent>();
|
||||
queue = new LinkedList<KernelQueuedEvent>();
|
||||
_pendingEvents[handle] = queue;
|
||||
}
|
||||
|
||||
queue.Enqueue(queuedEvent);
|
||||
queue.AddLast(queuedEvent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TriggerDisplayEvent(
|
||||
ulong handle,
|
||||
ulong ident,
|
||||
short filter,
|
||||
ulong eventHint,
|
||||
ulong userData)
|
||||
{
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
if (!_eventQueues.Contains(handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_pendingEvents.TryGetValue(handle, out var events))
|
||||
{
|
||||
events = new LinkedList<KernelQueuedEvent>();
|
||||
_pendingEvents[handle] = events;
|
||||
}
|
||||
|
||||
LinkedListNode<KernelQueuedEvent>? pendingNode = null;
|
||||
for (var node = events.First; node is not null; node = node.Next)
|
||||
{
|
||||
if (node.Value.Ident == ident && node.Value.Filter == filter)
|
||||
{
|
||||
pendingNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var count = 1UL;
|
||||
if (pendingNode is not null)
|
||||
{
|
||||
count = Math.Min(((pendingNode.Value.Data >> 12) & 0xFUL) + 1, 0xFUL);
|
||||
}
|
||||
|
||||
var timeBits = unchecked((ulong)Environment.TickCount64) & 0xFFFUL;
|
||||
var eventData = timeBits | (count << 12) | (eventHint & 0xFFFF_FFFF_FFFF_0000UL);
|
||||
var triggeredEvent = new KernelQueuedEvent(
|
||||
ident,
|
||||
filter,
|
||||
0x20,
|
||||
0,
|
||||
eventData,
|
||||
userData);
|
||||
|
||||
if (pendingNode is not null)
|
||||
{
|
||||
pendingNode.Value = triggeredEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
events.AddLast(triggeredEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -253,7 +312,8 @@ public static class KernelEventQueueCompatExports
|
||||
events = new KernelQueuedEvent[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
events[i] = queue.Dequeue();
|
||||
events[i] = queue.First!.Value;
|
||||
queue.RemoveFirst();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,13 @@ public static class KernelMemoryCompatExports
|
||||
private const int OrbisKernelBatchMapEntryProtectionOffset = 24;
|
||||
private const int OrbisKernelBatchMapEntryTypeOffset = 25;
|
||||
private const int OrbisKernelBatchMapEntryOperationOffset = 28;
|
||||
private const ulong OrbisPageSize = 0x4000;
|
||||
private const int OrbisProtCpuRead = 0x01;
|
||||
private const int OrbisProtCpuWrite = 0x02;
|
||||
private const int OrbisProtCpuExec = 0x04;
|
||||
private const int OrbisProtGpuRead = 0x10;
|
||||
private const int OrbisProtGpuWrite = 0x20;
|
||||
private const int OrbisProtCpuReadWrite = OrbisProtCpuRead | OrbisProtCpuWrite;
|
||||
private const int SeekSet = 0;
|
||||
private const int SeekCur = 1;
|
||||
private const int SeekEnd = 2;
|
||||
@@ -47,14 +54,15 @@ public static class KernelMemoryCompatExports
|
||||
private const int OrbisVirtualQueryInfoSize = 72;
|
||||
private const int OrbisKernelMaximumNameLength = 32;
|
||||
private const uint MemCommit = 0x1000;
|
||||
private const uint PageNoAccess = 0x01;
|
||||
private const uint PageReadOnly = 0x02;
|
||||
private const uint PageReadWrite = 0x04;
|
||||
private const uint PageWriteCopy = 0x08;
|
||||
private const uint PageExecuteRead = 0x20;
|
||||
private const uint PageExecuteReadWrite = 0x40;
|
||||
private const uint PageExecuteWriteCopy = 0x80;
|
||||
private const uint PageGuard = 0x100;
|
||||
private const uint HostPageNoAccess = 0x01;
|
||||
private const uint HostPageReadOnly = 0x02;
|
||||
private const uint HostPageReadWrite = 0x04;
|
||||
private const uint HostPageWriteCopy = 0x08;
|
||||
private const uint HostPageExecute = 0x10;
|
||||
private const uint HostPageExecuteRead = 0x20;
|
||||
private const uint HostPageExecuteReadWrite = 0x40;
|
||||
private const uint HostPageExecuteWriteCopy = 0x80;
|
||||
private const uint HostPageGuard = 0x100;
|
||||
private const int Enomem = 12;
|
||||
private const int Einval = 22;
|
||||
private const int Erange = 34;
|
||||
@@ -121,6 +129,10 @@ public static class KernelMemoryCompatExports
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern nuint VirtualQuery(nint lpAddress, out MemoryBasicInformation lpBuffer, nuint dwLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool VirtualProtect(nint lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
|
||||
|
||||
private sealed class OpenDirectory
|
||||
{
|
||||
public required string Path { get; init; }
|
||||
@@ -130,9 +142,59 @@ public static class KernelMemoryCompatExports
|
||||
|
||||
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType);
|
||||
private readonly record struct LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment);
|
||||
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
|
||||
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
|
||||
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
|
||||
|
||||
internal static bool TryAllocateHleData(
|
||||
CpuContext ctx,
|
||||
ulong length,
|
||||
ulong alignment,
|
||||
out ulong address)
|
||||
{
|
||||
address = 0;
|
||||
if (length == 0 || length > int.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mappedLength = AlignUp(length, 0x1000UL);
|
||||
var effectiveAlignment = Math.Max(alignment, 0x1000UL);
|
||||
lock (_memoryGate)
|
||||
{
|
||||
var desiredAddress = AlignUp(
|
||||
_nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress,
|
||||
effectiveAlignment);
|
||||
if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, out address) ||
|
||||
address == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_nextVirtualAddress = Math.Max(_nextVirtualAddress, address + mappedLength);
|
||||
_mappedRegions[address] = new MappedRegion(
|
||||
address,
|
||||
mappedLength,
|
||||
OrbisProtCpuReadWrite,
|
||||
IsFlexible: false,
|
||||
IsDirect: false,
|
||||
DirectStart: 0);
|
||||
}
|
||||
|
||||
var zeroes = new byte[(int)Math.Min(mappedLength, (ulong)MemsetChunkSize)];
|
||||
for (ulong offset = 0; offset < mappedLength;)
|
||||
{
|
||||
var chunkLength = (int)Math.Min((ulong)zeroes.Length, mappedLength - offset);
|
||||
if (!ctx.Memory.TryWrite(address + offset, zeroes.AsSpan(0, chunkLength)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += (ulong)chunkLength;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "8zTFvBIAIN8",
|
||||
ExportName = "memset",
|
||||
@@ -1238,6 +1300,13 @@ public static class KernelMemoryCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCloseUnderscore(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bY-PO6JhzhQ",
|
||||
ExportName = "close",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixClose(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "UK2Tl2DWUns",
|
||||
ExportName = "sceKernelClose",
|
||||
@@ -1444,6 +1513,20 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "AqBioC2vF3I",
|
||||
ExportName = "read",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixRead(CpuContext ctx) => KernelReadUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Cg4srZ6TKbU",
|
||||
ExportName = "sceKernelRead",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelRead(CpuContext ctx) => KernelReadUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Oy6IpwgtYOk",
|
||||
ExportName = "lseek",
|
||||
@@ -1629,6 +1712,20 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "FN4gaPmuFV8",
|
||||
ExportName = "write",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixWrite(CpuContext ctx) => KernelWriteUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4wSze92BhLI",
|
||||
ExportName = "sceKernelWrite",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelWrite(CpuContext ctx) => KernelWriteUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "lLMT9vJAck0",
|
||||
ExportName = "clock_gettime",
|
||||
@@ -2172,7 +2269,13 @@ public static class KernelMemoryCompatExports
|
||||
}
|
||||
|
||||
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
|
||||
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: false, DirectStart: directMemoryStart);
|
||||
_mappedRegions[mappedAddress] = new MappedRegion(
|
||||
mappedAddress,
|
||||
length,
|
||||
protection,
|
||||
IsFlexible: false,
|
||||
IsDirect: true,
|
||||
DirectStart: directMemoryStart);
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
|
||||
@@ -2245,7 +2348,13 @@ public static class KernelMemoryCompatExports
|
||||
|
||||
_nextVirtualAddress = Math.Max(_nextVirtualAddress, mappedAddress + length);
|
||||
_allocatedFlexibleBytes = Math.Min(FlexibleMemorySizeBytes, _allocatedFlexibleBytes + length);
|
||||
_mappedRegions[mappedAddress] = new MappedRegion(mappedAddress, length, protection, IsFlexible: true, DirectStart: 0);
|
||||
_mappedRegions[mappedAddress] = new MappedRegion(
|
||||
mappedAddress,
|
||||
length,
|
||||
protection,
|
||||
IsFlexible: true,
|
||||
IsDirect: false,
|
||||
DirectStart: 0);
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
|
||||
@@ -2344,8 +2453,7 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (region.DirectStart != 0 &&
|
||||
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
|
||||
if (region.IsDirect && TryFindDirectAllocationLocked(region.DirectStart, out var allocation))
|
||||
{
|
||||
memoryType = allocation.MemoryType;
|
||||
}
|
||||
@@ -2363,7 +2471,7 @@ public static class KernelMemoryCompatExports
|
||||
stateFlags |= 0x01u;
|
||||
}
|
||||
|
||||
if (region.DirectStart != 0)
|
||||
if (region.IsDirect)
|
||||
{
|
||||
stateFlags |= 0x02u;
|
||||
}
|
||||
@@ -2377,7 +2485,7 @@ public static class KernelMemoryCompatExports
|
||||
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
|
||||
payload[32] = unchecked((byte)stateFlags);
|
||||
|
||||
var name = region.DirectStart != 0
|
||||
var name = region.IsDirect
|
||||
? "direct"
|
||||
: region.IsFlexible
|
||||
? "flexible"
|
||||
@@ -2495,12 +2603,19 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryNormalizeProtectRange(address, length, out var alignedAddress, out var alignedLength))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryProtectHostRange(alignedAddress, alignedLength, protection))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
lock (_memoryGate)
|
||||
{
|
||||
if (!TryApplyMappedRegionProtectionLocked(address, length, protection))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
_ = TryApplyMappedRegionProtectionLocked(alignedAddress, alignedLength, protection);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -2522,12 +2637,19 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryNormalizeProtectRange(address, length, out var alignedAddress, out var alignedLength))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryProtectHostRange(alignedAddress, alignedLength, protection))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
lock (_memoryGate)
|
||||
{
|
||||
if (!TryApplyMappedRegionProtectionLocked(address, length, protection, memoryType))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
_ = TryApplyMappedRegionProtectionLocked(alignedAddress, alignedLength, protection, memoryType);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -3562,7 +3684,7 @@ public static class KernelMemoryCompatExports
|
||||
return false;
|
||||
}
|
||||
|
||||
var executable = (protection & 0x04) != 0;
|
||||
var executable = (protection & OrbisProtCpuExec) != 0;
|
||||
var invokeArgs = allocateAtHasAllowAlternativeArg
|
||||
? new object[] { desiredAddress, length, executable, true }
|
||||
: new object[] { desiredAddress, length, executable };
|
||||
@@ -3692,6 +3814,25 @@ public static class KernelMemoryCompatExports
|
||||
return temp0Root;
|
||||
}
|
||||
|
||||
var hostappRoot = ResolveHostappRoot();
|
||||
if (guestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]);
|
||||
return Path.Combine(hostappRoot, relative);
|
||||
}
|
||||
|
||||
if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]);
|
||||
return Path.Combine(hostappRoot, relative);
|
||||
}
|
||||
|
||||
if (string.Equals(guestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(guestPath, "hostapp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return hostappRoot;
|
||||
}
|
||||
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
if (!string.IsNullOrWhiteSpace(app0Root))
|
||||
{
|
||||
@@ -3775,6 +3916,25 @@ public static class KernelMemoryCompatExports
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string ResolveHostappRoot()
|
||||
{
|
||||
const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR";
|
||||
var configuredRoot = Environment.GetEnvironmentVariable(hostappVariableName);
|
||||
string root;
|
||||
if (!string.IsNullOrWhiteSpace(configuredRoot))
|
||||
{
|
||||
root = Path.GetFullPath(configuredRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "hostapp"));
|
||||
Environment.SetEnvironmentVariable(hostappVariableName, root);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hostPath))
|
||||
@@ -4336,23 +4496,171 @@ public static class KernelMemoryCompatExports
|
||||
int protection,
|
||||
int? memoryType = null)
|
||||
{
|
||||
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length)
|
||||
if (length == 0 || !TryAddU64(address, length, out var endAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_mappedRegions[address] = region with { Protection = protection };
|
||||
|
||||
if (memoryType.HasValue &&
|
||||
region.DirectStart != 0 &&
|
||||
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
|
||||
var affected = new List<MappedRegion>();
|
||||
var cursor = address;
|
||||
foreach (var region in _mappedRegions.Values.OrderBy(static region => region.Address))
|
||||
{
|
||||
_directAllocations[region.DirectStart] = allocation with { MemoryType = memoryType.Value };
|
||||
if (!TryAddU64(region.Address, region.Length, out var regionEnd) || regionEnd <= cursor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (region.Address > cursor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
affected.Add(region);
|
||||
cursor = regionEnd >= endAddress ? endAddress : regionEnd;
|
||||
if (cursor == endAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor != endAddress)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var region in affected)
|
||||
{
|
||||
_mappedRegions.Remove(region.Address);
|
||||
}
|
||||
|
||||
foreach (var region in affected)
|
||||
{
|
||||
var regionEnd = region.Address + region.Length;
|
||||
var protectStart = Math.Max(region.Address, address);
|
||||
var protectEnd = Math.Min(regionEnd, endAddress);
|
||||
|
||||
if (region.Address < protectStart)
|
||||
{
|
||||
AddMappedRegionSliceLocked(region, region.Address, protectStart, region.Protection);
|
||||
}
|
||||
|
||||
AddMappedRegionSliceLocked(region, protectStart, protectEnd, protection);
|
||||
|
||||
if (protectEnd < regionEnd)
|
||||
{
|
||||
AddMappedRegionSliceLocked(region, protectEnd, regionEnd, region.Protection);
|
||||
}
|
||||
|
||||
if (memoryType.HasValue && region.IsDirect && TryFindDirectAllocationLocked(region.DirectStart, out var allocation))
|
||||
{
|
||||
_directAllocations[allocation.Start] = allocation with { MemoryType = memoryType.Value };
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void AddMappedRegionSliceLocked(
|
||||
MappedRegion source,
|
||||
ulong start,
|
||||
ulong end,
|
||||
int protection)
|
||||
{
|
||||
if (end <= start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directStart = source.IsDirect
|
||||
? unchecked(source.DirectStart + (start - source.Address))
|
||||
: 0UL;
|
||||
|
||||
_mappedRegions[start] = source with
|
||||
{
|
||||
Address = start,
|
||||
Length = end - start,
|
||||
Protection = protection,
|
||||
DirectStart = directStart,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryFindDirectAllocationLocked(ulong directStart, out DirectAllocation allocation)
|
||||
{
|
||||
foreach (var candidate in _directAllocations.Values)
|
||||
{
|
||||
if (!TryAddU64(candidate.Start, candidate.Length, out var candidateEnd))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (directStart >= candidate.Start && directStart < candidateEnd)
|
||||
{
|
||||
allocation = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
allocation = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryNormalizeProtectRange(
|
||||
ulong address,
|
||||
ulong length,
|
||||
out ulong alignedAddress,
|
||||
out ulong alignedLength)
|
||||
{
|
||||
alignedAddress = 0;
|
||||
alignedLength = 0;
|
||||
if (length == 0 || !TryAddU64(address, length, out var endAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
alignedAddress = AlignDown(address, OrbisPageSize);
|
||||
var alignedEnd = AlignUp(endAddress, OrbisPageSize);
|
||||
alignedLength = alignedEnd - alignedAddress;
|
||||
return alignedLength != 0;
|
||||
}
|
||||
|
||||
private static bool TryProtectHostRange(ulong address, ulong length, int orbisProtection)
|
||||
{
|
||||
if (length == 0 || length > nuint.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostProtection = ResolveHostProtection(orbisProtection);
|
||||
if (!VirtualProtect((nint)address, (nuint)length, hostProtection, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static uint ResolveHostProtection(int orbisProtection)
|
||||
{
|
||||
var read = (orbisProtection & (OrbisProtCpuRead | OrbisProtGpuRead)) != 0;
|
||||
var write = (orbisProtection & (OrbisProtCpuWrite | OrbisProtGpuWrite)) != 0;
|
||||
var execute = (orbisProtection & OrbisProtCpuExec) != 0;
|
||||
|
||||
if (execute)
|
||||
{
|
||||
return write
|
||||
? HostPageExecuteReadWrite
|
||||
: read
|
||||
? HostPageExecuteRead
|
||||
: HostPageExecute;
|
||||
}
|
||||
|
||||
return write
|
||||
? HostPageReadWrite
|
||||
: read
|
||||
? HostPageReadOnly
|
||||
: HostPageNoAccess;
|
||||
}
|
||||
|
||||
private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region)
|
||||
{
|
||||
region = default;
|
||||
@@ -4907,13 +5215,13 @@ public static class KernelMemoryCompatExports
|
||||
|
||||
private static bool HasRequiredProtection(uint protect, bool writeAccess)
|
||||
{
|
||||
if ((protect & (PageNoAccess | PageGuard)) != 0)
|
||||
if ((protect & (HostPageNoAccess | HostPageGuard)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint readableMask = PageReadOnly | PageReadWrite | PageWriteCopy | PageExecuteRead | PageExecuteReadWrite | PageExecuteWriteCopy;
|
||||
const uint writableMask = PageReadWrite | PageWriteCopy | PageExecuteReadWrite | PageExecuteWriteCopy;
|
||||
const uint readableMask = HostPageReadOnly | HostPageReadWrite | HostPageWriteCopy | HostPageExecuteRead | HostPageExecuteReadWrite | HostPageExecuteWriteCopy;
|
||||
const uint writableMask = HostPageReadWrite | HostPageWriteCopy | HostPageExecuteReadWrite | HostPageExecuteWriteCopy;
|
||||
var expected = writeAccess ? writableMask : readableMask;
|
||||
return (protect & expected) != 0;
|
||||
}
|
||||
@@ -5233,6 +5541,17 @@ public static class KernelMemoryCompatExports
|
||||
return (value + mask) & ~mask;
|
||||
}
|
||||
|
||||
private static ulong AlignDown(ulong value, ulong alignment)
|
||||
{
|
||||
if (alignment <= 1)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var mask = alignment - 1;
|
||||
return value & ~mask;
|
||||
}
|
||||
|
||||
private static bool TryAddU64(ulong left, ulong right, out ulong sum)
|
||||
{
|
||||
sum = left + right;
|
||||
|
||||
@@ -15,9 +15,9 @@ public static class KernelPthreadCompatExports
|
||||
private const int MutexTypeNormal = 3;
|
||||
private const int MutexTypeAdaptiveNp = 4;
|
||||
private const ulong StaticAdaptiveMutexInitializer = 1;
|
||||
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
|
||||
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
|
||||
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
|
||||
private const int MutexObjectSize = 0x100;
|
||||
private const int MutexAttrObjectSize = 0x40;
|
||||
private const int CondObjectSize = 0x100;
|
||||
private const int DefaultSpuriousCondWakeMilliseconds = 1;
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
@@ -25,9 +25,6 @@ public static class KernelPthreadCompatExports
|
||||
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
|
||||
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
|
||||
private static readonly HashSet<ulong> _condAttrStates = new();
|
||||
private static long _nextSyntheticMutexHandleId = 1;
|
||||
private static long _nextSyntheticMutexAttrHandleId = 1;
|
||||
private static long _nextSyntheticCondHandleId = 1;
|
||||
|
||||
private sealed class PthreadMutexState
|
||||
{
|
||||
@@ -357,14 +354,34 @@ public static class KernelPthreadCompatExports
|
||||
Protocol = attr.Protocol,
|
||||
};
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
|
||||
if (!TryAllocateOpaqueObject(ctx, MutexObjectSize, out var handle))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
if (!InitializeMutexObject(ctx, handle, state))
|
||||
{
|
||||
state.Semaphore.Dispose();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexStates[mutexAddress] = state;
|
||||
_mutexStates[syntheticHandle] = state;
|
||||
_mutexStates[handle] = state;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(mutexAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexStates.Remove(mutexAddress);
|
||||
_mutexStates.Remove(handle);
|
||||
}
|
||||
|
||||
state.Semaphore.Dispose();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -533,14 +550,34 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
|
||||
lock (_stateGate)
|
||||
if (!TryAllocateOpaqueObject(ctx, MutexAttrObjectSize, out var handle))
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var initialState = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
if (!WriteMutexAttrObject(ctx, handle, initialState))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = initialState;
|
||||
_mutexAttrStates[handle] = initialState;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(attrAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexAttrStates.Remove(attrAddress);
|
||||
_mutexAttrStates.Remove(handle);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -572,6 +609,7 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
|
||||
PthreadMutexAttrState updatedState;
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
@@ -579,14 +617,17 @@ public static class KernelPthreadCompatExports
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
|
||||
_mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
|
||||
updatedState = state with { Type = NormalizeMutexType(type) };
|
||||
_mutexAttrStates[resolvedAddress] = updatedState;
|
||||
if (resolvedAddress != attrAddress)
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
|
||||
_mutexAttrStates[attrAddress] = updatedState;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
return WriteMutexAttrObject(ctx, resolvedAddress, updatedState)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
private static int PthreadMutexattrSetprotocolCore(CpuContext ctx, ulong attrAddress, int protocol)
|
||||
@@ -597,6 +638,7 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
|
||||
PthreadMutexAttrState updatedState;
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
@@ -604,14 +646,17 @@ public static class KernelPthreadCompatExports
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
|
||||
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
|
||||
updatedState = state with { Protocol = protocol };
|
||||
_mutexAttrStates[resolvedAddress] = updatedState;
|
||||
if (resolvedAddress != attrAddress)
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
|
||||
_mutexAttrStates[attrAddress] = updatedState;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
return WriteMutexAttrObject(ctx, resolvedAddress, updatedState)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
private static ulong ResolveMutexHandle(CpuContext ctx, ulong mutexAddress)
|
||||
@@ -703,14 +748,6 @@ public static class KernelPthreadCompatExports
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexAttrStates.ContainsKey(attrAddress))
|
||||
{
|
||||
return attrAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
@@ -722,6 +759,14 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexAttrStates.ContainsKey(attrAddress))
|
||||
{
|
||||
return attrAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return attrAddress;
|
||||
}
|
||||
|
||||
@@ -816,23 +861,60 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
|
||||
var createdState = new PthreadCondState();
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
|
||||
if (!TryAllocateOpaqueObject(ctx, CondObjectSize, out var handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates[condAddress] = createdState;
|
||||
_condStates[syntheticHandle] = createdState;
|
||||
_condStates[handle] = createdState;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
if (!ctx.TryWriteUInt64(condAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates.Remove(condAddress);
|
||||
_condStates.Remove(handle);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedAddress = handle;
|
||||
state = createdState;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
|
||||
private static bool TryAllocateOpaqueObject(CpuContext ctx, int size, out ulong address)
|
||||
{
|
||||
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
|
||||
return baseAddress + (id << 4);
|
||||
address = 0;
|
||||
if (ctx.Memory is not IGuestMemoryAllocator allocator ||
|
||||
!allocator.TryAllocateGuestMemory((ulong)size, alignment: 0x10, out address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<byte> initialData = stackalloc byte[size];
|
||||
initialData.Clear();
|
||||
return ctx.Memory.TryWrite(address, initialData);
|
||||
}
|
||||
|
||||
private static bool InitializeMutexObject(CpuContext ctx, ulong address, PthreadMutexState state) =>
|
||||
TryWriteUInt32(ctx, address + 0x20, unchecked((uint)state.Type)) &&
|
||||
TryWriteUInt32(ctx, address + 0x3C, unchecked((uint)state.Protocol));
|
||||
|
||||
private static bool WriteMutexAttrObject(CpuContext ctx, ulong address, PthreadMutexAttrState state) =>
|
||||
TryWriteUInt32(ctx, address, unchecked((uint)state.Type)) &&
|
||||
TryWriteUInt32(ctx, address + 4, unchecked((uint)state.Protocol));
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||
BitConverter.TryWriteBytes(bytes, value);
|
||||
return ctx.Memory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress)
|
||||
@@ -842,15 +924,29 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
|
||||
if (!TryAllocateOpaqueObject(ctx, CondObjectSize, out var handle))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
var state = new PthreadCondState();
|
||||
_condStates[condAddress] = state;
|
||||
_condStates[syntheticHandle] = state;
|
||||
_condStates[handle] = state;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(condAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates.Remove(condAddress);
|
||||
_condStates.Remove(handle);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -1047,7 +1143,19 @@ public static class KernelPthreadCompatExports
|
||||
Type = type,
|
||||
};
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
|
||||
if (!TryAllocateOpaqueObject(ctx, MutexObjectSize, out var handle))
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
return false;
|
||||
}
|
||||
if (!InitializeMutexObject(ctx, handle, createdState))
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexStates.TryGetValue(mutexAddress, out state))
|
||||
@@ -1056,18 +1164,30 @@ public static class KernelPthreadCompatExports
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_mutexStates.TryGetValue(syntheticHandle, out state))
|
||||
if (_mutexStates.TryGetValue(handle, out state))
|
||||
{
|
||||
resolvedAddress = syntheticHandle;
|
||||
resolvedAddress = handle;
|
||||
return true;
|
||||
}
|
||||
|
||||
_mutexStates[mutexAddress] = createdState;
|
||||
_mutexStates[syntheticHandle] = createdState;
|
||||
_mutexStates[handle] = createdState;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
if (!ctx.TryWriteUInt64(mutexAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexStates.Remove(mutexAddress);
|
||||
_mutexStates.Remove(handle);
|
||||
}
|
||||
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedAddress = handle;
|
||||
state = createdState;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -489,6 +489,37 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-quPa4SEJUw",
|
||||
ExportName = "scePthreadAttrGetstack",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadAttrGetstack(CpuContext ctx)
|
||||
{
|
||||
var attrAddress = ctx[CpuRegister.Rdi];
|
||||
var outStackAddressPointer = ctx[CpuRegister.Rsi];
|
||||
var outStackSizeAddress = ctx[CpuRegister.Rdx];
|
||||
if (attrAddress == 0 || outStackAddressPointer == 0 || outStackSizeAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
PthreadAttrState state;
|
||||
lock (_stateGate)
|
||||
{
|
||||
state = GetOrCreateAttrStateLocked(attrAddress);
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(outStackAddressPointer, state.StackAddress) ||
|
||||
!ctx.TryWriteUInt64(outStackSizeAddress, state.StackSize))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-fA+7ZlGDQs",
|
||||
ExportName = "scePthreadAttrGetstacksize",
|
||||
|
||||
@@ -25,6 +25,7 @@ public static class KernelRuntimeCompatExports
|
||||
private const ulong TlsNewReplaceOffset = 0x300;
|
||||
private const int MallocReplaceSize = 0x70;
|
||||
private const int NewReplaceSize = 0x68;
|
||||
private const int OrbisTimesecSize = sizeof(long) + sizeof(uint) + sizeof(uint);
|
||||
private const ulong ModuleInfoHandleOffset = 0x108;
|
||||
private const ulong ModuleInfoNameOffset = 0x10;
|
||||
private const int ModuleInfoNameMaxBytes = 64;
|
||||
@@ -52,6 +53,7 @@ public static class KernelRuntimeCompatExports
|
||||
private static readonly object _prtApertureGate = new();
|
||||
private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3];
|
||||
private static int _stackChkFailCount;
|
||||
private static long _usleepTraceCount;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate ulong RdtscDelegate();
|
||||
@@ -64,33 +66,52 @@ public static class KernelRuntimeCompatExports
|
||||
public static int KernelUsleep(CpuContext ctx)
|
||||
{
|
||||
var micros = ctx[CpuRegister.Rdi];
|
||||
TraceUsleepSpin(ctx, micros);
|
||||
if (micros == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
var sleepMilliseconds = (int)Math.Min(micros / 1000UL, int.MaxValue);
|
||||
if (sleepMilliseconds > 0)
|
||||
if (micros < 1000)
|
||||
{
|
||||
Thread.Sleep(sleepMilliseconds);
|
||||
Thread.Yield();
|
||||
}
|
||||
|
||||
var remainingMicros = micros % 1000UL;
|
||||
if (remainingMicros > 0)
|
||||
else
|
||||
{
|
||||
var targetTicks = (long)((double)remainingMicros * Stopwatch.Frequency / 1_000_000.0);
|
||||
var spin = Stopwatch.StartNew();
|
||||
while (spin.ElapsedTicks < targetTicks)
|
||||
{
|
||||
Thread.SpinWait(16);
|
||||
}
|
||||
var sleepMilliseconds = (int)Math.Min((micros + 999UL) / 1000UL, int.MaxValue);
|
||||
Thread.Sleep(sleepMilliseconds);
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static void TraceUsleepSpin(CpuContext ctx, ulong micros)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = Interlocked.Increment(ref _usleepTraceCount);
|
||||
if (count > 32 && count % 10000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rbx = ctx[CpuRegister.Rbx];
|
||||
var lockAddress = rbx == 0 ? 0 : rbx + 0xF78;
|
||||
var lockText = "unreadable";
|
||||
if (lockAddress != 0 && ctx.TryReadUInt64(lockAddress, out var lockValue))
|
||||
{
|
||||
lockText = $"0x{lockValue:X16}";
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] usleep#{count}: usec={micros} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r13=0x{ctx[CpuRegister.R13]:X16} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}");
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "QBi7HCK03hw",
|
||||
ExportName = "sceKernelClockGettime",
|
||||
@@ -156,6 +177,37 @@ public static class KernelRuntimeCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "n88vx3C5nW8",
|
||||
ExportName = "gettimeofday",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixGettimeofday(CpuContext ctx)
|
||||
{
|
||||
var timeAddress = ctx[CpuRegister.Rdi];
|
||||
var timezoneAddress = ctx[CpuRegister.Rsi];
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var seconds = now.ToUnixTimeSeconds();
|
||||
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
|
||||
|
||||
if (timeAddress != 0 &&
|
||||
(!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
|
||||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds))))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (timezoneAddress != 0 &&
|
||||
(!TryWriteInt32(ctx, timezoneAddress, 0) ||
|
||||
!TryWriteInt32(ctx, timezoneAddress + sizeof(int), 0)))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-2IRUCO--PM",
|
||||
ExportName = "sceKernelReadTsc",
|
||||
@@ -552,6 +604,17 @@ public static class KernelRuntimeCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "jh+8XiK4LeE",
|
||||
ExportName = "sceKernelIsAddressSanitizerEnabled",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelIsAddressSanitizerEnabled(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ca7v6Cxulzs",
|
||||
ExportName = "sceKernelSetGPO",
|
||||
@@ -1024,6 +1087,111 @@ public static class KernelRuntimeCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-o5uEDpN+oY",
|
||||
ExportName = "sceKernelConvertUtcToLocaltime",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelConvertUtcToLocaltime(CpuContext ctx)
|
||||
{
|
||||
var utcSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
|
||||
var localTimeAddress = ctx[CpuRegister.Rsi];
|
||||
var timesecAddress = ctx[CpuRegister.Rdx];
|
||||
var dstSecondsAddress = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (localTimeAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var utc = DateTimeOffset.FromUnixTimeSeconds(utcSeconds);
|
||||
var local = TimeZoneInfo.ConvertTime(utc, TimeZoneInfo.Local);
|
||||
var offset = local.Offset;
|
||||
var localSeconds = utcSeconds + (long)offset.TotalSeconds;
|
||||
var dstSeconds = TimeZoneInfo.Local.IsDaylightSavingTime(local.DateTime)
|
||||
? (uint)Math.Max(0, TimeZoneInfo.Local.GetAdjustmentRules()
|
||||
.Where(rule => rule.DateStart <= local.Date && rule.DateEnd >= local.Date)
|
||||
.Select(rule => rule.DaylightDelta.TotalSeconds)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max())
|
||||
: 0u;
|
||||
var westSeconds = unchecked((uint)(int)offset.TotalSeconds);
|
||||
|
||||
if (!ctx.TryWriteUInt64(localTimeAddress, unchecked((ulong)localSeconds)))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (timesecAddress != 0)
|
||||
{
|
||||
Span<byte> timesec = stackalloc byte[OrbisTimesecSize];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(timesec, utcSeconds);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(timesec.Slice(sizeof(long), sizeof(uint)), westSeconds);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(timesec.Slice(sizeof(long) + sizeof(uint), sizeof(uint)), dstSeconds);
|
||||
if (!ctx.Memory.TryWrite(timesecAddress, timesec))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
if (dstSecondsAddress != 0 && !ctx.TryWriteUInt64(dstSecondsAddress, dstSeconds))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "0NTHN1NKONI",
|
||||
ExportName = "sceKernelConvertLocaltimeToUtc",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelConvertLocaltimeToUtc(CpuContext ctx)
|
||||
{
|
||||
var localSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
|
||||
var utcTimeAddress = ctx[CpuRegister.Rdx];
|
||||
var timezoneAddress = ctx[CpuRegister.Rcx];
|
||||
var dstSecondsAddress = ctx[CpuRegister.R8];
|
||||
|
||||
if (timezoneAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var localDate = DateTimeOffset.FromUnixTimeSeconds(localSeconds).DateTime;
|
||||
var offset = TimeZoneInfo.Local.GetUtcOffset(localDate);
|
||||
var utcSeconds = localSeconds - (long)offset.TotalSeconds;
|
||||
var dstSeconds = TimeZoneInfo.Local.IsDaylightSavingTime(localDate)
|
||||
? (int)Math.Max(0, TimeZoneInfo.Local.GetAdjustmentRules()
|
||||
.Where(rule => rule.DateStart <= localDate.Date && rule.DateEnd >= localDate.Date)
|
||||
.Select(rule => rule.DaylightDelta.TotalSeconds)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max())
|
||||
: 0;
|
||||
var minutesWest = unchecked((int)-offset.TotalMinutes);
|
||||
|
||||
if (!TryWriteInt32(ctx, timezoneAddress, minutesWest) ||
|
||||
!TryWriteInt32(ctx, timezoneAddress + sizeof(int), dstSeconds / 60))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (utcTimeAddress != 0 && !ctx.TryWriteUInt64(utcTimeAddress, unchecked((ulong)utcSeconds)))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (dstSecondsAddress != 0 && !TryWriteInt32(ctx, dstSecondsAddress, dstSeconds))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vYU8P9Td2Zo",
|
||||
ExportName = "sceKernelAioInitializeImpl",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Network;
|
||||
|
||||
public static class Http2Exports
|
||||
{
|
||||
private const int Http2ErrorInvalidId = unchecked((int)0x80436004);
|
||||
private const int Http2ErrorInvalidArgument = unchecked((int)0x80436016);
|
||||
|
||||
private static readonly ConcurrentDictionary<int, Http2Context> _contexts = new();
|
||||
private static int _nextContextId;
|
||||
|
||||
private sealed record Http2Context(int NetId, int SslId, ulong PoolSize, int MaxRequests);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "3JCe3lCbQ8A",
|
||||
ExportName = "sceHttp2Init",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceHttp2")]
|
||||
public static int Http2Init(CpuContext ctx)
|
||||
{
|
||||
var netId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var sslId = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var poolSize = ctx[CpuRegister.Rdx];
|
||||
var maxRequests = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
|
||||
if (poolSize == 0 || maxRequests <= 0)
|
||||
{
|
||||
return SetReturn(ctx, Http2ErrorInvalidArgument);
|
||||
}
|
||||
|
||||
var id = Interlocked.Increment(ref _nextContextId);
|
||||
_contexts[id] = new Http2Context(netId, sslId, poolSize, maxRequests);
|
||||
|
||||
TraceHttp2("init", id, unchecked((ulong)netId), unchecked((ulong)sslId), poolSize, unchecked((ulong)maxRequests));
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)id);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YiBUtz-pGkc",
|
||||
ExportName = "sceHttp2Term",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceHttp2")]
|
||||
public static int Http2Term(CpuContext ctx)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!_contexts.TryRemove(id, out _))
|
||||
{
|
||||
return SetReturn(ctx, Http2ErrorInvalidId);
|
||||
}
|
||||
|
||||
TraceHttp2("term", id, 0, 0, 0, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceHttp2(string operation, int id, ulong arg0, ulong arg1, ulong arg2, ulong arg3)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_HTTP2"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] http2.{operation} id={id} arg0=0x{arg0:X16} arg1=0x{arg1:X16} arg2=0x{arg2:X16} arg3=0x{arg3:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Network;
|
||||
|
||||
public static class NetCtlExports
|
||||
{
|
||||
[SysAbiExport(
|
||||
Nid = "gky0+oaNM4k",
|
||||
ExportName = "sceNetCtlInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNetCtl")]
|
||||
public static int NetCtlInit(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Network;
|
||||
|
||||
public static class NetExports
|
||||
{
|
||||
private const int NetErrorBadFileDescriptor = unchecked((int)0x80410109);
|
||||
private const int NetErrorInvalidArgument = unchecked((int)0x80410116);
|
||||
private const int MaxNameLength = 256;
|
||||
|
||||
private static readonly ConcurrentDictionary<int, NetPool> _pools = new();
|
||||
private static int _nextPoolId;
|
||||
private static bool _initialized;
|
||||
|
||||
private sealed record NetPool(string Name, int Size, int Flags);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Nlev7Lg8k3A",
|
||||
ExportName = "sceNetInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetInit(CpuContext ctx)
|
||||
{
|
||||
_initialized = true;
|
||||
TraceNet("init", 0, 0, 0, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "cTGkc6-TBlI",
|
||||
ExportName = "sceNetTerm",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetTerm(CpuContext ctx)
|
||||
{
|
||||
_initialized = false;
|
||||
_pools.Clear();
|
||||
TraceNet("term", 0, 0, 0, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "dgJBaeJnGpo",
|
||||
ExportName = "sceNetPoolCreate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetPoolCreate(CpuContext ctx)
|
||||
{
|
||||
var nameAddress = ctx[CpuRegister.Rdi];
|
||||
var size = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var flags = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
|
||||
if (size <= 0)
|
||||
{
|
||||
return SetReturn(ctx, NetErrorInvalidArgument);
|
||||
}
|
||||
|
||||
var name = TryReadUtf8Z(ctx, nameAddress, MaxNameLength, out var value)
|
||||
? value
|
||||
: string.Empty;
|
||||
|
||||
var id = Interlocked.Increment(ref _nextPoolId);
|
||||
_pools[id] = new NetPool(name, size, flags);
|
||||
|
||||
TraceNet("pool.create", id, unchecked((ulong)size), unchecked((ulong)flags), _initialized ? 1UL : 0UL);
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)id);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "K7RlrTkI-mw",
|
||||
ExportName = "sceNetPoolDestroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetPoolDestroy(CpuContext ctx)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!_pools.TryRemove(id, out _))
|
||||
{
|
||||
return SetReturn(ctx, NetErrorBadFileDescriptor);
|
||||
}
|
||||
|
||||
TraceNet("pool.destroy", id, 0, 0, _initialized ? 1UL : 0UL);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool TryReadUtf8Z(CpuContext ctx, ulong address, int maxLength, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
if (address == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Span<byte> one = stackalloc byte[1];
|
||||
var bytes = new byte[maxLength];
|
||||
var count = 0;
|
||||
for (; count < maxLength; count++)
|
||||
{
|
||||
if (!ctx.Memory.TryRead(address + (ulong)count, one))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (one[0] == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bytes[count] = one[0];
|
||||
}
|
||||
|
||||
value = Encoding.UTF8.GetString(bytes, 0, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TraceNet(string operation, int id, ulong arg0, ulong arg1, ulong arg2)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] net.{operation} id={id} arg0=0x{arg0:X16} arg1=0x{arg1:X16} arg2=0x{arg2:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Network;
|
||||
|
||||
public static class SslExports
|
||||
{
|
||||
private const int SslErrorInvalidId = unchecked((int)0x8095F006);
|
||||
private const int SslErrorOutOfSize = unchecked((int)0x8095F008);
|
||||
|
||||
private static readonly ConcurrentDictionary<int, SslContext> _contexts = new();
|
||||
private static int _nextContextId;
|
||||
|
||||
private sealed record SslContext(ulong PoolSize);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hdpVEUDFW3s",
|
||||
ExportName = "sceSslInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSsl")]
|
||||
public static int SslInit(CpuContext ctx)
|
||||
{
|
||||
var poolSize = ctx[CpuRegister.Rdi];
|
||||
if (poolSize == 0)
|
||||
{
|
||||
return SetReturn(ctx, SslErrorOutOfSize);
|
||||
}
|
||||
|
||||
var id = Interlocked.Increment(ref _nextContextId);
|
||||
_contexts[id] = new SslContext(poolSize);
|
||||
|
||||
TraceSsl("init", id, poolSize);
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)id);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "0K1yQ6Lv-Yc",
|
||||
ExportName = "sceSslTerm",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSsl")]
|
||||
public static int SslTerm(CpuContext ctx)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!_contexts.TryRemove(id, out _))
|
||||
{
|
||||
return SetReturn(ctx, SslErrorInvalidId);
|
||||
}
|
||||
|
||||
TraceSsl("term", id, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "viRXSHZYd0c",
|
||||
ExportName = "sceSslClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSsl")]
|
||||
public static int SslClose(CpuContext ctx)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
TraceSsl("close", id, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceSsl(string operation, int id, ulong arg0)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SSL"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ssl.{operation} id={id} arg0=0x{arg0:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Np;
|
||||
|
||||
public static class NpWebApi2Exports
|
||||
{
|
||||
private const int NpWebApi2ErrorInvalidArgument = unchecked((int)0x80553402);
|
||||
|
||||
private static int _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "+o9816YQhqQ",
|
||||
ExportName = "sceNpWebApi2Initialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpWebApi2")]
|
||||
public static int NpWebApi2Initialize(CpuContext ctx)
|
||||
{
|
||||
var httpContextId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var poolSize = ctx[CpuRegister.Rsi];
|
||||
|
||||
if (httpContextId <= 0 || poolSize == 0)
|
||||
{
|
||||
return SetReturn(ctx, NpWebApi2ErrorInvalidArgument);
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref _initialized, 1);
|
||||
TraceNpWebApi2("init", httpContextId, poolSize);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bEvXpcEk200",
|
||||
ExportName = "sceNpWebApi2Terminate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpWebApi2")]
|
||||
public static int NpWebApi2Terminate(CpuContext ctx)
|
||||
{
|
||||
var libraryContextId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
Interlocked.Exchange(ref _initialized, 0);
|
||||
TraceNpWebApi2("term", libraryContextId, 0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceNpWebApi2(string operation, int id, ulong arg0)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NP_WEB_API2"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] npwebapi2.{operation} id={id} arg0=0x{arg0:X16} initialized={Volatile.Read(ref _initialized)}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.NpGameIntent;
|
||||
|
||||
public static class NpGameIntentExports
|
||||
{
|
||||
private static int _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "m87BHxt-H60",
|
||||
ExportName = "sceNpGameIntentInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpGameIntent")]
|
||||
public static int NpGameIntentInitialize(CpuContext ctx)
|
||||
{
|
||||
Interlocked.Exchange(ref _initialized, 1);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="Silk.NET.Windowing" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -10,6 +10,7 @@ public static class SystemServiceExports
|
||||
{
|
||||
private const int OrbisSystemServiceErrorParameter = unchecked((int)0x80A10003);
|
||||
private const int SystemServiceStatusSize = 0x0C;
|
||||
private const int DisplaySafeAreaInfoSize = sizeof(float) + 128;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "rPo6tV8D9bM",
|
||||
@@ -34,6 +35,28 @@ public static class SystemServiceExports
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "1n37q1Bvc5Y",
|
||||
ExportName = "sceSystemServiceGetDisplaySafeAreaInfo",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSystemService")]
|
||||
public static int SystemServiceGetDisplaySafeAreaInfo(CpuContext ctx)
|
||||
{
|
||||
var infoAddress = ctx[CpuRegister.Rdi];
|
||||
if (infoAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisSystemServiceErrorParameter);
|
||||
}
|
||||
|
||||
Span<byte> info = stackalloc byte[DisplaySafeAreaInfoSize];
|
||||
info.Clear();
|
||||
BinaryPrimitives.WriteSingleLittleEndian(info, 1.0f);
|
||||
|
||||
return ctx.Memory.TryWrite(infoAddress, info)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
|
||||
@@ -44,6 +44,36 @@ public static class VideoOutExports
|
||||
private static int _nextHandle = 1;
|
||||
private static int _frameDumpCount;
|
||||
private static long _nextFrameDumpIndex;
|
||||
private static string _windowTitle = "SharpEmu VideoOut";
|
||||
|
||||
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
parts.Add(title.Trim());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
parts.Add($"[{titleId.Trim()}]");
|
||||
}
|
||||
|
||||
var application = parts.Count == 0 ? "VideoOut" : string.Join(' ', parts);
|
||||
var versionSuffix = string.IsNullOrWhiteSpace(version) ? string.Empty : $" v{version.Trim()}";
|
||||
lock (_stateGate)
|
||||
{
|
||||
_windowTitle = $"SharpEmu - {application}{versionSuffix}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetWindowTitle()
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
return _windowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VideoOutPortState
|
||||
{
|
||||
@@ -210,7 +240,15 @@ public static class VideoOutExports
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
port.FlipEvents.Add(new FlipEventRegistration(equeue, userData));
|
||||
var existingIndex = port.FlipEvents.FindIndex(registration => registration.Equeue == equeue);
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
port.FlipEvents[existingIndex] = new FlipEventRegistration(equeue, userData);
|
||||
}
|
||||
else
|
||||
{
|
||||
port.FlipEvents.Add(new FlipEventRegistration(equeue, userData));
|
||||
}
|
||||
}
|
||||
|
||||
TraceVideoOut($"videoout.add_flip_event eq=0x{equeue:X16} handle={handle} udata=0x{userData:X16}");
|
||||
@@ -284,7 +322,8 @@ public static class VideoOutExports
|
||||
return OrbisVideoOutErrorInvalidEvent;
|
||||
}
|
||||
|
||||
return ctx.TryWriteUInt64(dataAddress, data >> 16)
|
||||
var decodedData = unchecked((ulong)(unchecked((long)data) >> 16));
|
||||
return ctx.TryWriteUInt64(dataAddress, decodedData)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
@@ -292,6 +331,25 @@ public static class VideoOutExports
|
||||
public static int SubmitFlipFromAgc(CpuContext ctx, int handle, int bufferIndex, int flipMode, long flipArg) =>
|
||||
SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg);
|
||||
|
||||
internal static void SubmitHostRgbaFrame(ReadOnlySpan<byte> rgbaFrame, uint width, uint height)
|
||||
{
|
||||
if (rgbaFrame.Length != checked((int)(width * height * 4)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bgraFrame = new byte[rgbaFrame.Length];
|
||||
for (var offset = 0; offset < rgbaFrame.Length; offset += 4)
|
||||
{
|
||||
bgraFrame[offset + 0] = rgbaFrame[offset + 2];
|
||||
bgraFrame[offset + 1] = rgbaFrame[offset + 1];
|
||||
bgraFrame[offset + 2] = rgbaFrame[offset + 0];
|
||||
bgraFrame[offset + 3] = rgbaFrame[offset + 3];
|
||||
}
|
||||
|
||||
VulkanVideoPresenter.Submit(bgraFrame, width, height);
|
||||
}
|
||||
|
||||
internal static bool TryGetDisplayBufferInfo(int handle, int bufferIndex, out DisplayBufferInfo info)
|
||||
{
|
||||
info = default;
|
||||
@@ -591,7 +649,7 @@ public static class VideoOutExports
|
||||
return OrbisVideoOutErrorInvalidIndex;
|
||||
}
|
||||
|
||||
ulong eventData;
|
||||
ulong eventHint;
|
||||
List<FlipEventRegistration> flipEvents;
|
||||
lock (_stateGate)
|
||||
{
|
||||
@@ -602,25 +660,27 @@ public static class VideoOutExports
|
||||
|
||||
port.CurrentBuffer = bufferIndex;
|
||||
port.FlipCount++;
|
||||
var eventCount = Math.Min(port.FlipCount, 0xFUL);
|
||||
var timeBits = (ulong)Environment.TickCount64 & 0xFFFUL;
|
||||
eventData = timeBits | (eventCount << 12) | ((unchecked((ulong)flipArg) & 0x0000_FFFF_FFFF_FFFFUL) << 16);
|
||||
eventHint = SceVideoOutInternalEventFlip |
|
||||
((unchecked((ulong)flipArg) & 0x0000_FFFF_FFFF_FFFFUL) << 16);
|
||||
flipEvents = new List<FlipEventRegistration>(port.FlipEvents);
|
||||
}
|
||||
|
||||
_ = TryDumpFrame(ctx, port, bufferIndex, flipMode, flipArg);
|
||||
if (string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_VIDEOOUT"),
|
||||
"1",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
_ = TryDumpFrame(ctx, port, bufferIndex, flipMode, flipArg);
|
||||
}
|
||||
|
||||
foreach (var flipEvent in flipEvents)
|
||||
{
|
||||
_ = KernelEventQueueCompatExports.EnqueueEvent(
|
||||
_ = KernelEventQueueCompatExports.TriggerDisplayEvent(
|
||||
flipEvent.Equeue,
|
||||
new KernelEventQueueCompatExports.KernelQueuedEvent(
|
||||
SceVideoOutInternalEventFlip,
|
||||
OrbisKernelEventFilterVideoOut,
|
||||
0,
|
||||
0,
|
||||
eventData,
|
||||
flipEvent.UserData));
|
||||
SceVideoOutInternalEventFlip,
|
||||
OrbisKernelEventFilterVideoOut,
|
||||
eventHint,
|
||||
flipEvent.UserData);
|
||||
}
|
||||
|
||||
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,91 @@
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.hle": {
|
||||
"type": "Project"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user