mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 04:39:17 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 994438963d | |||
| 233af123af | |||
| 02eb9b30e9 | |||
| fa46819030 | |||
| 7a915e88dc | |||
| 812879aa81 | |||
| 71ba5cf1db | |||
| 8f108ca01d | |||
| cecf55bf2e | |||
| 0ede7c70a9 | |||
| 8c39a55501 | |||
| 0a4b36ffcb | |||
| 1a1d61b21c | |||
| 3cf96f38de | |||
| d8c44bb64f | |||
| a4b5171586 |
@@ -0,0 +1,59 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Bug Report
|
||||
description: Report a bug in the emulator
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Before creating an issue
|
||||
|
||||
- Do not create issues involving **software piracy**. Our rules specifically prohibit this.
|
||||
Issues related to piracy will be closed and the author may be banned from the repository.
|
||||
|
||||
- The issue tracker is **not for asking questions**.
|
||||
Question-type issues will be closed.
|
||||
|
||||
- Only open an issue if you are reporting an actual **bug in the emulator**.
|
||||
|
||||
- type: input
|
||||
id: emulator_version
|
||||
attributes:
|
||||
label: Emulator Version / Commit
|
||||
description: The emulator version or git commit where the bug occurred
|
||||
placeholder: e.g. v0.0.2 / a1b2c3d
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: Describe the problem and how to reproduce it
|
||||
placeholder: |
|
||||
Steps to reproduce:
|
||||
1. Launch emulator
|
||||
2. Load game
|
||||
3. Crash occurs
|
||||
|
||||
Expected behavior:
|
||||
...
|
||||
|
||||
Actual behavior:
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log File
|
||||
description: |
|
||||
Drag & drop your log file here, or paste the log contents.
|
||||
|
||||
Supported: .log / .txt / .zip
|
||||
Large logs should be uploaded as a file.
|
||||
render: shell
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
blank_issues_enabled: false
|
||||
@@ -1,3 +1,6 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Game Compatibility Report
|
||||
description: Report compatibility status of a game
|
||||
labels: ["game-compatibility"]
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
@@ -79,7 +82,7 @@ jobs:
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
dotnet-version: 10.0.103
|
||||
cache: true
|
||||
cache-dependency-path: |
|
||||
Directory.Packages.props
|
||||
@@ -106,7 +109,7 @@ jobs:
|
||||
Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ needs.init.outputs.artifact-name }}
|
||||
path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }}
|
||||
@@ -123,7 +126,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ needs.init.outputs.artifact-name }}
|
||||
path: release
|
||||
|
||||
@@ -3,6 +3,7 @@ version = 1
|
||||
[[annotations]]
|
||||
path = [
|
||||
"REUSE.toml",
|
||||
"global.json",
|
||||
"**/packages.lock.json",
|
||||
"scripts/ps5_names.txt",
|
||||
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.103",
|
||||
"rollForward": "disable"
|
||||
}
|
||||
}
|
||||
@@ -154,15 +154,57 @@ public sealed partial class DirectExecutionBackend
|
||||
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)
|
||||
@@ -212,28 +254,6 @@ 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();
|
||||
}
|
||||
ProbeReturnRip(0x0000000800EA020Eul, 999001);
|
||||
ProbeReturnRip(0x0000000800EA0213ul, 999002);
|
||||
ProbeReturnRip(0x0000000800EA040Aul, 999003);
|
||||
try
|
||||
{
|
||||
OrbisGen2Result orbisGen2Result;
|
||||
@@ -426,7 +446,20 @@ public sealed partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3);
|
||||
return num2 <= 2;
|
||||
if (num2 > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num3 = Math.Min(_importLoopSignatureCount, Math.Max(sampleCount * 8, ImportLoopWideDiversityWindow));
|
||||
if (num3 <= sampleCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, num3, 3) > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, num3, 3) <= 2;
|
||||
}
|
||||
|
||||
private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter)
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable
|
||||
{
|
||||
private const int ImportLoopHistoryLength = 2048;
|
||||
|
||||
private const int ImportLoopWideDiversityWindow = 768;
|
||||
|
||||
private readonly struct ImportStubEntry
|
||||
{
|
||||
public ulong Address { get; }
|
||||
@@ -89,7 +93,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private const uint PAGE_EXECUTE_READ = 32u;
|
||||
|
||||
private const int TlsHandlerRegionSize = 4096;
|
||||
private const int TlsHandlerRegionSize = 16384;
|
||||
|
||||
private const ulong TlsModuleAllocStart = 140726751354880uL;
|
||||
|
||||
@@ -179,11 +183,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private ulong _entryReturnSentinelRip;
|
||||
|
||||
private readonly ulong[] _importLoopSignatures = new ulong[192];
|
||||
private readonly ulong[] _importLoopSignatures = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private readonly ulong[] _importLoopNidHashes = new ulong[192];
|
||||
private readonly ulong[] _importLoopNidHashes = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private readonly ulong[] _importLoopReturnRips = new ulong[192];
|
||||
private readonly ulong[] _importLoopReturnRips = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private int _importLoopSignatureCount;
|
||||
|
||||
@@ -601,14 +605,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
return exportName switch
|
||||
{
|
||||
"_init_env" or
|
||||
"atexit" or
|
||||
"strlen" or
|
||||
"strnlen" or
|
||||
"strcmp" or
|
||||
"strncmp" or
|
||||
"strcpy" or
|
||||
"strncpy" or
|
||||
"memcpy" or
|
||||
"memmove" or
|
||||
"memset" or
|
||||
|
||||
@@ -40,6 +40,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize);
|
||||
|
||||
@@ -351,6 +354,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
|
||||
{
|
||||
return false;
|
||||
@@ -389,6 +397,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
|
||||
{
|
||||
return false;
|
||||
@@ -494,6 +507,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
private static uint GetCommitProtection(MemoryRegion region)
|
||||
{
|
||||
return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
}
|
||||
|
||||
private static unsafe bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region)
|
||||
{
|
||||
if (size == 0 || !region.IsReservedOnly)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var startPage = AlignDown(address, PageSize);
|
||||
var endPage = AlignUp(address + size, PageSize);
|
||||
var commitProtection = GetCommitProtection(region);
|
||||
|
||||
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
|
||||
{
|
||||
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.State == MEM_COMMIT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.State != MEM_RESERVE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VirtualAlloc((void*)pageAddress, (nuint)PageSize, MEM_COMMIT, commitProtection) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTemporarilyProtectForRead(
|
||||
ulong address,
|
||||
ulong size,
|
||||
@@ -558,4 +613,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
public bool IsReservedOnly { get; set; }
|
||||
public uint Protection { get; set; }
|
||||
}
|
||||
|
||||
private struct MemoryBasicInformation64
|
||||
{
|
||||
public ulong BaseAddress;
|
||||
public ulong AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public uint Alignment1;
|
||||
public ulong RegionSize;
|
||||
public uint State;
|
||||
public uint Protect;
|
||||
public uint Type;
|
||||
public uint Alignment2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
LastBasicBlockTrace = null;
|
||||
LastMilestoneLog = null;
|
||||
KernelModuleRegistry.Reset();
|
||||
var image = LoadImage(ebootPath);
|
||||
var normalizedEbootPath = Path.GetFullPath(ebootPath);
|
||||
var image = LoadImage(normalizedEbootPath);
|
||||
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
||||
Console.Error.WriteLine($"[RUNTIME] Entry: 0x{image.EntryPoint:X16}");
|
||||
@@ -350,6 +351,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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,22 +264,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 +282,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
@@ -3,6 +3,7 @@
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Threading;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
@@ -11,7 +12,9 @@ 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 int MutexTypeNormal = 3;
|
||||
private const int MutexTypeAdaptiveNp = 4;
|
||||
private const ulong StaticAdaptiveMutexInitializer = 1;
|
||||
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
|
||||
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
|
||||
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
|
||||
@@ -86,18 +89,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",
|
||||
@@ -169,6 +168,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",
|
||||
@@ -232,6 +237,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",
|
||||
@@ -258,14 +270,14 @@ public static class KernelPthreadCompatExports
|
||||
ExportName = "scePthreadCondSignal",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: false);
|
||||
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JGgj7Uvrl+A",
|
||||
ExportName = "scePthreadCondBroadcast",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
|
||||
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Op8TBGY5KHg",
|
||||
@@ -279,7 +291,14 @@ public static class KernelPthreadCompatExports
|
||||
ExportName = "pthread_cond_broadcast",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
|
||||
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",
|
||||
@@ -402,6 +421,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;
|
||||
@@ -606,7 +642,7 @@ public static class KernelPthreadCompatExports
|
||||
return mutexAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, out PthreadMutexState? state)
|
||||
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
@@ -629,6 +665,11 @@ public static class KernelPthreadCompatExports
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pointedHandle == StaticAdaptiveMutexInitializer)
|
||||
{
|
||||
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeAdaptiveNp, out resolvedAddress, out state);
|
||||
}
|
||||
|
||||
if (pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
@@ -651,18 +692,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)
|
||||
@@ -739,7 +769,7 @@ public static class KernelPthreadCompatExports
|
||||
return condAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, out PthreadCondState? state)
|
||||
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadCondState? state)
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
@@ -901,14 +931,14 @@ public static class KernelPthreadCompatExports
|
||||
return waitResult;
|
||||
}
|
||||
|
||||
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast)
|
||||
private static int PthreadCondSignalCore(CpuContext ctx, ulong condAddress, bool broadcast)
|
||||
{
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryResolveCondState(null, condAddress, createIfZero: false, out _, out var state))
|
||||
if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
@@ -952,11 +982,43 @@ 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,
|
||||
};
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexStates.TryGetValue(mutexAddress, out state))
|
||||
{
|
||||
resolvedAddress = mutexAddress;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_mutexStates.TryGetValue(syntheticHandle, out state))
|
||||
{
|
||||
resolvedAddress = syntheticHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
_mutexStates[mutexAddress] = createdState;
|
||||
_mutexStates[syntheticHandle] = createdState;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
state = createdState;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
|
||||
{
|
||||
if (!ShouldTracePthread())
|
||||
|
||||
@@ -5,6 +5,7 @@ using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
@@ -23,13 +24,12 @@ public static class KernelPthreadExtendedCompatExports
|
||||
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;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<int, ulong>? _threadLocalSpecific;
|
||||
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new();
|
||||
|
||||
private sealed class ThreadState
|
||||
{
|
||||
@@ -40,6 +40,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(
|
||||
@@ -599,10 +640,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;
|
||||
}
|
||||
@@ -633,14 +673,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)
|
||||
@@ -648,8 +684,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;
|
||||
}
|
||||
@@ -702,29 +754,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;
|
||||
@@ -793,9 +854,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;
|
||||
}
|
||||
@@ -816,16 +881,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;
|
||||
}
|
||||
@@ -845,6 +917,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 _))
|
||||
@@ -852,12 +926,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;
|
||||
}
|
||||
|
||||
@@ -880,21 +957,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;
|
||||
}
|
||||
@@ -928,7 +1031,7 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return rwlockAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, 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;
|
||||
@@ -973,7 +1076,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)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ public static class KernelRuntimeCompatExports
|
||||
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;
|
||||
@@ -780,6 +781,48 @@ 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 = "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,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);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,408 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
public static class VideoOutExports
|
||||
{
|
||||
private const int OrbisVideoOutErrorInvalidValue = unchecked((int)0x80290001);
|
||||
private const int OrbisVideoOutErrorInvalidAddress = unchecked((int)0x80290002);
|
||||
private const int OrbisVideoOutErrorResourceBusy = unchecked((int)0x80290009);
|
||||
private const int OrbisVideoOutErrorInvalidIndex = unchecked((int)0x8029000A);
|
||||
private const int OrbisVideoOutErrorInvalidHandle = unchecked((int)0x8029000B);
|
||||
private const int OrbisVideoOutErrorInvalidOption = unchecked((int)0x8029001A);
|
||||
private const int SceVideoOutBusTypeMain = 0;
|
||||
private const int SceVideoOutBufferAttributeOptionNone = 0;
|
||||
private const int MaxOpenPorts = 4;
|
||||
private const int MaxDisplayBuffers = 16;
|
||||
private const int VideoOutBufferAttributeSize = 0x24;
|
||||
private const int VideoOutBufferAttribute2Size = 0x50;
|
||||
private const int VideoOutBuffersEntrySize = 0x20;
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
private static readonly Dictionary<int, VideoOutPortState> _ports = new();
|
||||
private static int _nextHandle = 1;
|
||||
|
||||
private sealed class VideoOutPortState
|
||||
{
|
||||
public required int Handle { get; init; }
|
||||
public int FlipRate { get; set; }
|
||||
public ulong VblankCount { get; set; }
|
||||
public int NextSetId { get; set; } = 1;
|
||||
public HashSet<int> RegisteredSetIds { get; } = new();
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Up36PTk687E",
|
||||
ExportName = "sceVideoOutOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutOpen(CpuContext ctx)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var busType = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var index = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
_ = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (busType != SceVideoOutBusTypeMain || index != 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
if (userId != 0 && userId != 255)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_ports.Count >= MaxOpenPorts)
|
||||
{
|
||||
return OrbisVideoOutErrorResourceBusy;
|
||||
}
|
||||
|
||||
var handle = _nextHandle++;
|
||||
_ports[handle] = new VideoOutPortState
|
||||
{
|
||||
Handle = handle,
|
||||
};
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uquVH4-Du78",
|
||||
ExportName = "sceVideoOutClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutClose(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_ports.Remove(handle);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "CBiu4mCE1DA",
|
||||
ExportName = "sceVideoOutSetFlipRate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutSetFlipRate(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var rate = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
if (rate is < 0 or > 2)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
port.FlipRate = rate;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "j6RaAUlaLv0",
|
||||
ExportName = "sceVideoOutWaitVblank",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutWaitVblank(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
lock (_stateGate)
|
||||
{
|
||||
port.VblankCount++;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "MTxxrOCeSig",
|
||||
ExportName = "sceVideoOutSetWindowModeMargins",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutSetWindowModeMargins(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
_ = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
_ = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
|
||||
return TryGetPort(handle, out _)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "N5KDtkIjjJ4",
|
||||
ExportName = "sceVideoOutUnregisterBuffers",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutUnregisterBuffers(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var attributeIndex = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
if (attributeIndex < 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
return port.RegisteredSetIds.Remove(attributeIndex)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "i6-sR91Wt-4",
|
||||
ExportName = "sceVideoOutSetBufferAttribute",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutSetBufferAttribute(CpuContext ctx)
|
||||
{
|
||||
var attributeAddress = ctx[CpuRegister.Rdi];
|
||||
var pixelFormat = unchecked((uint)ctx[CpuRegister.Rsi]);
|
||||
var tilingMode = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var aspectRatio = unchecked((uint)ctx[CpuRegister.Rcx]);
|
||||
var width = unchecked((uint)ctx[CpuRegister.R8]);
|
||||
var height = unchecked((uint)ctx[CpuRegister.R9]);
|
||||
if (!TryReadStackUInt32(ctx, 0, out var pitchInPixel))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (attributeAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidAddress;
|
||||
}
|
||||
|
||||
Span<byte> attribute = stackalloc byte[VideoOutBufferAttributeSize];
|
||||
attribute.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x00..0x04], pixelFormat);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x04..0x08], tilingMode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x08..0x0C], aspectRatio);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x0C..0x10], width);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x10..0x14], height);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x14..0x18], pitchInPixel);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x18..0x1C], SceVideoOutBufferAttributeOptionNone);
|
||||
if (!ctx.Memory.TryWrite(attributeAddress, attribute))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "PjS5uASwcV8",
|
||||
ExportName = "sceVideoOutSetBufferAttribute2",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutSetBufferAttribute2(CpuContext ctx)
|
||||
{
|
||||
var attributeAddress = ctx[CpuRegister.Rdi];
|
||||
var pixelFormat = ctx[CpuRegister.Rsi];
|
||||
var tilingMode = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var width = unchecked((uint)ctx[CpuRegister.Rcx]);
|
||||
var height = unchecked((uint)ctx[CpuRegister.R8]);
|
||||
var option = ctx[CpuRegister.R9];
|
||||
if (!TryReadStackUInt32(ctx, 0, out var dccControl) ||
|
||||
!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + 0x10, out var dccClearColor))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (attributeAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidAddress;
|
||||
}
|
||||
|
||||
Span<byte> attribute = stackalloc byte[VideoOutBufferAttribute2Size];
|
||||
attribute.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x04..0x08], tilingMode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x0C..0x10], width);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x10..0x14], height);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(attribute[0x18..0x20], option);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(attribute[0x20..0x28], pixelFormat);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(attribute[0x28..0x30], dccClearColor);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute[0x30..0x34], dccControl);
|
||||
if (!ctx.Memory.TryWrite(attributeAddress, attribute))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w3BY+tAEiQY",
|
||||
ExportName = "sceVideoOutRegisterBuffers",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutRegisterBuffers(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var startIndex = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var addressesAddress = ctx[CpuRegister.Rdx];
|
||||
var bufferNum = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
var attributeAddress = ctx[CpuRegister.R8];
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
if (addressesAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidAddress;
|
||||
}
|
||||
|
||||
if (attributeAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidOption;
|
||||
}
|
||||
|
||||
if (!IsValidBufferRange(startIndex, bufferNum))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < bufferNum; i++)
|
||||
{
|
||||
if (!ctx.TryReadUInt64(addressesAddress + ((ulong)i * 8), out _))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
var setId = AllocateBufferSet(port);
|
||||
return setId;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "rKBUtgRrtbk",
|
||||
ExportName = "sceVideoOutRegisterBuffers2",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutRegisterBuffers2(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var setIndex = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var bufferIndexStart = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
var buffersAddress = ctx[CpuRegister.Rcx];
|
||||
var bufferNum = unchecked((int)ctx[CpuRegister.R8]);
|
||||
var attributeAddress = ctx[CpuRegister.R9];
|
||||
if (!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + 0x08, out var categoryRaw) ||
|
||||
!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + 0x10, out var option))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
if (buffersAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidAddress;
|
||||
}
|
||||
|
||||
if (attributeAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidOption;
|
||||
}
|
||||
|
||||
if (!IsValidBufferRange(bufferIndexStart, bufferNum))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
if (categoryRaw != 0 || option != 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < bufferNum; i++)
|
||||
{
|
||||
var entryAddress = buffersAddress + ((ulong)i * VideoOutBuffersEntrySize);
|
||||
if (!ctx.TryReadUInt64(entryAddress + 0x00, out _) ||
|
||||
!ctx.TryReadUInt64(entryAddress + 0x08, out _))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
port.RegisteredSetIds.Add(setIndex);
|
||||
}
|
||||
|
||||
return setIndex;
|
||||
}
|
||||
|
||||
private static int AllocateBufferSet(VideoOutPortState port)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
var setId = port.NextSetId++;
|
||||
port.RegisteredSetIds.Add(setId);
|
||||
return setId;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidBufferRange(int startIndex, int bufferNum)
|
||||
{
|
||||
return startIndex >= 0 &&
|
||||
startIndex < MaxDisplayBuffers &&
|
||||
bufferNum >= 1 &&
|
||||
bufferNum <= MaxDisplayBuffers &&
|
||||
startIndex + bufferNum <= MaxDisplayBuffers;
|
||||
}
|
||||
|
||||
private static bool TryGetPort(int handle, [NotNullWhen(true)] out VideoOutPortState? port)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
return _ports.TryGetValue(handle, out port);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryReadStackUInt32(CpuContext ctx, int stackIndex, out uint value)
|
||||
{
|
||||
var address = ctx[CpuRegister.Rsp] + 0x08 + ((ulong)stackIndex * 0x08);
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user