mirror of
https://github.com/par274/sharpemu.git
synced 2026-09-05 08:10:13 +08:00
[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)
* [Core] Add POSIX native execution and PS5 SELF support Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases. * [HLE] Expand PS5 service and media compatibility Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT. * [AGC/Vulkan] Extend Gen5 shader and presentation support Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders. * [Core] Align static TLS reservation across hosts * [Pad] Align primary user ID with UserService * [Gpu] Preserve runtime scalar buffers across renderer seam * [AGC] Restore omitted command helper exports * [Vulkan] Reuse primary views for promoted MRT targets * [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
@@ -30,19 +30,15 @@ public static class SaveDataExports
|
||||
private const uint MountModeCreate2 = 1u << 5;
|
||||
private const int MountResultSize = 0x40;
|
||||
private static readonly object _stateGate = new();
|
||||
private static readonly HashSet<int> _transactionResources = [];
|
||||
private static readonly HashSet<int> _preparedTransactionResources = [];
|
||||
private static string? _titleId;
|
||||
private static int _nextTransactionResource;
|
||||
|
||||
public static void ConfigureApplicationInfo(string? titleId)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_titleId = string.IsNullOrWhiteSpace(titleId) ? null : SanitizePathSegment(titleId.Trim());
|
||||
_transactionResources.Clear();
|
||||
_preparedTransactionResources.Clear();
|
||||
_nextTransactionResource = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,15 +52,15 @@ public static class SaveDataExports
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ResolveSaveDataRoot());
|
||||
return ctx.SetReturn(0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +75,18 @@ public static class SaveDataExports
|
||||
var resultAddress = ctx[CpuRegister.Rsi];
|
||||
if (condAddress == 0 || resultAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
if (!TryReadSearchCond(ctx, condAddress, out var cond) ||
|
||||
!TryReadSearchResult(ctx, resultAddress, out var result))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (cond.UserId < 0 || cond.SortKey > SortKeyFreeBlocks || cond.SortOrder > SortOrderDescent)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -102,7 +98,7 @@ public static class SaveDataExports
|
||||
}
|
||||
else if (!TryReadFixedAscii(ctx, cond.TitleIdAddress, SaveDataTitleIdSize, out titleId))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var root = ResolveTitleSaveRoot(cond.UserId, titleId);
|
||||
@@ -114,21 +110,21 @@ public static class SaveDataExports
|
||||
var setNum = result.DirNamesNum == 0
|
||||
? 0
|
||||
: Math.Min(result.DirNamesNum, entries.Count);
|
||||
if (!ctx.TryWriteUInt32(resultAddress + ResultHitNumOffset, checked((uint)entries.Count)) ||
|
||||
!ctx.TryWriteUInt32(resultAddress + ResultSetNumOffset, checked((uint)setNum)))
|
||||
if (!TryWriteUInt32(ctx, resultAddress + ResultHitNumOffset, checked((uint)entries.Count)) ||
|
||||
!TryWriteUInt32(ctx, resultAddress + ResultSetNumOffset, checked((uint)setNum)))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (setNum == 0)
|
||||
{
|
||||
TraceSaveData($"dir_name_search user={cond.UserId} title={titleId} hits={entries.Count} set=0 root='{root}'");
|
||||
return ctx.SetReturn(0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
if (result.DirNamesAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
for (var i = 0; i < setNum; i++)
|
||||
@@ -144,20 +140,20 @@ public static class SaveDataExports
|
||||
(result.InfosAddress != 0 &&
|
||||
!TryWriteSearchInfo(ctx, result.InfosAddress + ((ulong)i * SaveDataSearchInfoSize), entry)))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
TraceSaveData($"dir_name_search user={cond.UserId} title={titleId} hits={entries.Count} set={setNum} root='{root}'");
|
||||
return ctx.SetReturn(0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,25 +168,25 @@ public static class SaveDataExports
|
||||
var resultAddress = ctx[CpuRegister.Rsi];
|
||||
if (mountAddress == 0 || resultAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
if (!ctx.TryReadInt32(mountAddress, out var userId) ||
|
||||
if (!TryReadInt32(ctx, mountAddress, out var userId) ||
|
||||
!ctx.TryReadUInt64(mountAddress + 0x08, out var dirNameAddress) ||
|
||||
!ctx.TryReadUInt64(mountAddress + 0x10, out var blocks) ||
|
||||
!ctx.TryReadUInt64(mountAddress + 0x18, out var systemBlocks) ||
|
||||
!ctx.TryReadUInt32(mountAddress + 0x20, out var mountMode) ||
|
||||
!ctx.TryReadUInt32(mountAddress + 0x24, out var resource) ||
|
||||
!ctx.TryReadUInt32(mountAddress + 0x28, out var mode) ||
|
||||
!TryReadUInt32(ctx, mountAddress + 0x20, out var mountMode) ||
|
||||
!TryReadUInt32(ctx, mountAddress + 0x24, out var resource) ||
|
||||
!TryReadUInt32(ctx, mountAddress + 0x28, out var mode) ||
|
||||
dirNameAddress == 0 ||
|
||||
!TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out var dirName))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (userId < 0 || string.IsNullOrWhiteSpace(dirName))
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -205,12 +201,12 @@ public static class SaveDataExports
|
||||
|
||||
if (!existed && !create && !createIfMissing)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorNotFound);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorNotFound);
|
||||
}
|
||||
|
||||
if (existed && create)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorExists);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorExists);
|
||||
}
|
||||
|
||||
if (!existed)
|
||||
@@ -227,29 +223,30 @@ public static class SaveDataExports
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[0x1C..], createIfMissing && !existed ? 1u : 0u);
|
||||
if (!ctx.Memory.TryWrite(resultAddress, result))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceSaveData(
|
||||
$"mount3 user={userId} title={titleId} dir={dirName} blocks={blocks} " +
|
||||
$"system_blocks={systemBlocks} mount_mode=0x{mountMode:X} resource={resource} mode={mode} " +
|
||||
$"mount_point={mountPoint} created={!existed} root='{savePath}'");
|
||||
return ctx.SetReturn(0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorInternal);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private static int _nextTransactionResource;
|
||||
[SysAbiExport(
|
||||
Nid = "gjRZNnw0JPE",
|
||||
ExportName = "sceSaveDataCreateTransactionResource",
|
||||
@@ -257,16 +254,39 @@ public static class SaveDataExports
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataCreateTransactionResource(CpuContext ctx)
|
||||
{
|
||||
var memorySize = ctx[CpuRegister.Rdi];
|
||||
int resource;
|
||||
lock (_stateGate)
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var reserved = ctx[CpuRegister.Rsi];
|
||||
|
||||
var id = (uint)Interlocked.Increment(ref _nextTransactionResource);
|
||||
|
||||
// The resource-out pointer's argument slot varies by SDK revision: some
|
||||
// callers pass it in rdx, others in rcx (a 4-arg form where rdx holds a
|
||||
// count/flag). Void Terrarium passes rdx=0x1 (not a pointer) and the
|
||||
// real out-pointer in rcx. Probe the plausible candidates and write the
|
||||
// handle to the first writable one instead of faulting on a bad rdx.
|
||||
// This is a stub-level create (matches shadPS4's return-OK semantics);
|
||||
// never return MEMORY_FAULT for it, or the guest treats savedata init as
|
||||
// failed and never advances.
|
||||
var resourceAddress = 0UL;
|
||||
foreach (var candidate in new[]
|
||||
{
|
||||
ctx[CpuRegister.Rdx],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
ctx[CpuRegister.R9],
|
||||
})
|
||||
{
|
||||
resource = ++_nextTransactionResource;
|
||||
_transactionResources.Add(resource);
|
||||
if (candidate != 0 && TryWriteUInt32(ctx, candidate, id))
|
||||
{
|
||||
resourceAddress = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TraceSaveData($"create_transaction_resource memory_size=0x{memorySize:X} resource={resource}");
|
||||
return ctx.SetReturn(resource);
|
||||
TraceSaveData(
|
||||
$"create_transaction_resource user={userId} reserved=0x{reserved:X} resource_addr=0x{resourceAddress:X} id={id}");
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
@@ -279,70 +299,11 @@ public static class SaveDataExports
|
||||
var resource = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_transactionResources.Remove(resource);
|
||||
_preparedTransactionResources.Remove(resource);
|
||||
}
|
||||
|
||||
TraceSaveData($"delete_transaction_resource resource={resource}");
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "sDCBrmc61XU",
|
||||
ExportName = "sceSaveDataPrepare",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataPrepare(CpuContext ctx)
|
||||
{
|
||||
var mountPointAddress = ctx[CpuRegister.Rdi];
|
||||
var resource = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
if (mountPointAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
if (!TryReadFixedAscii(ctx, mountPointAddress, 16, out var mountPoint))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mountPoint))
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (resource != 0)
|
||||
{
|
||||
_preparedTransactionResources.Add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
TraceSaveData($"prepare mount_point={mountPoint} resource={resource}");
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ie7qhZ4X0Cc",
|
||||
ExportName = "sceSaveDataCommit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataCommit(CpuContext ctx)
|
||||
{
|
||||
var commitAddress = ctx[CpuRegister.Rdi];
|
||||
if (commitAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_preparedTransactionResources.Clear();
|
||||
}
|
||||
|
||||
TraceSaveData($"commit commit=0x{commitAddress:X16}");
|
||||
return ctx.SetReturn(0);
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
@@ -352,40 +313,21 @@ public static class SaveDataExports
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataUmount2(CpuContext ctx)
|
||||
{
|
||||
var mountPointAddress = ctx[CpuRegister.Rdi];
|
||||
if (mountPointAddress == 0)
|
||||
{
|
||||
mountPointAddress = ctx[CpuRegister.Rsi];
|
||||
}
|
||||
|
||||
if (mountPointAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
if (!TryReadFixedAscii(ctx, mountPointAddress, 16, out var mountPoint))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mountPoint))
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
var unmounted = KernelMemoryCompatExports.TryUnregisterGuestPathMount(mountPoint);
|
||||
TraceSaveData($"umount2 mount_point={mountPoint} unregistered={unmounted}");
|
||||
return ctx.SetReturn(0);
|
||||
// Unmounting a save directory always succeeds in the stub filesystem;
|
||||
// returning an error here makes the game's save flow stall before it
|
||||
// hands control to the title/gameplay state.
|
||||
TraceSaveData($"umount2 user={unchecked((int)ctx[CpuRegister.Rdi])}");
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
|
||||
{
|
||||
cond = default;
|
||||
if (!ctx.TryReadInt32(address, out var userId) ||
|
||||
if (!TryReadInt32(ctx, address, out var userId) ||
|
||||
!ctx.TryReadUInt64(address + 0x08, out var titleIdAddress) ||
|
||||
!ctx.TryReadUInt64(address + 0x10, out var dirNameAddress) ||
|
||||
!ctx.TryReadUInt32(address + 0x18, out var sortKey) ||
|
||||
!ctx.TryReadUInt32(address + 0x1C, out var sortOrder))
|
||||
!TryReadUInt32(ctx, address + 0x18, out var sortKey) ||
|
||||
!TryReadUInt32(ctx, address + 0x1C, out var sortOrder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -408,7 +350,7 @@ public static class SaveDataExports
|
||||
{
|
||||
result = default;
|
||||
if (!ctx.TryReadUInt64(address + ResultDirNamesOffset, out var dirNamesAddress) ||
|
||||
!ctx.TryReadUInt32(address + ResultDirNamesNumOffset, out var dirNamesNum) ||
|
||||
!TryReadUInt32(ctx, address + ResultDirNamesNumOffset, out var dirNamesNum) ||
|
||||
!ctx.TryReadUInt64(address + ResultParamsOffset, out var paramsAddress) ||
|
||||
!ctx.TryReadUInt64(address + ResultInfosOffset, out var infosAddress))
|
||||
{
|
||||
@@ -609,6 +551,45 @@ public static class SaveDataExports
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryReadInt32(CpuContext ctx, ulong address, out int value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
|
||||
return ctx.Memory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceSaveData(string message)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SAVEDATA"), "1", StringComparison.Ordinal))
|
||||
@@ -631,4 +612,62 @@ public static class SaveDataExports
|
||||
ulong InfosAddress);
|
||||
|
||||
private readonly record struct SaveEntry(string Name, string Path, DateTime LastWriteUtc);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "sDCBrmc61XU",
|
||||
ExportName = "sceSaveDataPrepare",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataPrepare(CpuContext ctx)
|
||||
{
|
||||
var mountPointAddress = ctx[CpuRegister.Rdi];
|
||||
var resource = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
if (mountPointAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
if (!TryReadFixedAscii(ctx, mountPointAddress, 16, out var mountPoint))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mountPoint))
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (resource != 0)
|
||||
{
|
||||
_preparedTransactionResources.Add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
TraceSaveData($"prepare mount_point={mountPoint} resource={resource}");
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ie7qhZ4X0Cc",
|
||||
ExportName = "sceSaveDataCommit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataCommit(CpuContext ctx)
|
||||
{
|
||||
var commitAddress = ctx[CpuRegister.Rdi];
|
||||
if (commitAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_preparedTransactionResources.Clear();
|
||||
}
|
||||
|
||||
TraceSaveData($"commit commit=0x{commitAddress:X16}");
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user