mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 04:39:17 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c859f04ad | |||
| 994438963d | |||
| 233af123af | |||
| 02eb9b30e9 | |||
| fa46819030 | |||
| 7a915e88dc | |||
| 812879aa81 | |||
| 71ba5cf1db | |||
| 8f108ca01d |
@@ -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 }}
|
||||
|
||||
@@ -29,6 +29,8 @@ arm64/
|
||||
packages/
|
||||
*.nupkg
|
||||
.nuget/
|
||||
.dotnet-home/
|
||||
.cache/
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.103",
|
||||
"rollForward": "latestFeature"
|
||||
"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,25 +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();
|
||||
}
|
||||
try
|
||||
{
|
||||
OrbisGen2Result orbisGen2Result;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
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 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)
|
||||
{
|
||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out var buffer, out var capacity, out var state) || state is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<byte> record = stackalloc byte[(int)ReadFileRecordSize];
|
||||
record.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], 1);
|
||||
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);
|
||||
|
||||
lock (state)
|
||||
{
|
||||
if (state.Buffer == 0 || state.WriteOffset + ReadFileRecordSize > state.Size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ctx.Memory.TryWrite(state.Buffer + state.WriteOffset, record))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
state.WriteOffset += ReadFileRecordSize;
|
||||
}
|
||||
|
||||
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,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,197 @@
|
||||
// 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 static readonly object _eventQueueGate = new();
|
||||
private static readonly HashSet<ulong> _eventQueues = new();
|
||||
private static long _nextEventQueueHandle = 1;
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 outCountAddress = ctx[CpuRegister.Rcx];
|
||||
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceEventQueue(ctx, "wait", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -12,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;
|
||||
@@ -87,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",
|
||||
@@ -170,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",
|
||||
@@ -233,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",
|
||||
@@ -282,6 +293,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",
|
||||
@@ -403,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;
|
||||
@@ -630,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)
|
||||
@@ -652,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)
|
||||
@@ -953,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())
|
||||
|
||||
@@ -24,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
|
||||
{
|
||||
@@ -41,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(
|
||||
@@ -600,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;
|
||||
}
|
||||
@@ -634,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)
|
||||
@@ -649,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;
|
||||
}
|
||||
@@ -703,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;
|
||||
@@ -794,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;
|
||||
}
|
||||
@@ -817,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;
|
||||
}
|
||||
@@ -846,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 _))
|
||||
@@ -853,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;
|
||||
}
|
||||
|
||||
@@ -881,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;
|
||||
}
|
||||
@@ -929,7 +1031,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 +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;
|
||||
@@ -257,6 +258,8 @@ public static class KernelRuntimeCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
TraceProcParam(ctx, address);
|
||||
|
||||
ctx[CpuRegister.Rax] = address;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -269,6 +272,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",
|
||||
@@ -780,6 +1000,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