Compare commits

..

24 Commits

Author SHA1 Message Date
ParantezTech dad85b87c3 [video] Add game title to window 2026-06-21 23:19:16 +03:00
ParantezTech 464422f23e [video] Add Vulkan presentation 2026-06-21 23:19:05 +03:00
ParantezTech 5afa5e1ffa [hle] Add missing service exports 2026-06-21 23:18:55 +03:00
ParantezTech 5512da9e12 [kernel] Add thread and event primitives 2026-06-21 23:18:42 +03:00
ParantezTech 2a52ccfe6a [cpu] Add guest thread continuation 2026-06-21 23:18:29 +03:00
ParantezTech 08b315b5fc [kernel] Add guest memory support 2026-06-21 23:18:18 +03:00
ParantezTech 5d204afd10 [videoOut] add minimal Vulkan swapchain presenter 2026-06-14 12:12:09 +03:00
ParantezTech 7bd1e8dc29 change jpg to sharpemu videoOut test window 2026-06-07 16:15:43 +03:00
ParantezTech fad61e116e reuse 2026-06-07 15:56:07 +03:00
ParantezTech bc297fa175 [readme] update jpg 2026-06-07 15:48:11 +03:00
ParantezTech 79e767ed9f [readme] added rendering texture from Dreaming Sarah (this is just a presenter draw, not GPU swapchain) 2026-06-07 15:45:33 +03:00
ParantezTech 1ea1396979 [kernel/videoOut] extend memory managements and videoOut (this is not a swapchain) 2026-06-07 15:43:26 +03:00
ParantezTech a1dea499bc update readme 2026-05-10 20:37:49 +03:00
ParantezTech fee6af3136 update readme for new titles 2026-05-10 20:02:58 +03:00
ParantezTech bbf5ff7be8 more HLE's and fix cpu execution some titles 2026-05-10 19:51:57 +03:00
ParantezTech 0c859f04ad Added more HLE's 2026-04-27 12:40:50 +03:00
ParantezTech 994438963d [ci] temporary fix 2026-04-13 15:59:19 +03:00
ParantezTech 233af123af More new HLE's and fixes 2026-04-13 15:54:38 +03:00
ParantezTech 02eb9b30e9 correct patch 2026-03-28 18:40:54 +03:00
ParantezTech fa46819030 fix for actions (again) 2026-03-28 18:23:46 +03:00
ParantezTech 7a915e88dc fix dotnet version on Github Actions 2026-03-28 18:15:14 +03:00
ParantezTech 812879aa81 PlayGo, VideoOut minimum HLE implements and fix some direct runner 2026-03-28 18:02:37 +03:00
ParantezTech 71ba5cf1db More HLE implements: wcs* 2026-03-19 23:57:48 +03:00
ParantezTech 8f108ca01d Upgrade GitHub Actions to Node 24 2026-03-19 13:33:19 +03:00
54 changed files with 14172 additions and 478 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

