mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 15:36:11 +08:00
PlayGo, VideoOut minimum HLE implements and fix some direct runner
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,84 +290,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelMemoryCompatExports
|
||||
{
|
||||
private const int MaxGuestStringLength = 4096;
|
||||
private const int WideCharSize = sizeof(ushort);
|
||||
private const int MemsetChunkSize = 16 * 1024;
|
||||
private const int TlsModuleBlockSize = 0x10000;
|
||||
private const int O_WRONLY = 0x1;
|
||||
@@ -51,6 +53,26 @@ public static class KernelMemoryCompatExports
|
||||
private const int Enomem = 12;
|
||||
private const int Einval = 22;
|
||||
private const nuint DefaultLibcHeapAlignment = 16;
|
||||
private const ushort KernelStatModeDirectory = 0x41FF;
|
||||
private const ushort KernelStatModeRegular = 0x81FF;
|
||||
private const int KernelStatSize = 120;
|
||||
private const int KernelStatStDevOffset = 0;
|
||||
private const int KernelStatStInoOffset = 4;
|
||||
private const int KernelStatStModeOffset = 8;
|
||||
private const int KernelStatStNlinkOffset = 10;
|
||||
private const int KernelStatStUidOffset = 12;
|
||||
private const int KernelStatStGidOffset = 16;
|
||||
private const int KernelStatStRdevOffset = 20;
|
||||
private const int KernelStatStAtimOffset = 24;
|
||||
private const int KernelStatStMtimOffset = 40;
|
||||
private const int KernelStatStCtimOffset = 56;
|
||||
private const int KernelStatStSizeOffset = 72;
|
||||
private const int KernelStatStBlocksOffset = 80;
|
||||
private const int KernelStatStBlksizeOffset = 88;
|
||||
private const int KernelStatStFlagsOffset = 92;
|
||||
private const int KernelStatStGenOffset = 96;
|
||||
private const int KernelStatStLspareOffset = 100;
|
||||
private const int KernelStatStBirthtimOffset = 104;
|
||||
|
||||
private static readonly object _fdGate = new();
|
||||
private static readonly Dictionary<int, FileStream> _openFiles = new();
|
||||
@@ -232,8 +254,24 @@ public static class KernelMemoryCompatExports
|
||||
LibraryName = "libc")]
|
||||
public static int Wcslen(CpuContext ctx)
|
||||
{
|
||||
if (!TryReadWideCString(ctx, ctx[CpuRegister.Rdi], 1_048_576, out var units))
|
||||
var address = ctx[CpuRegister.Rdi];
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_WIDE"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
Span<byte> probe = stackalloc byte[32];
|
||||
if (TryReadCompat(ctx, address, probe))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] wcslen probe @0x{address:X16}: {Convert.ToHexString(probe).ToLowerInvariant()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] wcslen probe @0x{address:X16}: <unreadable>");
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryReadWideCString(ctx, address, 1_048_576, out var units))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARN] wcslen: unreadable string at 0x{address:X16}");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
@@ -468,21 +506,21 @@ public static class KernelMemoryCompatExports
|
||||
var destination = ctx[CpuRegister.Rdi];
|
||||
var source = ctx[CpuRegister.Rsi];
|
||||
var count = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
|
||||
if (count < 0 || count > (int.MaxValue / sizeof(ushort)))
|
||||
if (count < 0 || count > (int.MaxValue / WideCharSize))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var payload = new byte[count * sizeof(ushort)];
|
||||
var payload = new byte[count * WideCharSize];
|
||||
for (var copied = 0; copied < count; copied++)
|
||||
{
|
||||
if (!TryReadUInt16Compat(ctx, source + ((ulong)copied * sizeof(ushort)), out var unit))
|
||||
if (!TryReadUInt16Compat(ctx, source + ((ulong)copied * WideCharSize), out var unit))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(
|
||||
payload.AsSpan(copied * sizeof(ushort), sizeof(ushort)),
|
||||
payload.AsSpan(copied * WideCharSize, WideCharSize),
|
||||
unit);
|
||||
|
||||
if (unit == 0)
|
||||
@@ -511,14 +549,14 @@ public static class KernelMemoryCompatExports
|
||||
var needle = unchecked((ushort)ctx[CpuRegister.Rsi]);
|
||||
for (ulong index = 0; index < 1_048_576; index++)
|
||||
{
|
||||
if (!TryReadUInt16Compat(ctx, address + (index * sizeof(ushort)), out var unit))
|
||||
if (!TryReadUInt16Compat(ctx, address + (index * WideCharSize), out var unit))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (unit == needle)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = address + (index * sizeof(ushort));
|
||||
ctx[CpuRegister.Rax] = address + (index * WideCharSize);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -814,6 +852,38 @@ public static class KernelMemoryCompatExports
|
||||
var mode = ResolveOpenMode(flags, access);
|
||||
try
|
||||
{
|
||||
var wantsDirectory = (flags & O_DIRECTORY) != 0;
|
||||
if (wantsDirectory || Directory.Exists(hostPath))
|
||||
{
|
||||
if (!Directory.Exists(hostPath))
|
||||
{
|
||||
LogOpenTrace($"_open miss path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} directory=1");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (access != FileAccess.Read || (flags & (O_CREAT | O_TRUNC | O_APPEND)) != 0)
|
||||
{
|
||||
LogOpenTrace($"_open invalid-dir path='{guestPath}' host='{hostPath}' flags=0x{flags:X8}");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var directoryFd = (int)Interlocked.Increment(ref _nextFileDescriptor);
|
||||
lock (_fdGate)
|
||||
{
|
||||
_openDirectories[directoryFd] = new OpenDirectory
|
||||
{
|
||||
Path = hostPath,
|
||||
Entries = EnumerateDirectoryEntries(hostPath),
|
||||
NextIndex = 0
|
||||
};
|
||||
}
|
||||
|
||||
LogOpenTrace($"_open dir path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={directoryFd}");
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)directoryFd);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
EnsureOpenParentDirectoryExists(guestPath, hostPath, flags);
|
||||
var stream = new FileStream(hostPath, mode, access, FileShare.ReadWrite);
|
||||
if ((flags & O_APPEND) != 0)
|
||||
{
|
||||
@@ -826,16 +896,16 @@ public static class KernelMemoryCompatExports
|
||||
_openFiles[fd] = stream;
|
||||
}
|
||||
|
||||
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)fd);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
catch (IOException)
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
LogOpenTrace($"_open fail path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} ex={ex.GetType().Name}: {ex.Message}");
|
||||
return ex is UnauthorizedAccessException
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,9 +914,69 @@ public static class KernelMemoryCompatExports
|
||||
ExportName = "_close",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCloseUnderscore(CpuContext ctx)
|
||||
public static int KernelCloseUnderscore(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "UK2Tl2DWUns",
|
||||
ExportName = "sceKernelClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelClose(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "eV9wAD2riIA",
|
||||
ExportName = "sceKernelStat",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelStat(CpuContext ctx)
|
||||
{
|
||||
var pathAddress = ctx[CpuRegister.Rdi];
|
||||
var statAddress = ctx[CpuRegister.Rsi];
|
||||
if (pathAddress == 0 || statAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var hostPath = ResolveGuestPath(guestPath);
|
||||
if (!TryWriteHostPathStat(ctx, statAddress, hostPath))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "kBwCPsYX-m4",
|
||||
ExportName = "sceKernelFstat",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelFstat(CpuContext ctx)
|
||||
{
|
||||
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var statAddress = ctx[CpuRegister.Rsi];
|
||||
if (statAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryWriteOpenDescriptorStat(ctx, fd, statAddress))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int KernelCloseCore(CpuContext ctx, int fd)
|
||||
{
|
||||
if (fd is 0 or 1 or 2)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
@@ -856,7 +986,15 @@ public static class KernelMemoryCompatExports
|
||||
FileStream? stream;
|
||||
lock (_fdGate)
|
||||
{
|
||||
if (!_openFiles.Remove(fd, out stream))
|
||||
if (_openFiles.Remove(fd, out stream))
|
||||
{
|
||||
}
|
||||
else if (_openDirectories.Remove(fd))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
@@ -910,6 +1048,36 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "taRWhTJFTgE",
|
||||
ExportName = "sceKernelGetdirentries",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelGetdirentries(CpuContext ctx)
|
||||
{
|
||||
return KernelGetdirentriesCore(
|
||||
ctx,
|
||||
unchecked((int)ctx[CpuRegister.Rdi]),
|
||||
ctx[CpuRegister.Rsi],
|
||||
unchecked((int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue)),
|
||||
ctx[CpuRegister.Rcx]);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "j2AIqSqJP0w",
|
||||
ExportName = "sceKernelGetdents",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelGetdents(CpuContext ctx)
|
||||
{
|
||||
return KernelGetdirentriesCore(
|
||||
ctx,
|
||||
unchecked((int)ctx[CpuRegister.Rdi]),
|
||||
ctx[CpuRegister.Rsi],
|
||||
unchecked((int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue)),
|
||||
0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "FxVZqBAA7ks",
|
||||
ExportName = "_write",
|
||||
@@ -1883,9 +2051,9 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static string FormatString(CpuContext ctx, string format)
|
||||
internal static string FormatStringFromVarArgs(CpuContext ctx, string format, int firstGpArgIndex)
|
||||
{
|
||||
var gpIndex = 3; // RDI, RSI, RDX used by snprintf itself, args start at RCX (index 3)
|
||||
var gpIndex = Math.Max(0, firstGpArgIndex);
|
||||
|
||||
ulong GetGpArg(int index)
|
||||
{
|
||||
@@ -1911,6 +2079,11 @@ public static class KernelMemoryCompatExports
|
||||
return FormatString(ctx, format, NextGpArg, NextFloatArg);
|
||||
}
|
||||
|
||||
private static string FormatString(CpuContext ctx, string format)
|
||||
{
|
||||
return FormatStringFromVarArgs(ctx, format, firstGpArgIndex: 3);
|
||||
}
|
||||
|
||||
private static string FormatString(
|
||||
CpuContext ctx,
|
||||
string format,
|
||||
@@ -2137,6 +2310,20 @@ public static class KernelMemoryCompatExports
|
||||
{
|
||||
sb.Append("(null)");
|
||||
}
|
||||
else if (lengthMod == "l")
|
||||
{
|
||||
if (TryReadWideCString(ctx, strAddr, 1_048_576, out var wideUnits))
|
||||
{
|
||||
var str = DecodeWideUnits(wideUnits);
|
||||
if (precision >= 0 && str.Length > precision)
|
||||
str = str.Substring(0, precision);
|
||||
sb.Append(PadString(str, width, leftAlign, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("(null)");
|
||||
}
|
||||
}
|
||||
else if (TryReadCString(ctx, strAddr, 1_048_576, out var strBytes))
|
||||
{
|
||||
var str = Encoding.UTF8.GetString(strBytes);
|
||||
@@ -2153,8 +2340,20 @@ public static class KernelMemoryCompatExports
|
||||
|
||||
case 'c':
|
||||
{
|
||||
var ch = (char)(byte)nextGpArg();
|
||||
sb.Append(PadString(ch.ToString(), width, leftAlign, false));
|
||||
string renderedChar;
|
||||
if (lengthMod == "l")
|
||||
{
|
||||
var scalar = unchecked((ushort)nextGpArg());
|
||||
renderedChar = TryConvertWideScalarToString(scalar, out var wideCharText)
|
||||
? wideCharText
|
||||
: "?";
|
||||
}
|
||||
else
|
||||
{
|
||||
renderedChar = ((char)(byte)nextGpArg()).ToString();
|
||||
}
|
||||
|
||||
sb.Append(PadString(renderedChar, width, leftAlign, false));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2510,6 +2709,37 @@ public static class KernelMemoryCompatExports
|
||||
return guestPath;
|
||||
}
|
||||
|
||||
var devlogAppRoot = ResolveDevlogAppRoot();
|
||||
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = guestPath["/devlog/app/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
||||
return Path.Combine(devlogAppRoot, relative);
|
||||
}
|
||||
|
||||
if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = guestPath["devlog/app/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
||||
return Path.Combine(devlogAppRoot, relative);
|
||||
}
|
||||
|
||||
if (string.Equals(guestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return devlogAppRoot;
|
||||
}
|
||||
|
||||
var temp0Root = ResolveTemp0Root();
|
||||
if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = guestPath["/temp0/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
||||
return Path.Combine(temp0Root, relative);
|
||||
}
|
||||
|
||||
if (string.Equals(guestPath, "/temp0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return temp0Root;
|
||||
}
|
||||
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
if (!string.IsNullOrWhiteSpace(app0Root))
|
||||
{
|
||||
@@ -2529,6 +2759,71 @@ public static class KernelMemoryCompatExports
|
||||
return guestPath;
|
||||
}
|
||||
|
||||
private static string ResolveDevlogAppRoot()
|
||||
{
|
||||
var configuredRoot = Environment.GetEnvironmentVariable("SHARPEMU_DEVLOG_APP_DIR");
|
||||
string root;
|
||||
if (!string.IsNullOrWhiteSpace(configuredRoot))
|
||||
{
|
||||
root = Path.GetFullPath(configuredRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
root = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs", "devlog", "app"));
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hostPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldCreateParent =
|
||||
(flags & O_CREAT) != 0 ||
|
||||
guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase) ||
|
||||
guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase);
|
||||
if (!shouldCreateParent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parentDirectory = Path.GetDirectoryName(hostPath);
|
||||
if (!string.IsNullOrWhiteSpace(parentDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(parentDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes)
|
||||
{
|
||||
bytes = Array.Empty<byte>();
|
||||
@@ -2602,7 +2897,7 @@ public static class KernelMemoryCompatExports
|
||||
var buffer = new List<ushort>(Math.Min(limit, 256));
|
||||
for (var i = 0; i < limit; i++)
|
||||
{
|
||||
if (!TryReadUInt16Compat(ctx, address + ((ulong)i * sizeof(ushort)), out var unit))
|
||||
if (!TryReadUInt16Compat(ctx, address + ((ulong)i * WideCharSize), out var unit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2631,13 +2926,13 @@ public static class KernelMemoryCompatExports
|
||||
var max = limit == ulong.MaxValue ? 1_048_576UL : Math.Min(limit, 1_048_576UL);
|
||||
for (ulong i = 0; i < max; i++)
|
||||
{
|
||||
if (!TryReadUInt16Compat(ctx, left + (i * sizeof(ushort)), out var leftUnit) ||
|
||||
!TryReadUInt16Compat(ctx, right + (i * sizeof(ushort)), out var rightUnit))
|
||||
if (!TryReadUInt16Compat(ctx, left + (i * WideCharSize), out var leftUnit) ||
|
||||
!TryReadUInt16Compat(ctx, right + (i * WideCharSize), out var rightUnit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
compare = leftUnit - rightUnit;
|
||||
compare = leftUnit == rightUnit ? 0 : leftUnit < rightUnit ? -1 : 1;
|
||||
if (compare != 0 || leftUnit == 0 || rightUnit == 0)
|
||||
{
|
||||
return true;
|
||||
@@ -2650,20 +2945,36 @@ public static class KernelMemoryCompatExports
|
||||
|
||||
private static byte[] EncodeWideUnits(ReadOnlySpan<ushort> units)
|
||||
{
|
||||
var bytes = new byte[units.Length * sizeof(ushort)];
|
||||
var bytes = new byte[units.Length * WideCharSize];
|
||||
for (var i = 0; i < units.Length; i++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(
|
||||
bytes.AsSpan(i * sizeof(ushort), sizeof(ushort)),
|
||||
bytes.AsSpan(i * WideCharSize, WideCharSize),
|
||||
units[i]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static string DecodeWideUnits(ReadOnlySpan<ushort> units)
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return new string(MemoryMarshal.Cast<ushort, char>(units));
|
||||
}
|
||||
|
||||
private static bool TryConvertWideScalarToString(ushort scalar, out string text)
|
||||
{
|
||||
text = ((char)scalar).ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] EncodeWideUnitsWithTerminator(ReadOnlySpan<ushort> units)
|
||||
{
|
||||
var bytes = new byte[(units.Length + 1) * sizeof(ushort)];
|
||||
var bytes = new byte[(units.Length + 1) * WideCharSize];
|
||||
EncodeWideUnits(units).CopyTo(bytes, 0);
|
||||
return bytes;
|
||||
}
|
||||
@@ -3501,6 +3812,202 @@ public static class KernelMemoryCompatExports
|
||||
return ctx.Memory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static bool TryWriteOpenDescriptorStat(CpuContext ctx, int fd, ulong statAddress)
|
||||
{
|
||||
if (fd is 0 or 1 or 2)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
return TryWriteKernelStat(ctx, statAddress, isDirectory: false, size: 0, now, now, now, $"stdio:{fd}");
|
||||
}
|
||||
|
||||
string? hostPath = null;
|
||||
bool isDirectory = false;
|
||||
lock (_fdGate)
|
||||
{
|
||||
if (_openDirectories.TryGetValue(fd, out var directory))
|
||||
{
|
||||
hostPath = directory.Path;
|
||||
isDirectory = true;
|
||||
}
|
||||
else if (_openFiles.TryGetValue(fd, out var stream))
|
||||
{
|
||||
hostPath = stream.Name;
|
||||
}
|
||||
}
|
||||
|
||||
return !string.IsNullOrWhiteSpace(hostPath) && TryWriteHostPathStat(ctx, statAddress, hostPath!, isDirectory);
|
||||
}
|
||||
|
||||
private static bool TryWriteHostPathStat(CpuContext ctx, ulong statAddress, string hostPath)
|
||||
{
|
||||
var isDirectory = Directory.Exists(hostPath);
|
||||
if (!isDirectory && !File.Exists(hostPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryWriteHostPathStat(ctx, statAddress, hostPath, isDirectory);
|
||||
}
|
||||
|
||||
private static bool TryWriteHostPathStat(CpuContext ctx, ulong statAddress, string hostPath, bool isDirectory)
|
||||
{
|
||||
if (isDirectory)
|
||||
{
|
||||
if (!Directory.Exists(hostPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!File.Exists(hostPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var lastAccessUtc = File.GetLastAccessTimeUtc(hostPath);
|
||||
var lastWriteUtc = File.GetLastWriteTimeUtc(hostPath);
|
||||
var creationUtc = File.GetCreationTimeUtc(hostPath);
|
||||
var size = isDirectory ? 65536L : new FileInfo(hostPath).Length;
|
||||
return TryWriteKernelStat(ctx, statAddress, isDirectory, size, lastAccessUtc, lastWriteUtc, creationUtc, hostPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryWriteKernelStat(
|
||||
CpuContext ctx,
|
||||
ulong statAddress,
|
||||
bool isDirectory,
|
||||
long size,
|
||||
DateTime lastAccessUtc,
|
||||
DateTime lastWriteUtc,
|
||||
DateTime creationUtc,
|
||||
string inodeSeed)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[KernelStatSize];
|
||||
payload.Clear();
|
||||
|
||||
var seedBytes = Encoding.UTF8.GetBytes(inodeSeed);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStDevOffset..], 0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStInoOffset..], ComputeDirectoryEntryHash(seedBytes));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(payload[KernelStatStModeOffset..], isDirectory ? KernelStatModeDirectory : KernelStatModeRegular);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(payload[KernelStatStNlinkOffset..], 1);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStUidOffset..], 0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStGidOffset..], 0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStRdevOffset..], 0);
|
||||
WriteKernelTimespec(payload[KernelStatStAtimOffset..], lastAccessUtc);
|
||||
WriteKernelTimespec(payload[KernelStatStMtimOffset..], lastWriteUtc);
|
||||
WriteKernelTimespec(payload[KernelStatStCtimOffset..], lastWriteUtc);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(payload[KernelStatStSizeOffset..], size);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(payload[KernelStatStBlocksOffset..], isDirectory ? 128 : (size + 511) / 512);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStBlksizeOffset..], isDirectory ? 65536U : 512U);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStFlagsOffset..], 0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload[KernelStatStGenOffset..], 0);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(payload[KernelStatStLspareOffset..], 0);
|
||||
WriteKernelTimespec(payload[KernelStatStBirthtimOffset..], creationUtc);
|
||||
return TryWriteCompat(ctx, statAddress, payload);
|
||||
}
|
||||
|
||||
private static void WriteKernelTimespec(Span<byte> destination, DateTime utcTime)
|
||||
{
|
||||
var timestamp = utcTime.Kind == DateTimeKind.Utc ? utcTime : utcTime.ToUniversalTime();
|
||||
var dto = new DateTimeOffset(timestamp);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination, dto.ToUnixTimeSeconds());
|
||||
var ticksWithinSecond = timestamp.Ticks % TimeSpan.TicksPerSecond;
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination[sizeof(long)..], ticksWithinSecond * 100);
|
||||
}
|
||||
|
||||
private static int KernelGetdirentriesCore(CpuContext ctx, int fd, ulong bufferAddress, int requested, ulong basePointerAddress)
|
||||
{
|
||||
if (fd < 0 || bufferAddress == 0 || requested < 512)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
OpenDirectory? directory;
|
||||
lock (_fdGate)
|
||||
{
|
||||
_openDirectories.TryGetValue(fd, out directory);
|
||||
}
|
||||
|
||||
if (directory is null)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var currentIndex = directory.NextIndex;
|
||||
if (basePointerAddress != 0 && !TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (currentIndex >= directory.Entries.Length)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
var entryName = directory.Entries[currentIndex];
|
||||
directory.NextIndex = currentIndex + 1;
|
||||
|
||||
var entryBytes = Encoding.UTF8.GetBytes(entryName);
|
||||
var nameLength = Math.Min(entryBytes.Length, 255);
|
||||
var entryPath = Path.Combine(directory.Path, entryName);
|
||||
var entryType = Directory.Exists(entryPath) ? (byte)4 : (byte)8;
|
||||
|
||||
var payload = new byte[512];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(0, sizeof(uint)), ComputeDirectoryEntryHash(entryBytes.AsSpan(0, nameLength)));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(payload.AsSpan(4, sizeof(ushort)), 512);
|
||||
payload[6] = entryType;
|
||||
payload[7] = unchecked((byte)nameLength);
|
||||
entryBytes.AsSpan(0, nameLength).CopyTo(payload.AsSpan(8));
|
||||
|
||||
if (!TryWriteCompat(ctx, bufferAddress, payload))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 512;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static string[] EnumerateDirectoryEntries(string hostPath)
|
||||
{
|
||||
return Directory.EnumerateFileSystemEntries(hostPath)
|
||||
.Select(Path.GetFileName)
|
||||
.Where(static name => !string.IsNullOrEmpty(name))
|
||||
.OrderBy(static name => name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray()!;
|
||||
}
|
||||
|
||||
private static uint ComputeDirectoryEntryHash(ReadOnlySpan<byte> utf8Name)
|
||||
{
|
||||
const uint offsetBasis = 2166136261;
|
||||
const uint prime = 16777619;
|
||||
|
||||
var hash = offsetBasis;
|
||||
for (var i = 0; i < utf8Name.Length; i++)
|
||||
{
|
||||
hash ^= utf8Name[i];
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static void LogOpenTrace(string message)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_OPEN"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
|
||||
}
|
||||
|
||||
private static ulong AlignUp(ulong value, ulong alignment)
|
||||
{
|
||||
if (alignment <= 1)
|
||||
|
||||
@@ -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",
|
||||
@@ -630,6 +634,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 +661,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 +951,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,7 +24,7 @@ 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;
|
||||
@@ -41,6 +41,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 +641,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 +674,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 +685,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 +755,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;
|
||||
@@ -881,21 +942,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 +1016,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 +1061,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