+1 -1
View File
@@ -109,7 +109,7 @@ jobs:
Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal
- name: Upload build artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ${{ needs.init.outputs.artifact-name }}
path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }}
+2
View File
@@ -29,6 +29,8 @@ arm64/
packages/
*.nupkg
.nuget/
.dotnet-home/
.cache/
.DS_Store
Thumbs.db
+3
View File
@@ -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>
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -7,7 +7,8 @@ path = [
"**/packages.lock.json",
"scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
"_logs/**"
"_logs/**",
".github/images/**"
]
precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.103",
"rollForward": "latestFeature"
"rollForward": "disable"
}
}
+110 -4
View File
@@ -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=="
}
}
}
}
+37 -4
View File
@@ -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;
@@ -193,6 +194,11 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!InitializeGuestFrameChainSentinel(context))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!InitializeTls(context, tlsBase))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
@@ -337,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);
@@ -358,9 +365,28 @@ 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)
{
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
var seedRsp = sentinelFrame - sizeof(ulong);
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
!context.TryWriteUInt64(seedRsp, 0))
{
return false;
}
context[CpuRegister.Rbp] = sentinelFrame;
context[CpuRegister.Rsp] = seedRsp;
return true;
}
private static bool InitializeProcessEntryFrame(
@@ -398,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,55 +154,105 @@ 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);
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;
bool flag3 = num >= 1020 && num <= 1040;
if (!flag0 && (num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3))
bool logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal);
string importFilter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FILTER");
bool flag4 = !string.IsNullOrWhiteSpace(importFilter);
bool flag5 = false;
ExportedFunction matchedExport = null;
if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export))
{
if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export))
matchedExport = export;
if (flag4)
{
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid})");
flag5 = export.LibraryName.Contains(importFilter, StringComparison.OrdinalIgnoreCase)
|| export.Name.Contains(importFilter, StringComparison.OrdinalIgnoreCase)
|| importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase);
}
}
else if (flag4)
{
flag5 = importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase);
}
bool flag6 = logAllImports || flag5;
if (!flag0 && (flag6 || num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3))
{
if (matchedExport != null)
{
if (flag6)
{
Console.Error.WriteLine(
$"[LOADER][TRACE] Import#{num}: {matchedExport.LibraryName}:{matchedExport.Name} ({importStubEntry.Nid}) " +
$"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}");
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {matchedExport.LibraryName}:{matchedExport.Name} ({importStubEntry.Nid})");
}
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid}");
if (flag6)
{
Console.Error.WriteLine(
$"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid} " +
$"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}");
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid}");
}
}
if (flag6)
{
Console.Error.Flush();
}
}
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();
@@ -201,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];
@@ -212,40 +281,35 @@ public sealed partial class DirectExecutionBackend
}
TryBypassStackChkFailTrap(num, num7);
}
if (importStubEntry.Nid == "9rAeANT2tyE" || importStubEntry.Nid == "1j3S3n-tTW4")
{
ulong rspDbg = _cpuContext[CpuRegister.Rsp];
Console.Error.WriteLine(
$"[LOADER][TRACE] ImportDbg#{num}: nid={importStubEntry.Nid} " +
$"ret=0x{num7:X16} rsp=0x{rspDbg:X16} rsp&7=0x{(rspDbg & 7):X} rsp&F=0x{(rspDbg & 0xF):X}");
if (_cpuContext.TryReadUInt64(rspDbg, out var s0))
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x00] = 0x{s0:X16}");
if (_cpuContext.TryReadUInt64(rspDbg + 8, out var s8))
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x08] = 0x{s8:X16}");
if (_cpuContext.TryReadUInt64(rspDbg + 16, out var s10))
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x10] = 0x{s10:X16}");
if (_cpuContext.TryReadUInt64(rspDbg + 24, out var s18))
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x18] = 0x{s18:X16}");
ProbeReturnRip(num7, num);
Console.Error.Flush();
}
try
{
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)
{
@@ -264,42 +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;
if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitStatus, out var exitReason))
{
if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, 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;
}
}
if (GuestThreadExecution.TryConsumeCurrentThreadBlock(out var blockReason) &&
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, blockReason))
{
cpuContext[CpuRegister.Rax] = 0uL;
}
_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 (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;
@@ -312,13 +420,56 @@ 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();
return true;
}
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status)
{
ulong hostExit = ActiveEntryReturnSentinelRip;
if (hostExit < 65536)
{
return false;
}
try
{
*(ulong*)(argPackPtr + 96) = hostExit;
}
catch
{
return false;
}
Console.Error.WriteLine(
$"[LOADER][INFO] Guest entry exit at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={reason} status={status}");
return true;
}
private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason)
{
ulong hostExit = ActiveEntryReturnSentinelRip;
if (hostExit < 65536)
{
return false;
}
try
{
*(ulong*)(argPackPtr + 96) = hostExit;
}
catch
{
return false;
}
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={ActiveGuestThreadYieldReason}");
return true;
}
private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
{
if (dispatchIndex < 1200)
@@ -341,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)
@@ -562,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();
@@ -608,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;
}
@@ -654,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;
}
@@ -679,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;
}
@@ -698,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;
}
@@ -721,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;
}
@@ -735,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
+12 -1
View File
@@ -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;
}
}
+13 -1
View File
@@ -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; }
}
+18 -10
View File
@@ -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;
}
}
+39 -2
View File
@@ -125,6 +125,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
public OrbisGen2Result Run(string ebootPath)
{
var normalizedEbootPath = Path.GetFullPath(ebootPath);
using var app0Binding = BindApp0Root(normalizedEbootPath);
Console.Error.WriteLine($"[RUNTIME] Loading: {ebootPath}");
LastExecutionDiagnostics = null;
LastExecutionTrace = null;
@@ -132,8 +134,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
LastBasicBlockTrace = null;
LastMilestoneLog = null;
KernelModuleRegistry.Reset();
var image = LoadImage(ebootPath);
var normalizedEbootPath = Path.GetFullPath(ebootPath);
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}");
@@ -350,6 +352,41 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return result;
}
private static App0BindingScope? BindApp0Root(string normalizedEbootPath)
{
const string app0VariableName = "SHARPEMU_APP0_DIR";
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(app0VariableName)))
{
return null;
}
var app0Root = Path.GetDirectoryName(normalizedEbootPath);
if (string.IsNullOrWhiteSpace(app0Root))
{
return null;
}
Environment.SetEnvironmentVariable(app0VariableName, app0Root);
return new App0BindingScope(app0VariableName);
}
private sealed class App0BindingScope(string variableName) : IDisposable
{
private readonly string _variableName = variableName;
private bool _disposed;
public void Dispose()
{
if (_disposed)
{
return;
}
Environment.SetEnvironmentVariable(_variableName, null);
_disposed = true;
}
}
private OrbisGen2Result? RunAllInitializers(
SelfImage mainImage,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
+89 -1
View File
@@ -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"
}
}
}
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public readonly record struct GuestThreadStartRequest(
ulong ThreadHandle,
ulong EntryPoint,
ulong Argument,
ulong AttributeAddress,
string Name);
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]
private static ulong _currentGuestThreadHandle;
[ThreadStatic]
private static string? _pendingBlockReason;
[ThreadStatic]
private static bool _pendingEntryExit;
[ThreadStatic]
private static int _pendingEntryExitStatus;
[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;
public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle;
public static ulong EnterGuestThread(ulong threadHandle)
{
var previous = _currentGuestThreadHandle;
_currentGuestThreadHandle = threadHandle;
_pendingBlockReason = null;
_pendingEntryExit = false;
_pendingEntryExitStatus = 0;
_pendingEntryExitReason = null;
_hasCurrentImportCallFrame = false;
_currentImportReturnRip = 0;
_currentImportResumeRsp = 0;
return previous;
}
public static void RestoreGuestThread(ulong previousThreadHandle)
{
_currentGuestThreadHandle = previousThreadHandle;
_pendingBlockReason = null;
_pendingEntryExit = false;
_pendingEntryExitStatus = 0;
_pendingEntryExitReason = null;
_hasCurrentImportCallFrame = false;
_currentImportReturnRip = 0;
_currentImportResumeRsp = 0;
}
public static bool RequestCurrentThreadBlock(string reason)
{
if (!IsGuestThread)
{
return false;
}
_pendingBlockReason = string.IsNullOrWhiteSpace(reason) ? "guest_thread_blocked" : reason;
return true;
}
public static bool TryConsumeCurrentThreadBlock(out string reason)
{
reason = _pendingBlockReason ?? string.Empty;
if (string.IsNullOrEmpty(reason))
{
return false;
}
_pendingBlockReason = null;
return true;
}
public static void RequestCurrentEntryExit(string reason, int status)
{
_pendingEntryExit = true;
_pendingEntryExitStatus = status;
_pendingEntryExitReason = string.IsNullOrWhiteSpace(reason) ? "guest_entry_exit" : reason;
}
public static bool TryConsumeCurrentEntryExit(out int status, out string reason)
{
status = _pendingEntryExitStatus;
reason = _pendingEntryExitReason ?? string.Empty;
if (!_pendingEntryExit)
{
return false;
}
_pendingEntryExit = false;
_pendingEntryExitStatus = 0;
_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);
}
File diff suppressed because it is too large Load Diff
@@ -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;
}
}
+641
View File
@@ -0,0 +1,641 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Collections.Concurrent;
namespace SharpEmu.Libs.Ampr;
public static class AmprExports
{
private const int CommandBufferHeaderSize = 0x28;
private const ulong CommandBufferSelfOffset = 0x00;
private const ulong CommandBufferDataOffset = 0x08;
private const ulong CommandBufferSizeOffset = 0x10;
private const ulong CommandBufferAux0Offset = 0x18;
private const ulong CommandBufferAux1Offset = 0x20;
private const ulong ReadFileRecordSize = 0x30;
private const ulong KernelEventQueueRecordSize = 0x30;
private const uint ReadFileRecordType = 1;
private const uint KernelEventQueueRecordType = 2;
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
private sealed class CommandBufferState
{
public ulong Buffer;
public ulong Size;
public ulong WriteOffset;
}
[SysAbiExport(
Nid = "8aI7R7WaOlc",
ExportName = "sceAmprCommandBufferConstructor",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferConstructor(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var buffer = ctx[CpuRegister.Rsi];
var size = ctx[CpuRegister.Rdx];
if (commandBuffer == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer, size, aux0: 0, aux1: 0, clear: true))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "ctor", commandBuffer, buffer, size);
ctx[CpuRegister.Rax] = commandBuffer;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "a8uLzYY--tM",
ExportName = "sceAmprAprCommandBufferConstructor",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int AprCommandBufferConstructor(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var aux0 = ctx[CpuRegister.Rsi];
var aux1 = ctx[CpuRegister.Rdx];
if (commandBuffer == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var buffer = 0UL;
var size = 0UL;
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer);
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size);
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer, size, aux0, aux1, clear: false))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "apr_ctor", commandBuffer, aux0, aux1);
ctx[CpuRegister.Rax] = commandBuffer;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Qs1xtplKo0U",
ExportName = "sceAmprAprCommandBufferDestructor",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int AprCommandBufferDestructor(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, 0) ||
!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "apr_dtor", commandBuffer, 0, 0);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "GuchCTefuZw",
ExportName = "sceAmprCommandBufferDestructor",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferDestructor(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer: 0, size: 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_commandBuffers.TryRemove(commandBuffer, out _);
TraceAmpr(ctx, "dtor", commandBuffer, 0, 0);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "N-FSPA4S3nI",
ExportName = "sceAmprCommandBufferSetBuffer",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferSetBuffer(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var buffer = ctx[CpuRegister.Rsi];
var size = ctx[CpuRegister.Rdx];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "set_buffer", commandBuffer, buffer, size);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "baQO9ez2gL4",
ExportName = "sceAmprCommandBufferReset",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferReset(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out var buffer) ||
!ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out var size) ||
!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "reset", commandBuffer, buffer, size);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "ULvXMDz56po",
ExportName = "sceAmprCommandBufferClearBuffer",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferClearBuffer(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryGetCommandBufferState(ctx, commandBuffer, out var buffer, out var size, out _) ||
!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer: 0, size: 0))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_commandBuffers.TryRemove(commandBuffer, out _);
TraceAmpr(ctx, "clear_buffer", commandBuffer, buffer, size);
ctx[CpuRegister.Rax] = buffer;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "mQ16-QdKv7k",
ExportName = "sceAmprAprCommandBufferReadFile",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int AprCommandBufferReadFile(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var fileId = unchecked((uint)ctx[CpuRegister.Rcx]);
var destination = ctx[CpuRegister.R8];
var size = ctx[CpuRegister.R9];
if (commandBuffer == 0 || (destination == 0 && size != 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out var fileOffset))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath) || !File.Exists(hostPath))
{
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var result = TryReadFileToGuestMemory(ctx, hostPath, fileOffset, destination, size, out var bytesRead);
if (result != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead, hostPath, result);
return result;
}
if (!AppendReadFileRecord(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_OK);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "vWU-odnS+fU",
ExportName = "sceAmprMeasureCommandSizeReadFile",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int MeasureCommandSizeReadFile(CpuContext ctx)
{
TraceAmpr(ctx, "measure_read_file", 0, ReadFileRecordSize, 0);
ctx[CpuRegister.Rax] = ReadFileRecordSize;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "sSAUCCU1dv4",
ExportName = "sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int MeasureCommandSizeWriteKernelEventQueue0400(CpuContext ctx)
{
TraceAmpr(ctx, "measure_write_equeue", 0, KernelEventQueueRecordSize, 0);
ctx[CpuRegister.Rax] = KernelEventQueueRecordSize;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "tZDDEo2tE5k",
ExportName = "sceAmprCommandBufferGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferGetSize(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out var size, out _))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "get_size", commandBuffer, size, 0);
ctx[CpuRegister.Rax] = size;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "GnxKOHEawhk",
ExportName = "sceAmprCommandBufferGetCurrentOffset",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferGetCurrentOffset(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryGetCommandBufferOffset(ctx, commandBuffer, out var offset))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "get_offset", commandBuffer, offset, 0);
ctx[CpuRegister.Rax] = offset;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "H896Pt-yB4I",
ExportName = "sceAmprCommandBufferWriteKernelEventQueue_04_00",
Target = Generation.Gen5,
LibraryName = "libSceAmpr")]
public static int CommandBufferWriteKernelEventQueue0400(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var equeue = ctx[CpuRegister.Rsi];
var ident = ctx[CpuRegister.Rdx];
var filter = ctx[CpuRegister.Rcx];
var userData = ctx[CpuRegister.R8];
var data = ctx[CpuRegister.R9];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var extra = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out extra);
if (!AppendKernelEventQueueRecord(ctx, commandBuffer, equeue, ident, filter, userData, data, extra))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceAmpr(ctx, "write_equeue", commandBuffer, equeue, ident);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool InitializeCommandBuffer(
CpuContext ctx,
ulong commandBuffer,
ulong buffer,
ulong size,
ulong aux0,
ulong aux1,
bool clear)
{
if (clear)
{
Span<byte> header = stackalloc byte[CommandBufferHeaderSize];
header.Clear();
if (!ctx.Memory.TryWrite(commandBuffer, header))
{
return false;
}
}
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, aux0) &&
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, aux1) &&
WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0);
}
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
{
return WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0);
}
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size, ulong writeOffset)
{
if (!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer, size))
{
return false;
}
var state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
lock (state)
{
state.Buffer = buffer;
state.Size = size;
state.WriteOffset = writeOffset;
}
return true;
}
private static bool WriteVisibleCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
{
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
ctx.TryWriteUInt64(commandBuffer + CommandBufferDataOffset, buffer) &&
ctx.TryWriteUInt64(commandBuffer + CommandBufferSizeOffset, size);
}
private static bool TryGetCommandBufferState(
CpuContext ctx,
ulong commandBuffer,
out ulong buffer,
out ulong size,
out CommandBufferState? state)
{
if (_commandBuffers.TryGetValue(commandBuffer, out state))
{
lock (state)
{
buffer = state.Buffer;
size = state.Size;
}
return true;
}
if (ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer) &&
ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size))
{
state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
lock (state)
{
state.Buffer = buffer;
state.Size = size;
state.WriteOffset = 0;
}
return true;
}
buffer = 0;
size = 0;
state = null;
return false;
}
private static bool TryGetCommandBufferOffset(CpuContext ctx, ulong commandBuffer, out ulong offset)
{
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
{
offset = 0;
return false;
}
lock (state)
{
offset = state.WriteOffset;
}
return true;
}
private static int TryReadFileToGuestMemory(
CpuContext ctx,
string hostPath,
ulong fileOffset,
ulong destination,
ulong size,
out ulong bytesRead)
{
bytesRead = 0;
if (size == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (fileOffset > long.MaxValue)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
const int ChunkSize = 64 * 1024;
var buffer = new byte[(int)Math.Min((ulong)ChunkSize, size)];
try
{
using var stream = new FileStream(hostPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
if (fileOffset >= (ulong)stream.Length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
stream.Position = unchecked((long)fileOffset);
while (bytesRead < size)
{
var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead);
var read = stream.Read(buffer, 0, request);
if (read <= 0)
{
break;
}
if (!ctx.Memory.TryWrite(destination + bytesRead, buffer.AsSpan(0, read)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
bytesRead += (ulong)read;
}
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool AppendReadFileRecord(
CpuContext ctx,
ulong commandBuffer,
uint fileId,
ulong destination,
ulong size,
ulong fileOffset,
ulong bytesRead)
{
Span<byte> record = stackalloc byte[(int)ReadFileRecordSize];
record.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], ReadFileRecordType);
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], fileId);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], destination);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], size);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], fileOffset);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], bytesRead);
return AppendCommandBufferRecord(ctx, commandBuffer, record);
}
private static bool AppendKernelEventQueueRecord(
CpuContext ctx,
ulong commandBuffer,
ulong equeue,
ulong ident,
ulong filter,
ulong userData,
ulong data,
ulong extra)
{
Span<byte> record = stackalloc byte[(int)KernelEventQueueRecordSize];
record.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], KernelEventQueueRecordType);
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], unchecked((uint)filter));
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], equeue);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], ident);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], userData);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], data);
BinaryPrimitives.WriteUInt64LittleEndian(record[0x28..], extra);
return AppendCommandBufferRecord(ctx, commandBuffer, record);
}
private static bool AppendCommandBufferRecord(CpuContext ctx, ulong commandBuffer, ReadOnlySpan<byte> record)
{
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
{
return false;
}
var recordSize = (ulong)record.Length;
lock (state)
{
if (state.Buffer == 0 ||
state.WriteOffset > state.Size ||
recordSize > state.Size - state.WriteOffset)
{
return false;
}
if (!ctx.Memory.TryWrite(state.Buffer + state.WriteOffset, record))
{
return false;
}
state.WriteOffset += recordSize;
}
return true;
}
private static void TraceAmpr(CpuContext ctx, string operation, ulong commandBuffer, ulong arg0, ulong arg1)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine(
$"[LOADER][TRACE] ampr.{operation}: cmd=0x{commandBuffer:X16} arg0=0x{arg0:X16} arg1=0x{arg1:X16} ret=0x{returnRip:X16}");
}
private static void TraceAmprRead(
CpuContext ctx,
ulong commandBuffer,
uint fileId,
ulong destination,
ulong size,
ulong fileOffset,
ulong bytesRead,
string? hostPath,
int result)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine(
$"[LOADER][TRACE] ampr.read_file: cmd=0x{commandBuffer:X16} id=0x{fileId:X8} dst=0x{destination:X16} size=0x{size:X16} offset=0x{fileOffset:X16} read=0x{bytesRead:X16} result=0x{result:X8} path='{hostPath ?? string.Empty}' ret=0x{returnRip:X16}");
}
}
@@ -0,0 +1,40 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
namespace SharpEmu.Libs.Ampr;
internal static class AmprFileRegistry
{
private static readonly ConcurrentDictionary<uint, string> _hostPathsById = new();
public static uint Register(string guestPath, string hostPath)
{
var id = ComputeFileId(guestPath);
_hostPathsById[id] = hostPath;
return id;
}
public static bool TryGetHostPath(uint id, out string hostPath)
{
return _hostPathsById.TryGetValue(id, out hostPath!);
}
private static uint ComputeFileId(string guestPath)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(guestPath);
const uint offsetBasis = 2166136261;
const uint prime = 16777619;
var hash = offsetBasis;
foreach (var b in bytes)
{
hash ^= b;
hash *= prime;
}
return hash;
}
}
@@ -2,11 +2,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Text;
namespace SharpEmu.Libs.AppContent;
public static class AppContentExports
{
private const ulong BootParamAttrOffset = 4;
private const string Temp0MountPoint = "/temp0";
[SysAbiExport(
Nid = "R9lA82OraNs",
ExportName = "sceAppContentInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAppContent")]
public static int AppContentInitialize(CpuContext ctx)
{
var initParamAddress = ctx[CpuRegister.Rdi];
var bootParamAddress = ctx[CpuRegister.Rsi];
if (initParamAddress == 0 || bootParamAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> attrBytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(attrBytes, 0);
if (!ctx.Memory.TryWrite(bootParamAddress + BootParamAttrOffset, attrBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "xnd8BJzAxmk",
ExportName = "sceAppContentGetAddcontInfoList",
@@ -14,7 +44,67 @@ public static class AppContentExports
LibraryName = "libSceAppContent")]
public static int AppContentGetAddcontInfoList(CpuContext ctx)
{
_ = ctx;
var hitCountAddress = ctx[CpuRegister.Rcx];
if (hitCountAddress != 0)
{
Span<byte> hitCountBytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(hitCountBytes, 0);
if (!ctx.Memory.TryWrite(hitCountAddress, hitCountBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "buYbeLOGWmA",
ExportName = "sceAppContentTemporaryDataMount2",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAppContent")]
public static int AppContentTemporaryDataMount2(CpuContext ctx)
{
var mountPointAddress = ctx[CpuRegister.Rsi];
if (mountPointAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Directory.CreateDirectory(ResolveTemp0Root());
var mountPointBytes = Encoding.ASCII.GetBytes($"{Temp0MountPoint}\0");
if (!ctx.Memory.TryWrite(mountPointAddress, mountPointBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static string ResolveTemp0Root()
{
const string temp0VariableName = "SHARPEMU_TEMP0_DIR";
var configuredRoot = Environment.GetEnvironmentVariable(temp0VariableName);
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
return Path.GetFullPath(configuredRoot);
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
var appName = string.IsNullOrWhiteSpace(app0Root)
? "default"
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
if (string.IsNullOrWhiteSpace(appName))
{
appName = "default";
}
var invalidChars = Path.GetInvalidFileNameChars();
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
var root = Path.Combine(Path.GetTempPath(), "SharpEmu", appName, "temp0");
Environment.SetEnvironmentVariable(temp0VariableName, root);
return root;
}
}
@@ -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;
}
}
+961
View File
@@ -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;
}
}
+91
View File
@@ -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,158 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelAprCompatExports
{
private static readonly ConcurrentDictionary<uint, ulong> _submittedCommandBuffers = new();
private static int _nextSubmissionId;
[SysAbiExport(
Nid = "ASoW5WE-UPo",
ExportName = "sceKernelAprSubmitCommandBufferAndGetResult",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAprSubmitCommandBufferAndGetResult(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var priority = ctx[CpuRegister.Rsi];
var resultAddress = ctx[CpuRegister.Rdx];
var outSubmissionId = ctx[CpuRegister.Rcx];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
if (submissionId == 0)
{
submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
}
_submittedCommandBuffers[submissionId] = commandBuffer;
if (outSubmissionId != 0 && !TryWriteUInt32(ctx, outSubmissionId, submissionId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceApr(ctx, "submit_get_result", submissionId, commandBuffer, priority, resultAddress);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "rqwFKI4PAiM",
ExportName = "sceKernelAprWaitCommandBuffer",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAprWaitCommandBuffer(CpuContext ctx)
{
var submissionId = unchecked((uint)ctx[CpuRegister.Rdi]);
var priority = ctx[CpuRegister.Rsi];
var resultAddress = ctx[CpuRegister.Rdx];
if (!_submittedCommandBuffers.TryRemove(submissionId, out var commandBuffer))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceApr(ctx, "wait", submissionId, commandBuffer, priority, resultAddress);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eE4Szl8sil8",
ExportName = "sceKernelAprSubmitCommandBuffer",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAprSubmitCommandBuffer(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
if (commandBuffer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer;
TraceApr(ctx, "submit", submissionId, commandBuffer, ctx[CpuRegister.Rsi], 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "qvMUCyyaCSI",
ExportName = "sceKernelAprSubmitCommandBufferAndGetId",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAprSubmitCommandBufferAndGetId(CpuContext ctx)
{
var commandBuffer = ctx[CpuRegister.Rdi];
var outSubmissionId = ctx[CpuRegister.Rdx];
if (commandBuffer == 0 || outSubmissionId == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer;
if (!TryWriteUInt32(ctx, outSubmissionId, submissionId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceApr(ctx, "submit_get_id", submissionId, commandBuffer, ctx[CpuRegister.Rsi], outSubmissionId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryWriteAprResult(CpuContext ctx, ulong resultAddress)
{
Span<byte> result = stackalloc byte[sizeof(ulong)];
result.Clear();
return ctx.Memory.TryWrite(resultAddress, result);
}
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 void TraceApr(
CpuContext ctx,
string operation,
uint submissionId,
ulong commandBuffer,
ulong priority,
ulong aux)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine(
$"[LOADER][TRACE] apr.{operation}: id=0x{submissionId:X8} cmd=0x{commandBuffer:X16} priority=0x{priority:X16} aux=0x{aux:X16} ret=0x{returnRip: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}");
}
}
}
@@ -0,0 +1,362 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelEventQueueCompatExports
{
private const int KernelEventSize = 0x20;
private static readonly object _eventQueueGate = new();
private static readonly HashSet<ulong> _eventQueues = new();
private static readonly Dictionary<ulong, LinkedList<KernelQueuedEvent>> _pendingEvents = new();
private static long _nextEventQueueHandle = 1;
public readonly record struct KernelQueuedEvent(
ulong Ident,
short Filter,
ushort Flags,
uint Fflags,
ulong Data,
ulong UserData);
[SysAbiExport(
Nid = "D0OdFMjp46I",
ExportName = "sceKernelCreateEqueue",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCreateEqueue(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var handle = unchecked((ulong)Interlocked.Increment(ref _nextEventQueueHandle));
lock (_eventQueueGate)
{
_eventQueues.Add(handle);
_pendingEvents[handle] = new LinkedList<KernelQueuedEvent>();
}
if (!ctx.TryWriteUInt64(outAddress, handle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceEventQueue(ctx, "create", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "jpFjmgAC5AE",
ExportName = "sceKernelDeleteEqueue",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteEqueue(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
lock (_eventQueueGate)
{
_eventQueues.Remove(handle);
_pendingEvents.Remove(handle);
}
TraceEventQueue(ctx, "delete", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "WDszmSbWuDk",
ExportName = "sceKernelAddUserEventEdge",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAddUserEventEdge(CpuContext ctx)
{
TraceEventQueue(ctx, "add_user_edge", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "4R6-OvI2cEA",
ExportName = "sceKernelAddUserEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAddUserEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "add_user", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "LJDwdSNTnDg",
ExportName = "sceKernelDeleteUserEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteUserEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "delete_user", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "F6e0kwo4cnk",
ExportName = "sceKernelTriggerUserEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelTriggerUserEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "trigger_user", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "bBfz7kMF2Ho",
ExportName = "sceKernelAddAmprEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAddAmprEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "add_ampr", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "vuae5JPNt9A",
ExportName = "sceKernelAddAmprSystemEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAddAmprSystemEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "add_ampr_system", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "bMmid3pfyjo",
ExportName = "sceKernelDeleteAmprEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteAmprEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "delete_ampr", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ij+ryuEClXQ",
ExportName = "sceKernelDeleteAmprSystemEvent",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteAmprSystemEvent(CpuContext ctx)
{
TraceEventQueue(ctx, "delete_ampr_system", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "QyrxcdBrb0M",
ExportName = "sceKernelGetKqueueFromEqueue",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetKqueueFromEqueue(CpuContext ctx)
{
ctx[CpuRegister.Rax] = ctx[CpuRegister.Rdi];
TraceEventQueue(ctx, "get_kqueue", ctx[CpuRegister.Rdi]);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "fzyMKs9kim0",
ExportName = "sceKernelWaitEqueue",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWaitEqueue(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var eventsAddress = ctx[CpuRegister.Rsi];
var eventCapacity = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
var outCountAddress = ctx[CpuRegister.Rcx];
var timeoutAddress = ctx[CpuRegister.R8];
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (deliveredCount > 0)
{
TraceEventQueue(ctx, "wait-deliver", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (timeoutAddress == 0 && GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEqueue"))
{
TraceEventQueue(ctx, "wait-block", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
TraceEventQueue(ctx, "wait", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
public static bool IsValidEqueue(ulong handle)
{
lock (_eventQueueGate)
{
return _eventQueues.Contains(handle);
}
}
public static bool EnqueueEvent(ulong handle, KernelQueuedEvent queuedEvent)
{
lock (_eventQueueGate)
{
if (!_eventQueues.Contains(handle))
{
return false;
}
if (!_pendingEvents.TryGetValue(handle, out var queue))
{
queue = new LinkedList<KernelQueuedEvent>();
_pendingEvents[handle] = queue;
}
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;
}
}
private static int DequeueEvents(CpuContext ctx, ulong handle, ulong eventsAddress, int eventCapacity)
{
if (eventsAddress == 0 || eventCapacity <= 0)
{
return 0;
}
KernelQueuedEvent[] events;
lock (_eventQueueGate)
{
if (!_pendingEvents.TryGetValue(handle, out var queue) || queue.Count == 0)
{
return 0;
}
var count = Math.Min(eventCapacity, queue.Count);
events = new KernelQueuedEvent[count];
for (var i = 0; i < count; i++)
{
events[i] = queue.First!.Value;
queue.RemoveFirst();
}
}
for (var i = 0; i < events.Length; i++)
{
if (!WriteKernelEvent(ctx, eventsAddress + ((ulong)i * KernelEventSize), events[i]))
{
return i;
}
}
return events.Length;
}
private static bool WriteKernelEvent(CpuContext ctx, ulong address, KernelQueuedEvent queuedEvent)
{
Span<byte> eventBytes = stackalloc byte[KernelEventSize];
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x00..], queuedEvent.Ident);
BinaryPrimitives.WriteInt16LittleEndian(eventBytes[0x08..], queuedEvent.Filter);
BinaryPrimitives.WriteUInt16LittleEndian(eventBytes[0x0A..], queuedEvent.Flags);
BinaryPrimitives.WriteUInt32LittleEndian(eventBytes[0x0C..], queuedEvent.Fflags);
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x10..], queuedEvent.Data);
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x18..], queuedEvent.UserData);
return ctx.Memory.TryWrite(address, eventBytes);
}
private static void TraceEventQueue(CpuContext ctx, string operation, ulong handle)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_EQUEUE"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine(
$"[LOADER][TRACE] equeue.{operation}: handle=0x{handle:X16} rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} ret=0x{returnRip:X16}");
}
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);
}
}
+38 -86
View File
@@ -55,7 +55,10 @@ public static class KernelExports
LibraryName = "libKernel")]
public static int Exit(CpuContext ctx)
{
_ = ctx;
var status = unchecked((int)ctx[CpuRegister.Rdi]);
Console.Error.WriteLine($"[LOADER][INFO] exit(status={status})");
GuestThreadExecution.RequestCurrentEntryExit("exit", status);
ctx[CpuRegister.Rax] = unchecked((ulong)status);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -68,7 +71,8 @@ public static class KernelExports
{
var status = unchecked((int)ctx[CpuRegister.Rdi]);
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
ctx[CpuRegister.Rax] = 0;
GuestThreadExecution.RequestCurrentEntryExit("catchReturnFromMain", status);
ctx[CpuRegister.Rax] = unchecked((ulong)status);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -211,6 +215,19 @@ public static class KernelExports
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
}
var scheduler = GuestThreadExecution.Scheduler;
if (scheduler is not null && entryAddress != 0)
{
var request = new GuestThreadStartRequest(threadHandle, entryAddress, argument, attrAddress, name);
if (!scheduler.TryStartThread(ctx, request, out var error))
{
Console.Error.WriteLine(
$"[LOADER][ERROR] pthread_create: failed to schedule guest thread '{name}' entry=0x{entryAddress:X16}: {error}");
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -225,6 +242,16 @@ public static class KernelExports
return PthreadCreate(ctx);
}
[SysAbiExport(
Nid = "Jmi+9w9u0E4",
ExportName = "pthread_create_name_np",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCreateNameNp(CpuContext ctx)
{
return PthreadCreate(ctx);
}
[SysAbiExport(
Nid = "onNY9Byn-W8",
ExportName = "scePthreadJoin",
@@ -264,22 +291,14 @@ public static class KernelExports
ExportName = "open",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int Open(CpuContext ctx)
{
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
public static int Open(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
[SysAbiExport(
Nid = "1G3lF1Gg1k8",
ExportName = "sceKernelOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelOpen(CpuContext ctx)
{
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
[SysAbiExport(
Nid = "hcuQgD53UxM",
@@ -290,84 +309,17 @@ public static class KernelExports
{
ulong fmtPtr = ctx[CpuRegister.Rdi];
string fmt = ReadCString(ctx, fmtPtr, 4096);
ulong[] args =
string outStr = KernelMemoryCompatExports.FormatStringFromVarArgs(ctx, fmt, firstGpArgIndex: 1);
if (outStr.EndsWith('\n') || outStr.EndsWith('\r'))
{
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
ctx[CpuRegister.R9],
};
int argIndex = 0;
var sb = new System.Text.StringBuilder(fmt.Length + 64);
for (int i = 0; i < fmt.Length; i++)
Console.Write($"[DEBUG][PRINF] {outStr}");
}
else
{
char ch = fmt[i];
if (ch != '%')
{
sb.Append(ch);
continue;
}
if (i + 1 < fmt.Length && fmt[i + 1] == '%')
{
sb.Append('%');
i++;
continue;
}
int j = i + 1;
while (j < fmt.Length && "-+ #0".IndexOf(fmt[j]) >= 0) j++;
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
if (j < fmt.Length && fmt[j] == '.')
{
j++;
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
}
if (j >= fmt.Length) break;
char spec = fmt[j];
i = j;
ulong a = argIndex < args.Length ? args[argIndex++] : 0;
switch (spec)
{
case 's':
sb.Append(a == 0 ? "(null)" : ReadCString(ctx, a, 4096));
break;
case 'd':
case 'i':
sb.Append(unchecked((int)a));
break;
case 'u':
sb.Append(unchecked((uint)a));
break;
case 'x':
sb.Append(unchecked((uint)a).ToString("x"));
break;
case 'p':
sb.Append("0x").Append(a.ToString("x16"));
break;
default:
sb.Append('%').Append(spec);
break;
}
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
}
string outStr = sb.ToString();
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
ctx[CpuRegister.Rax] = (ulong)outStr.Length;
ctx[CpuRegister.Rax] = (ulong)System.Text.Encoding.UTF8.GetByteCount(outStr);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
File diff suppressed because it is too large Load Diff
@@ -12,19 +12,19 @@ public static class KernelPthreadCompatExports
private const int MutexTypeDefault = 1;
private const int MutexTypeErrorCheck = 1;
private const int MutexTypeRecursive = 2;
private const int MutexTypeNormal = 4;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
private const int MutexTypeNormal = 3;
private const int MutexTypeAdaptiveNp = 4;
private const ulong StaticAdaptiveMutexInitializer = 1;
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();
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
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
{
@@ -87,18 +87,14 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadGetthreadid",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadGetthreadid(CpuContext ctx)
{
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
public static int PthreadGetthreadid(CpuContext ctx) => PthreadGetthreadidCore(ctx);
ctx[CpuRegister.Rax] = currentThreadId;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "3eqs37G74-s",
ExportName = "pthread_getthreadid_np",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadGetthreadidNp(CpuContext ctx) => PthreadGetthreadidCore(ctx);
[SysAbiExport(
Nid = "cmo1RIYva9o",
@@ -170,6 +166,12 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PosixPthreadMutexUnlock(CpuContext ctx) => PthreadMutexUnlockCore(ctx, ctx[CpuRegister.Rdi], requireOwner: true);
private static int PthreadGetthreadidCore(CpuContext ctx)
{
ctx[CpuRegister.Rax] = KernelPthreadState.GetCurrentThreadUniqueId();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "F8bUHwAG284",
ExportName = "scePthreadMutexattrInit",
@@ -233,6 +235,13 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "0TyVk4MSLt0",
ExportName = "pthread_cond_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "g+PZd2hiacg",
ExportName = "scePthreadCondDestroy",
@@ -282,6 +291,13 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport(
Nid = "2MOy+rUfuhQ",
ExportName = "pthread_cond_signal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
[SysAbiExport(
Nid = "m5-2bsNfv7s",
ExportName = "scePthreadCondattrInit",
@@ -338,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;
}
@@ -403,6 +439,23 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (state.Type is MutexTypeNormal or MutexTypeAdaptiveNp)
{
if (tryOnly)
{
TracePthreadMutex(ctx, "trylock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
// Normal/adaptive mutexes do not report EDEADLK on self-lock.
// Under the current single-host-thread guest execution model,
// treating them as nested ownership keeps init paths moving
// without turning a would-block path into a hard error.
state.RecursionCount++;
TracePthreadMutex(ctx, "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var ownedResult = tryOnly
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
@@ -497,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;
}
@@ -536,6 +609,7 @@ public static class KernelPthreadCompatExports
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
PthreadMutexAttrState updatedState;
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
@@ -543,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)
@@ -561,6 +638,7 @@ public static class KernelPthreadCompatExports
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
PthreadMutexAttrState updatedState;
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
@@ -568,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)
@@ -630,6 +711,11 @@ public static class KernelPthreadCompatExports
return false;
}
if (pointedHandle == StaticAdaptiveMutexInitializer)
{
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeAdaptiveNp, out resolvedAddress, out state);
}
if (pointedHandle != 0)
{
lock (_stateGate)
@@ -652,18 +738,7 @@ public static class KernelPthreadCompatExports
return false;
}
var createdState = new PthreadMutexState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
lock (_stateGate)
{
_mutexStates[mutexAddress] = createdState;
_mutexStates[syntheticHandle] = createdState;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
state = createdState;
return true;
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeDefault, out resolvedAddress, out state);
}
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
@@ -673,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)
@@ -692,6 +759,14 @@ public static class KernelPthreadCompatExports
}
}
lock (_stateGate)
{
if (_mutexAttrStates.ContainsKey(attrAddress))
{
return attrAddress;
}
}
return attrAddress;
}
@@ -786,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)
@@ -812,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;
}
@@ -858,6 +984,7 @@ public static class KernelPthreadCompatExports
}
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
var spuriousWake = false;
lock (state.SyncRoot)
{
state.Waiters++;
@@ -872,11 +999,36 @@ public static class KernelPthreadCompatExports
return unlockResult;
}
var scheduler = GuestThreadExecution.Scheduler;
if (!timed && GuestThreadExecution.RequestCurrentThreadBlock("pthread_cond_wait"))
{
TracePthreadCond("wait-block", condAddress, mutexAddress, state, timed, waitResult);
return waitResult;
}
if (scheduler is not null)
{
Monitor.Exit(state.SyncRoot);
try
{
scheduler.Pump(ctx, "pthread_cond_wait");
}
finally
{
Monitor.Enter(state.SyncRoot);
}
}
while (state.SignalEpoch == observedEpoch)
{
if (!timed)
{
Monitor.Wait(state.SyncRoot);
if (!Monitor.Wait(state.SyncRoot, GetCondSpuriousWakeTimeout()))
{
spuriousWake = true;
break;
}
continue;
}
@@ -888,7 +1040,15 @@ public static class KernelPthreadCompatExports
}
state.Waiters = Math.Max(0, state.Waiters - 1);
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout", condAddress, mutexAddress, state, timed, waitResult);
TracePthreadCond(
spuriousWake
? "wait-spurious"
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout"),
condAddress,
mutexAddress,
state,
timed,
waitResult);
}
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
@@ -898,7 +1058,15 @@ public static class KernelPthreadCompatExports
return lockResult;
}
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout", condAddress, mutexAddress, state, timed, waitResult);
TracePthreadCond(
spuriousWake
? "wait-exit-spurious"
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout"),
condAddress,
mutexAddress,
state,
timed,
waitResult);
return waitResult;
}
@@ -945,6 +1113,16 @@ public static class KernelPthreadCompatExports
return TimeSpan.FromTicks((long)timeoutUsec * 10L);
}
private static TimeSpan GetCondSpuriousWakeTimeout()
{
if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_PTHREAD_COND_SPURIOUS_WAKE_MS"), out var milliseconds))
{
return TimeSpan.FromMilliseconds(Math.Max(1, milliseconds));
}
return TimeSpan.FromMilliseconds(DefaultSpuriousCondWakeMilliseconds);
}
private static int NormalizeMutexType(int type)
{
return type switch
@@ -953,11 +1131,67 @@ public static class KernelPthreadCompatExports
1 => MutexTypeErrorCheck,
2 => MutexTypeRecursive,
3 => MutexTypeNormal,
4 => MutexTypeNormal,
4 => MutexTypeAdaptiveNp,
_ => MutexTypeDefault,
};
}
private static bool CreateImplicitMutexState(CpuContext ctx, ulong mutexAddress, int type, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
{
var createdState = new PthreadMutexState
{
Type = type,
};
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))
{
resolvedAddress = mutexAddress;
return true;
}
if (_mutexStates.TryGetValue(handle, out state))
{
resolvedAddress = handle;
return true;
}
_mutexStates[mutexAddress] = createdState;
_mutexStates[handle] = createdState;
}
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;
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
{
if (!ShouldTracePthread())
@@ -20,17 +20,18 @@ public static class KernelPthreadExtendedCompatExports
private const int DefaultSchedPolicy = 0;
private const int DefaultSchedPriority = 0;
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
private static readonly Dictionary<ulong, PthreadAttrState> _attrStates = new();
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
private static readonly Dictionary<ulong, PthreadRwlockState> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
private static int _nextTlsKey = 1;
private static long _nextSyntheticRwlockHandleId = 1;
private static long _nextSyntheticPthreadAttrHandleId = 1;
[ThreadStatic]
private static Dictionary<int, ulong>? _threadLocalSpecific;
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new();
private sealed class ThreadState
{
@@ -41,6 +42,47 @@ public static class KernelPthreadExtendedCompatExports
public PthreadAttrState Attributes { get; set; } = PthreadAttrState.Default;
}
private sealed class PthreadRwlockState
{
public object SyncRoot { get; } = new();
public Dictionary<ulong, int> ReaderCounts { get; } = new();
public int ReaderTotalCount { get; set; }
public ulong WriterThreadId { get; set; }
public int WaitingWriters { get; set; }
public int GetReaderCount(ulong threadId)
{
return ReaderCounts.TryGetValue(threadId, out var count) ? count : 0;
}
public void AddReader(ulong threadId)
{
ReaderCounts.TryGetValue(threadId, out var currentCount);
ReaderCounts[threadId] = currentCount + 1;
ReaderTotalCount++;
}
public bool RemoveReader(ulong threadId)
{
if (!ReaderCounts.TryGetValue(threadId, out var currentCount) || currentCount <= 0)
{
return false;
}
if (currentCount == 1)
{
ReaderCounts.Remove(threadId);
}
else
{
ReaderCounts[threadId] = currentCount - 1;
}
ReaderTotalCount = Math.Max(0, ReaderTotalCount - 1);
return true;
}
}
private readonly record struct TlsKeyState(ulong Destructor);
private readonly record struct PthreadAttrState(
@@ -195,6 +237,41 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Xs9hdiD7sAA",
ExportName = "pthread_setschedparam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadSetschedparam(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var policy = unchecked((int)ctx[CpuRegister.Rsi]);
var schedParamAddress = ctx[CpuRegister.Rdx];
if (thread == 0 || schedParamAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadInt32(ctx, schedParamAddress, out var schedPriority))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
var state = GetOrCreateThreadStateLocked(thread);
state.Priority = schedPriority;
state.Attributes = state.Attributes with
{
SchedPolicy = policy,
SchedPriority = schedPriority,
};
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "nsYoNRywwNg",
ExportName = "scePthreadAttrInit",
@@ -208,15 +285,32 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var syntheticHandle = AllocateSyntheticHandle(SyntheticPthreadAttrHandleBase, ref _nextSyntheticPthreadAttrHandleId);
if (!ctx.TryWriteUInt64(attrAddress, syntheticHandle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
_attrStates[attrAddress] = PthreadAttrState.Default;
_attrStates[syntheticHandle] = PthreadAttrState.Default;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "wtkt-teR1so",
ExportName = "pthread_attr_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadAttrInit(CpuContext ctx)
{
return PthreadAttrInit(ctx);
}
[SysAbiExport(
Nid = "62KCwEMmzcM",
ExportName = "scePthreadAttrDestroy",
@@ -230,15 +324,31 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
_attrStates.Remove(attrAddress);
if (resolvedAddress != attrAddress)
{
_attrStates.Remove(resolvedAddress);
}
}
_ = ctx.TryWriteUInt64(attrAddress, 0);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "zHchY8ft5pk",
ExportName = "pthread_attr_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadAttrDestroy(CpuContext ctx)
{
return PthreadAttrDestroy(ctx);
}
[SysAbiExport(
Nid = "x1X76arYMxU",
ExportName = "scePthreadAttrGet",
@@ -379,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",
@@ -571,16 +712,32 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { StackSize = stackSize };
var state = GetOrCreateAttrStateLocked(resolvedAddress);
var updated = state with { StackSize = stackSize };
_attrStates[resolvedAddress] = updated;
if (resolvedAddress != attrAddress)
{
_attrStates[attrAddress] = updated;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2Q0z6rnBrTE",
ExportName = "pthread_attr_setstacksize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadAttrSetstacksize(CpuContext ctx)
{
return PthreadAttrSetstacksize(ctx);
}
[SysAbiExport(
Nid = "6ULAa0fq4jA",
ExportName = "scePthreadRwlockInit",
@@ -600,10 +757,9 @@ public static class KernelPthreadExtendedCompatExports
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
if (_rwlockStates.Remove(resolvedAddress, out var existing))
{
existing.Dispose();
}
var rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
var rwlock = new PthreadRwlockState();
_rwlockStates[rwlockAddress] = rwlock;
_rwlockStates[syntheticHandle] = rwlock;
}
@@ -634,14 +790,10 @@ public static class KernelPthreadExtendedCompatExports
}
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
ReaderWriterLockSlim? state;
PthreadRwlockState? state;
lock (_stateGate)
{
_rwlockStates.Remove(resolvedAddress, out state);
if (resolvedAddress != rwlockAddress)
{
_rwlockStates.Remove(rwlockAddress);
}
_rwlockStates.TryGetValue(resolvedAddress, out state);
}
if (state is null)
@@ -649,8 +801,24 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
lock (state.SyncRoot)
{
if (state.WriterThreadId != 0 || state.ReaderTotalCount != 0 || state.WaitingWriters != 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
}
lock (_stateGate)
{
_rwlockStates.Remove(resolvedAddress);
if (resolvedAddress != rwlockAddress)
{
_rwlockStates.Remove(rwlockAddress);
}
}
_ = ctx.TryWriteUInt64(rwlockAddress, 0);
state.Dispose();
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -703,29 +871,38 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: false, out _, out var rwlock))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
try
{
if (rwlock.IsWriteLockHeld)
lock (rwlock.SyncRoot)
{
rwlock.ExitWriteLock();
}
else if (rwlock.IsReadLockHeld)
{
rwlock.ExitReadLock();
}
else
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
if (rwlock.WriterThreadId == currentThreadId)
{
rwlock.WriterThreadId = 0;
Monitor.PulseAll(rwlock.SyncRoot);
}
else if (rwlock.RemoveReader(currentThreadId))
{
if (rwlock.ReaderTotalCount == 0 || rwlock.WaitingWriters > 0)
{
Monitor.PulseAll(rwlock.SyncRoot);
}
}
else
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
}
}
catch (SynchronizationLockException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -794,9 +971,13 @@ public static class KernelPthreadExtendedCompatExports
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
foreach (var entry in _threadLocalSpecific)
{
entry.Value.Remove(key);
}
}
_threadLocalSpecific?.Remove(key);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -817,16 +998,23 @@ public static class KernelPthreadExtendedCompatExports
{
var key = unchecked((int)ctx[CpuRegister.Rdi]);
var value = ctx[CpuRegister.Rsi];
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
lock (_stateGate)
{
if (!_tlsKeys.TryGetValue(key, out _))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values))
{
values = new Dictionary<int, ulong>();
_threadLocalSpecific[currentThreadHandle] = values;
}
values[key] = value;
}
_threadLocalSpecific ??= new Dictionary<int, ulong>();
_threadLocalSpecific[key] = value;
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -846,6 +1034,8 @@ public static class KernelPthreadExtendedCompatExports
public static int PosixPthreadGetspecific(CpuContext ctx)
{
var key = unchecked((int)ctx[CpuRegister.Rdi]);
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
ulong value = 0;
lock (_stateGate)
{
if (!_tlsKeys.TryGetValue(key, out _))
@@ -853,12 +1043,15 @@ public static class KernelPthreadExtendedCompatExports
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values) &&
values.TryGetValue(key, out var storedValue))
{
value = storedValue;
}
}
ctx[CpuRegister.Rax] =
_threadLocalSpecific is not null && _threadLocalSpecific.TryGetValue(key, out var value)
? value
: 0UL;
ctx[CpuRegister.Rax] = value;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -881,21 +1074,47 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
lock (rwlock.SyncRoot)
{
if (write)
{
rwlock.EnterWriteLock();
if (rwlock.WriterThreadId == currentThreadId || rwlock.GetReaderCount(currentThreadId) > 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
}
rwlock.WaitingWriters++;
try
{
while (rwlock.WriterThreadId != 0 || rwlock.ReaderTotalCount != 0)
{
Monitor.Wait(rwlock.SyncRoot);
}
}
finally
{
rwlock.WaitingWriters--;
}
rwlock.WriterThreadId = currentThreadId;
}
else
{
rwlock.EnterReadLock();
if (rwlock.WriterThreadId == currentThreadId)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
}
while (rwlock.WriterThreadId != 0 ||
(rwlock.WaitingWriters > 0 && rwlock.GetReaderCount(currentThreadId) == 0))
{
Monitor.Wait(rwlock.SyncRoot);
}
rwlock.AddReader(currentThreadId);
}
}
catch (LockRecursionException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -929,7 +1148,7 @@ public static class KernelPthreadExtendedCompatExports
return rwlockAddress;
}
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out ReaderWriterLockSlim? rwlock)
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadRwlockState? rwlock)
{
resolvedAddress = 0;
rwlock = null;
@@ -974,7 +1193,7 @@ public static class KernelPthreadExtendedCompatExports
return false;
}
var createdRwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
var createdRwlock = new PthreadRwlockState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
lock (_stateGate)
{
@@ -1029,6 +1248,35 @@ public static class KernelPthreadExtendedCompatExports
return state;
}
private static ulong ResolvePthreadAttrHandle(CpuContext ctx, ulong attrAddress)
{
if (attrAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_attrStates.ContainsKey(attrAddress))
{
return attrAddress;
}
}
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_attrStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return attrAddress;
}
private static bool TryWriteFixedUtf8CString(CpuContext ctx, ulong address, string value, int maxBytes)
{
if (maxBytes <= 0)
@@ -3,6 +3,7 @@
using System.Runtime.InteropServices;
using System.Threading;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
@@ -25,12 +26,24 @@ internal static class KernelPthreadState
internal static ulong GetCurrentThreadHandle()
{
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out _))
{
return guestThreadHandle;
}
EnsureCurrentThreadRegistered();
return _currentThreadHandle;
}
internal static ulong GetCurrentThreadUniqueId()
{
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out var identity))
{
return identity.UniqueId;
}
EnsureCurrentThreadRegistered();
return _currentThreadUniqueId;
}
@@ -25,12 +25,14 @@ 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;
private const ulong DefaultKernelTscFrequency = 10_000_000UL;
private const ulong PrtAreaStartAddress = 0x0000001000000000UL;
private const ulong PrtAreaSize = 0x000000EC00000000UL;
private const int AioInitParamSize = 0x3C;
private const uint MemCommit = 0x1000;
private const uint MemReserve = 0x2000;
private const uint PageExecuteReadWrite = 0x40;
@@ -51,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();
@@ -63,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",
@@ -155,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",
@@ -257,6 +310,8 @@ public static class KernelRuntimeCompatExports
}
}
TraceProcParam(ctx, address);
ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -269,6 +324,223 @@ public static class KernelRuntimeCompatExports
}
}
private static void TraceProcParam(CpuContext ctx, ulong address)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM"), "1", StringComparison.Ordinal))
{
return;
}
if (address == 0)
{
Console.Error.WriteLine("[LOADER][TRACE] proc_param: address=0");
return;
}
const int dumpSize = 0x200;
var buffer = GC.AllocateUninitializedArray<byte>(dumpSize);
if (!ctx.Memory.TryRead(address, buffer))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} unreadable");
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} size=0x{dumpSize:X}");
for (var offset = 0; offset < dumpSize; offset += 16)
{
var slice = buffer.AsSpan(offset, 16);
var hex = Convert.ToHexString(slice);
Console.Error.WriteLine($"[LOADER][TRACE] proc_param[{offset:X3}]: {hex}");
}
TraceProcParamPointers(ctx, address, buffer);
}
private static void TraceProcParamPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> buffer)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM_PTRS"), "1", StringComparison.Ordinal))
{
return;
}
if (buffer.Length < 0x50)
{
return;
}
for (var offset = 0x20; offset <= 0x48; offset += 8)
{
var ptr = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(offset, 8));
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr@{offset:X2}: 0x{ptr:X16}");
if (ptr == 0)
{
continue;
}
TraceProcParamPointerTarget(ctx, ptr);
}
}
private static void TraceProcParamPointerTarget(CpuContext ctx, ulong address)
{
const int maxAsciiBytes = 256;
const int maxWideChars = 128;
if (TryReadUtf8CString(ctx, address, maxAsciiBytes, out var asciiValue))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target ascii@0x{address:X16}: \"{asciiValue}\"");
return;
}
if (TryReadUtf16CString(ctx, address, maxWideChars, out var wideValue))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target wide@0x{address:X16}: \"{wideValue}\"");
return;
}
var preview = GC.AllocateUninitializedArray<byte>(64);
if (ctx.Memory.TryRead(address, preview))
{
var hex = Convert.ToHexString(preview);
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target hex@0x{address:X16}: {hex}");
TraceProcParamEmbeddedPointers(ctx, address, preview);
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target unreadable@0x{address:X16}");
}
}
private static bool TryReadUtf8CString(CpuContext ctx, ulong address, int maxBytes, out string value)
{
value = string.Empty;
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
if (!ctx.Memory.TryRead(address, buffer))
{
return false;
}
var length = Array.IndexOf(buffer, (byte)0);
if (length < 0)
{
length = maxBytes;
}
if (length == 0)
{
return false;
}
var text = Encoding.UTF8.GetString(buffer, 0, length);
if (!IsMostlyPrintable(text))
{
return false;
}
value = text;
return true;
}
private static bool TryReadUtf16CString(CpuContext ctx, ulong address, int maxChars, out string value)
{
value = string.Empty;
var maxBytes = maxChars * 2;
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
if (!ctx.Memory.TryRead(address, buffer))
{
return false;
}
var lengthBytes = -1;
for (var i = 0; i + 1 < buffer.Length; i += 2)
{
if (buffer[i] == 0 && buffer[i + 1] == 0)
{
lengthBytes = i;
break;
}
}
if (lengthBytes <= 0)
{
return false;
}
var text = Encoding.Unicode.GetString(buffer, 0, lengthBytes);
if (!IsMostlyPrintable(text))
{
return false;
}
value = text;
return true;
}
private static bool IsMostlyPrintable(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
var printable = 0;
for (var i = 0; i < text.Length; i++)
{
var ch = text[i];
if (ch == '\0')
{
continue;
}
if (!char.IsControl(ch) || ch == '\r' || ch == '\n' || ch == '\t')
{
printable++;
}
}
return printable >= Math.Max(4, text.Length * 3 / 4);
}
private static void TraceProcParamEmbeddedPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> data)
{
const int maxCandidates = 12;
var found = 0;
Span<byte> probe = stackalloc byte[2];
for (var offset = 0; offset + 8 <= data.Length; offset += 8)
{
var candidate = BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(offset, 8));
if (candidate == 0)
{
continue;
}
if (!ctx.Memory.TryRead(candidate, probe))
{
continue;
}
if (TryReadUtf8CString(ctx, candidate, 256, out var ascii))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} ascii \"{ascii}\"");
if (++found >= maxCandidates)
{
return;
}
continue;
}
if (TryReadUtf16CString(ctx, candidate, 128, out var wide))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} wide \"{wide}\"");
if (++found >= maxCandidates)
{
return;
}
}
}
}
[SysAbiExport(
Nid = "9BcDykPmo1I",
ExportName = "__error",
@@ -332,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",
@@ -780,6 +1063,153 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "nu4a0-arQis",
ExportName = "sceKernelAioInitializeParam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAioInitializeParam(CpuContext ctx)
{
var paramAddress = ctx[CpuRegister.Rdi];
if (paramAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> zero = stackalloc byte[AioInitParamSize];
zero.Clear();
if (!ctx.Memory.TryWrite(paramAddress, zero))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
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",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAioInitializeImpl(CpuContext ctx)
{
var paramAddress = ctx[CpuRegister.Rdi];
var size = unchecked((int)ctx[CpuRegister.Rsi]);
if (paramAddress == 0 || size < AioInitParamSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eR2bZFAAU0Q",
ExportName = "sceSysmoduleUnloadModule",
@@ -0,0 +1,318 @@
// 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 KernelSemaphoreCompatExports
{
private const int MaxSemaphoreNameLength = 128;
private static readonly ConcurrentDictionary<uint, KernelSemaphoreState> _semaphores = new();
private static int _nextSemaphoreHandle = 1;
private sealed class KernelSemaphoreState
{
public required string Name { get; init; }
public required int InitialCount { get; init; }
public required int MaxCount { get; init; }
public int Count { get; set; }
public int WaitingThreads { get; set; }
public object Gate { get; } = new();
}
[SysAbiExport(
Nid = "188x57JYp0g",
ExportName = "sceKernelCreateSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCreateSema(CpuContext ctx)
{
var semaphoreAddress = ctx[CpuRegister.Rdi];
var nameAddress = ctx[CpuRegister.Rsi];
var attr = unchecked((uint)ctx[CpuRegister.Rdx]);
var initialCount = unchecked((int)ctx[CpuRegister.Rcx]);
var maxCount = unchecked((int)ctx[CpuRegister.R8]);
var optionAddress = ctx[CpuRegister.R9];
if (semaphoreAddress == 0 ||
nameAddress == 0 ||
attr > 2 ||
initialCount < 0 ||
maxCount <= 0 ||
initialCount > maxCount ||
optionAddress != 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxSemaphoreNameLength, out var name))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
if (handle == 0)
{
handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
}
_semaphores[handle] = new KernelSemaphoreState
{
Name = name,
InitialCount = initialCount,
MaxCount = maxCount,
Count = initialCount,
};
if (!TryWriteUInt32(ctx, semaphoreAddress, handle))
{
_semaphores.TryRemove(handle, out _);
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceSemaphore($"create handle=0x{handle:X8} name='{name}' attr=0x{attr:X} init={initialCount} max={maxCount}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "Zxa0VhQVTsk",
ExportName = "sceKernelWaitSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWaitSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
var timeoutAddress = ctx[CpuRegister.Rdx];
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (needCount < 1 || needCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (semaphore.Count >= needCount)
{
semaphore.Count -= needCount;
TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
if (timeoutAddress != 0)
{
if (!TryReadUInt32(ctx, timeoutAddress, out _))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
TraceSemaphore($"wait-timeout handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
}
if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitSema"))
{
TraceSemaphore($"wait-would-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
}
semaphore.WaitingThreads++;
TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "12wOHk8ywb0",
ExportName = "sceKernelPollSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelPollSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (needCount < 1 || needCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (semaphore.Count < needCount)
{
TraceSemaphore($"poll-busy handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
}
semaphore.Count -= needCount;
TraceSemaphore($"poll handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "4czppHBiriw",
ExportName = "sceKernelSignalSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSignalSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
var signalCount = unchecked((int)ctx[CpuRegister.Rsi]);
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (signalCount <= 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (semaphore.Count > semaphore.MaxCount - signalCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
semaphore.Count += signalCount;
TraceSemaphore($"signal handle=0x{handle:X8} name='{semaphore.Name}' signal={signalCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "4DM06U2BNEY",
ExportName = "sceKernelCancelSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCancelSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
var setCount = unchecked((int)ctx[CpuRegister.Rsi]);
var waitingThreadsAddress = ctx[CpuRegister.Rdx];
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (setCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (waitingThreadsAddress != 0 && !TryWriteUInt32(ctx, waitingThreadsAddress, unchecked((uint)semaphore.WaitingThreads)))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
semaphore.WaitingThreads = 0;
TraceSemaphore($"cancel handle=0x{handle:X8} name='{semaphore.Name}' set={setCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "R1Jvn8bSCW8",
ExportName = "sceKernelDeleteSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
if (!_semaphores.TryRemove(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
TraceSemaphore($"delete handle=0x{handle:X8} name='{semaphore.Name}'");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
{
var value = (int)result;
ctx[CpuRegister.Rax] = unchecked((ulong)value);
return value;
}
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 maxLength, out string value)
{
value = string.Empty;
if (address == 0 || maxLength <= 0)
{
return false;
}
var bytes = new byte[Math.Min(maxLength, 4096)];
for (var i = 0; i < bytes.Length; i++)
{
Span<byte> current = stackalloc byte[1];
if (!ctx.Memory.TryRead(address + (ulong)i, current))
{
return false;
}
if (current[0] == 0)
{
value = Encoding.UTF8.GetString(bytes, 0, i);
return true;
}
bytes[i] = current[0];
}
value = Encoding.UTF8.GetString(bytes);
return true;
}
private static void TraceSemaphore(string message)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SEMA"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine($"[LOADER][TRACE] sema.{message}");
}
}
}
+79
View File
@@ -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;
}
}
+140
View File
@@ -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}");
}
}
+87
View File
@@ -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}");
}
}
+65
View File
@@ -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;
}
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Diagnostics;
namespace SharpEmu.Libs.Pad;
public static class PadExports
{
private const int OrbisPadErrorInvalidHandle = unchecked((int)0x80920003);
private const int OrbisPadErrorNotInitialized = unchecked((int)0x80920005);
private const int OrbisPadErrorDeviceNotConnected = unchecked((int)0x80920007);
private const int OrbisPadErrorDeviceNoHandle = unchecked((int)0x80920008);
private const int PrimaryUserId = 1;
private const int StandardPortType = 0;
private const int PrimaryPadHandle = 1;
private const int ControllerInformationSize = 0x1C;
private const int PadDataSize = 0x78;
private static bool _initialized;
[SysAbiExport(
Nid = "hv1luiJrqQM",
ExportName = "scePadInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadInit(CpuContext ctx)
{
_initialized = true;
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "xk0AcarP3V4",
ExportName = "scePadOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadOpen(CpuContext ctx)
{
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
var type = unchecked((int)ctx[CpuRegister.Rsi]);
var index = unchecked((int)ctx[CpuRegister.Rdx]);
var parameterAddress = ctx[CpuRegister.Rcx];
if (!_initialized)
{
return SetReturn(ctx, OrbisPadErrorNotInitialized);
}
if (userId == -1)
{
return SetReturn(ctx, OrbisPadErrorDeviceNoHandle);
}
if (userId != PrimaryUserId || type != StandardPortType || index != 0 || parameterAddress != 0)
{
return SetReturn(ctx, OrbisPadErrorDeviceNotConnected);
}
return SetReturn(ctx, PrimaryPadHandle);
}
[SysAbiExport(
Nid = "clVvL4ZDntw",
ExportName = "scePadSetMotionSensorState",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadSetMotionSensorState(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
return handle == PrimaryPadHandle
? SetReturn(ctx, 0)
: SetReturn(ctx, OrbisPadErrorInvalidHandle);
}
[SysAbiExport(
Nid = "gjP9-KQzoUk",
ExportName = "scePadGetControllerInformation",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadGetControllerInformation(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
var informationAddress = ctx[CpuRegister.Rsi];
if (handle != PrimaryPadHandle)
{
return SetReturn(ctx, OrbisPadErrorInvalidHandle);
}
if (informationAddress == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
Span<byte> information = stackalloc byte[ControllerInformationSize];
BinaryPrimitives.WriteSingleLittleEndian(information[0x00..], 44.86f);
BinaryPrimitives.WriteUInt16LittleEndian(information[0x04..], 1920);
BinaryPrimitives.WriteUInt16LittleEndian(information[0x06..], 943);
information[0x08] = 30;
information[0x09] = 30;
information[0x0A] = StandardPortType;
information[0x0B] = 1;
information[0x0C] = 1;
BinaryPrimitives.WriteInt32LittleEndian(information[0x10..], 0);
return ctx.Memory.TryWrite(informationAddress, information)
? SetReturn(ctx, 0)
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "YndgXqQVV7c",
ExportName = "scePadReadState",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadReadState(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
var dataAddress = ctx[CpuRegister.Rsi];
if (handle != PrimaryPadHandle)
{
return SetReturn(ctx, OrbisPadErrorInvalidHandle);
}
if (dataAddress == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
Span<byte> data = stackalloc byte[PadDataSize];
data.Clear();
data[0x04] = 128;
data[0x05] = 128;
data[0x06] = 128;
data[0x07] = 128;
BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f);
data[0x4C] = 1;
var timestampTicks = Stopwatch.GetTimestamp();
var timestampMicroseconds =
((ulong)(timestampTicks / Stopwatch.Frequency) * 1_000_000UL) +
((ulong)(timestampTicks % Stopwatch.Frequency) * 1_000_000UL / (ulong)Stopwatch.Frequency);
BinaryPrimitives.WriteUInt64LittleEndian(
data[0x50..],
timestampMicroseconds);
data[0x68] = 1;
return ctx.Memory.TryWrite(dataAddress, data)
? 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);
return result;
}
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.PlayGo;
public static class PlayGoExports
{
private const int OrbisPlayGoErrorInvalidArgument = unchecked((int)0x80B20004);
private const int OrbisPlayGoErrorNotInitialized = unchecked((int)0x80B20005);
private const int OrbisPlayGoErrorAlreadyInitialized = unchecked((int)0x80B20006);
private const int OrbisPlayGoErrorBadHandle = unchecked((int)0x80B20009);
private const int OrbisPlayGoErrorBadPointer = unchecked((int)0x80B2000A);
private const int OrbisPlayGoErrorBadSize = unchecked((int)0x80B2000B);
private const int OrbisPlayGoErrorNotSupportPlayGo = unchecked((int)0x80B2000E);
private const ulong PlayGoInitBufAddrOffset = 0;
private const ulong PlayGoInitBufSizeOffset = 8;
private const uint PlayGoMinimumInitBufferSize = 0x200000;
private const uint PlayGoHandle = 1;
private static readonly object _stateGate = new();
private static bool _initialized;
private static bool _hasPlayGoData;
[SysAbiExport(
Nid = "ts6GlZOKRrE",
ExportName = "scePlayGoInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePlayGo")]
public static int PlayGoInitialize(CpuContext ctx)
{
var initParamsAddress = ctx[CpuRegister.Rdi];
if (initParamsAddress == 0)
{
return OrbisPlayGoErrorBadPointer;
}
if (!ctx.TryReadUInt64(initParamsAddress + PlayGoInitBufAddrOffset, out var bufferAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
Span<byte> bufferSizeBytes = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(initParamsAddress + PlayGoInitBufSizeOffset, bufferSizeBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var bufferSize = BinaryPrimitives.ReadUInt32LittleEndian(bufferSizeBytes);
if (bufferAddress == 0)
{
return OrbisPlayGoErrorBadPointer;
}
if (bufferSize < PlayGoMinimumInitBufferSize)
{
return OrbisPlayGoErrorBadSize;
}
lock (_stateGate)
{
if (_initialized)
{
return OrbisPlayGoErrorAlreadyInitialized;
}
_hasPlayGoData = HasPlayGoChunkData();
_initialized = true;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "M1Gma1ocrGE",
ExportName = "scePlayGoOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePlayGo")]
public static int PlayGoOpen(CpuContext ctx)
{
var outHandleAddress = ctx[CpuRegister.Rdi];
var paramAddress = ctx[CpuRegister.Rsi];
if (outHandleAddress == 0)
{
return OrbisPlayGoErrorBadPointer;
}
if (paramAddress != 0)
{
return OrbisPlayGoErrorInvalidArgument;
}
lock (_stateGate)
{
if (!_initialized)
{
return OrbisPlayGoErrorNotInitialized;
}
if (!_hasPlayGoData)
{
return OrbisPlayGoErrorNotSupportPlayGo;
}
}
Span<byte> handleBytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(handleBytes, PlayGoHandle);
if (!ctx.Memory.TryWrite(outHandleAddress, handleBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "MPe0EeBGM-E",
ExportName = "scePlayGoTerminate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePlayGo")]
public static int PlayGoTerminate(CpuContext ctx)
{
_ = ctx;
lock (_stateGate)
{
if (!_initialized)
{
return OrbisPlayGoErrorNotInitialized;
}
_initialized = false;
_hasPlayGoData = false;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Uco1I0dlDi8",
ExportName = "scePlayGoClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePlayGo")]
public static int PlayGoClose(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
lock (_stateGate)
{
if (!_initialized)
{
return OrbisPlayGoErrorNotInitialized;
}
if (handle != PlayGoHandle)
{
return OrbisPlayGoErrorBadHandle;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool HasPlayGoChunkData()
{
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (string.IsNullOrWhiteSpace(app0Root))
{
return false;
}
var hostPath = Path.Combine(app0Root, "sce_sys", "playgo-chunk.dat");
return File.Exists(hostPath);
}
}
+6
View File
@@ -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>
@@ -0,0 +1,65 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.SystemService;
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",
ExportName = "sceSystemServiceGetStatus",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSystemService")]
public static int SystemServiceGetStatus(CpuContext ctx)
{
var statusAddress = ctx[CpuRegister.Rdi];
if (statusAddress == 0)
{
return SetReturn(ctx, OrbisSystemServiceErrorParameter);
}
Span<byte> status = stackalloc byte[SystemServiceStatusSize];
status.Clear();
BinaryPrimitives.WriteInt32LittleEndian(status, 0);
status[0x06] = 1;
return ctx.Memory.TryWrite(statusAddress, status)
? SetReturn(ctx, 0)
: 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);
return result;
}
}
@@ -0,0 +1,79 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.UserService;
public static class UserServiceExports
{
private const int OrbisUserServiceErrorInvalidArgument = unchecked((int)0x80960005);
private const int PrimaryUserId = 1;
private const int InvalidUserId = -1;
[SysAbiExport(
Nid = "j3YMu1MVNNo",
ExportName = "sceUserServiceInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceUserService")]
public static int UserServiceInitialize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "CdWp0oHWGr0",
ExportName = "sceUserServiceGetInitialUser",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceUserService")]
public static int UserServiceGetInitialUser(CpuContext ctx)
{
var userIdAddress = ctx[CpuRegister.Rdi];
if (userIdAddress == 0)
{
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
}
return TryWriteInt32(ctx, userIdAddress, PrimaryUserId)
? SetReturn(ctx, 0)
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "fPhymKNvK-A",
ExportName = "sceUserServiceGetLoginUserIdList",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceUserService")]
public static int UserServiceGetLoginUserIdList(CpuContext ctx)
{
var userIdListAddress = ctx[CpuRegister.Rdi];
if (userIdListAddress == 0)
{
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
}
Span<byte> userIds = stackalloc byte[sizeof(int) * 4];
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x00..], PrimaryUserId);
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x04..], InvalidUserId);
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x08..], InvalidUserId);
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x0C..], InvalidUserId);
return ctx.Memory.TryWrite(userIdListAddress, userIds)
? SetReturn(ctx, 0)
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+85
View File
@@ -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"
}