mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Fix Gen5 boot and restore stable AGC rendering (#139)
Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
This commit is contained in:
@@ -736,6 +736,9 @@ public sealed partial class DirectExecutionBackend
|
||||
var expectedEqueueTimeout =
|
||||
string.Equals(nid, "fzyMKs9kim0", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||
var expectedEventFlagTimeout =
|
||||
string.Equals(nid, "JTvBflhYazQ", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||
var expectedMutexTrylockBusy =
|
||||
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
@@ -748,6 +751,7 @@ public sealed partial class DirectExecutionBackend
|
||||
if (!expectedFileProbeMiss &&
|
||||
!expectedTimedWaitTimeout &&
|
||||
!expectedEqueueTimeout &&
|
||||
!expectedEventFlagTimeout &&
|
||||
!expectedMutexTrylockBusy &&
|
||||
!expectedUserServiceNoEvent &&
|
||||
!expectedPrivacyInvalidParameter)
|
||||
|
||||
@@ -52,8 +52,6 @@ public static class AgcExports
|
||||
private const uint RFlip = 0x17;
|
||||
private const uint RReleaseMem = 0x18;
|
||||
private const uint RDmaData = 0x19;
|
||||
private const uint RPredication = 0x1A;
|
||||
private const uint RJump = 0x1B;
|
||||
private const uint SpiShaderPgmLoPs = 0x8;
|
||||
private const uint SpiShaderPgmHiPs = 0x9;
|
||||
private const uint SpiShaderPgmLoEs = 0xC8;
|
||||
@@ -391,7 +389,6 @@ public static class AgcExports
|
||||
public Dictionary<uint, SubmittedDcbState> ComputeQueues { get; } = new();
|
||||
public Dictionary<ulong, ComputeImageWriter> ComputeImageWriters { get; } = new();
|
||||
public ulong WorkSequence { get; set; }
|
||||
public ulong FlipSequence { get; set; }
|
||||
}
|
||||
|
||||
private readonly record struct RegisterDefaultValue(uint Offset, uint Value);
|
||||
@@ -1926,90 +1923,6 @@ public static class AgcExports
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bbFueFP+J4k",
|
||||
ExportName = "sceAgcDcbSetPredication",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbSetPredication(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var enable = (uint)ctx[CpuRegister.Rsi];
|
||||
var predicateType = (uint)ctx[CpuRegister.Rdx];
|
||||
var continuePredicate = (uint)ctx[CpuRegister.Rcx];
|
||||
var predicateAddress = ctx[CpuRegister.R8];
|
||||
if (commandBufferAddress == 0 || enable > 1 || continuePredicate > 1)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
|
||||
!ctx.TryWriteUInt32(commandAddress, Pm4(5, ItNop, RPredication)) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 4, enable) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 8, predicateType) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 12, continuePredicate) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 16, unchecked((uint)predicateAddress)))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_set_predication buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
|
||||
$"enable={enable} type={predicateType} continue={continuePredicate} addr=0x{predicateAddress:X16}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "xSAR0LTcRKM",
|
||||
ExportName = "sceAgcDcbJump",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbJump(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var targetAddress = ctx[CpuRegister.Rsi];
|
||||
if (commandBufferAddress == 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 3, out var commandAddress) ||
|
||||
!ctx.TryWriteUInt32(commandAddress, Pm4(3, ItNop, RJump)) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 4, unchecked((uint)targetAddress)) ||
|
||||
!ctx.TryWriteUInt32(commandAddress + 8, unchecked((uint)(targetAddress >> 32))))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_jump buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
|
||||
$"target=0x{targetAddress:X16}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w6Dj1VJt5qY",
|
||||
ExportName = "sceAgcSetPacketPredication",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int SetPacketPredication(CpuContext ctx)
|
||||
{
|
||||
var packetAddress = ctx[CpuRegister.Rdi];
|
||||
var enabled = ctx[CpuRegister.Rsi] != 0;
|
||||
if (packetAddress == 0 || !ctx.TryReadUInt32(packetAddress, out var packetHeader))
|
||||
{
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
packetHeader = enabled ? packetHeader | 1u : packetHeader & ~1u;
|
||||
if (!ctx.TryWriteUInt32(packetAddress, packetHeader))
|
||||
{
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w2rJhmD+dsE",
|
||||
ExportName = "sceAgcDriverAddEqEvent",
|
||||
@@ -2062,22 +1975,9 @@ public static class AgcExports
|
||||
public static int DriverSubmitDcb(CpuContext ctx)
|
||||
{
|
||||
var packetAddress = ctx[CpuRegister.Rdi];
|
||||
// Gen5 supports direct RSI/RDX and packed RDI submissions.
|
||||
var commandAddress = ctx[CpuRegister.Rsi];
|
||||
var directDwordCount = ctx[CpuRegister.Rdx];
|
||||
uint dwordCount;
|
||||
var directArguments = commandAddress != 0 &&
|
||||
directDwordCount is > 0 and <= 1_000_000UL;
|
||||
if (directArguments)
|
||||
{
|
||||
dwordCount = (uint)directDwordCount;
|
||||
}
|
||||
else if (packetAddress == 0 ||
|
||||
!ctx.TryReadUInt64(packetAddress, out commandAddress) ||
|
||||
!ctx.TryReadUInt32(packetAddress + 8, out dwordCount) ||
|
||||
commandAddress == 0 ||
|
||||
dwordCount == 0 ||
|
||||
dwordCount > 1_000_000)
|
||||
if (packetAddress == 0 ||
|
||||
!ctx.TryReadUInt64(packetAddress, out var commandAddress) ||
|
||||
!ctx.TryReadUInt32(packetAddress + 8, out var dwordCount))
|
||||
{
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
@@ -2093,29 +1993,16 @@ public static class AgcExports
|
||||
|
||||
if (tracePackets)
|
||||
{
|
||||
TraceAgc(
|
||||
$"agc.driver_submit_dcb abi={(directArguments ? "direct" : "packed")} " +
|
||||
$"packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
||||
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
|
||||
}
|
||||
|
||||
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
|
||||
ulong flipSequenceBefore;
|
||||
lock (gpuState.Gate)
|
||||
{
|
||||
flipSequenceBefore = gpuState.FlipSequence;
|
||||
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
|
||||
DrainResumableDcbs(ctx, gpuState, tracePackets);
|
||||
}
|
||||
|
||||
// Flip submissions are completed through VideoOut pacing.
|
||||
if (gpuState.FlipSequence == flipSequenceBefore)
|
||||
{
|
||||
KernelEventQueueCompatExports.TriggerRegisteredEvents(
|
||||
0,
|
||||
KernelEventQueueCompatExports.KernelEventFilterGraphics,
|
||||
0);
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -2141,10 +2028,8 @@ public static class AgcExports
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal);
|
||||
|
||||
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
|
||||
ulong flipSequenceBefore;
|
||||
lock (gpuState.Gate)
|
||||
{
|
||||
flipSequenceBefore = gpuState.FlipSequence;
|
||||
for (uint i = 0; i < bufferCount; i++)
|
||||
{
|
||||
if (!ctx.TryReadUInt64(addressArray + i * 8, out var commandAddress) ||
|
||||
@@ -2168,14 +2053,6 @@ public static class AgcExports
|
||||
DrainResumableDcbs(ctx, gpuState, tracePackets);
|
||||
}
|
||||
|
||||
if (gpuState.FlipSequence == flipSequenceBefore)
|
||||
{
|
||||
KernelEventQueueCompatExports.TriggerRegisteredEvents(
|
||||
0,
|
||||
KernelEventQueueCompatExports.KernelEventFilterGraphics,
|
||||
0);
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -2424,40 +2301,6 @@ public static class AgcExports
|
||||
}
|
||||
|
||||
var packetType = header >> 30;
|
||||
if (packetType == 0)
|
||||
{
|
||||
// Consume alignment padding one dword at a time.
|
||||
if (header == 0)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preserve stream alignment across type-0 packets.
|
||||
var type0Length = ((header >> 16) & 0x3FFFu) + 2u;
|
||||
if (offset + type0Length > dwordCount)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
offset += type0Length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packetType == 1)
|
||||
{
|
||||
const uint type1Length = 3;
|
||||
if (offset + type1Length > dwordCount)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
offset += type1Length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packetType == 2)
|
||||
{
|
||||
if (tracePackets)
|
||||
@@ -2472,15 +2315,13 @@ public static class AgcExports
|
||||
|
||||
if (packetType != 3)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
var length = Pm4Length(header);
|
||||
if (length == 0 || offset + length > dwordCount)
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
var op = (header >> 8) & 0xFFu;
|
||||
@@ -2718,7 +2559,6 @@ public static class AgcExports
|
||||
|
||||
if (op == ItNop && register == RFlip && length >= 6)
|
||||
{
|
||||
gpuState.FlipSequence++;
|
||||
if (!ctx.TryReadUInt32(currentAddress + 4, out var videoOutHandle) ||
|
||||
!ctx.TryReadUInt32(currentAddress + 8, out var displayBufferIndexRaw) ||
|
||||
!ctx.TryReadUInt32(currentAddress + 12, out var flipMode) ||
|
||||
@@ -2731,8 +2571,23 @@ public static class AgcExports
|
||||
var flipArg = unchecked((long)(((ulong)flipArgHi << 32) | flipArgLo));
|
||||
var displayBufferIndex = unchecked((int)displayBufferIndexRaw);
|
||||
var handle = unchecked((int)videoOutHandle);
|
||||
// Prefer output decoded from the current DCB.
|
||||
if (state.SawIndexedDraw &&
|
||||
if (VideoOutExports.TryGetDisplayBufferInfo(
|
||||
handle,
|
||||
displayBufferIndex,
|
||||
out var cachedDisplayBuffer) &&
|
||||
VulkanVideoPresenter.TrySubmitGuestImage(
|
||||
cachedDisplayBuffer.Address,
|
||||
cachedDisplayBuffer.Width,
|
||||
cachedDisplayBuffer.Height,
|
||||
cachedDisplayBuffer.PitchInPixel))
|
||||
{
|
||||
TraceDisplayBuffer(
|
||||
handle,
|
||||
displayBufferIndex,
|
||||
cachedDisplayBuffer,
|
||||
"gpu-cache");
|
||||
}
|
||||
else if (state.SawIndexedDraw &&
|
||||
state.TranslatedDraw is { } translatedDraw &&
|
||||
VideoOutExports.TryGetDisplayBufferInfo(
|
||||
handle,
|
||||
@@ -2796,22 +2651,6 @@ public static class AgcExports
|
||||
displayBuffer.Width,
|
||||
displayBuffer.Height);
|
||||
}
|
||||
else if (VideoOutExports.TryGetDisplayBufferInfo(
|
||||
handle,
|
||||
displayBufferIndex,
|
||||
out var cachedDisplayBuffer) &&
|
||||
VulkanVideoPresenter.TrySubmitGuestImage(
|
||||
cachedDisplayBuffer.Address,
|
||||
cachedDisplayBuffer.Width,
|
||||
cachedDisplayBuffer.Height,
|
||||
cachedDisplayBuffer.PitchInPixel))
|
||||
{
|
||||
TraceDisplayBuffer(
|
||||
handle,
|
||||
displayBufferIndex,
|
||||
cachedDisplayBuffer,
|
||||
"gpu-cache");
|
||||
}
|
||||
|
||||
_ = VideoOutExports.SubmitFlipFromAgc(ctx, handle, displayBufferIndex, unchecked((int)flipMode), flipArg);
|
||||
state.SawIndexedDraw = false;
|
||||
@@ -4133,29 +3972,26 @@ public static class AgcExports
|
||||
|
||||
TraceTextureHash(descriptor, source);
|
||||
|
||||
if (_traceAgcShader)
|
||||
var nonZero = 0;
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
var nonZero = 0;
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
if (source[i] != 0)
|
||||
{
|
||||
if (source[i] != 0)
|
||||
nonZero++;
|
||||
if (nonZero >= 64)
|
||||
{
|
||||
nonZero++;
|
||||
if (nonZero >= 64)
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TraceAgcShader(
|
||||
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
|
||||
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
|
||||
$"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " +
|
||||
$"dst=0x{descriptor.DstSelect:X3} " +
|
||||
$"bytes={source.Length} nonzero64={nonZero}");
|
||||
}
|
||||
|
||||
TraceAgcShader(
|
||||
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
|
||||
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
|
||||
$"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " +
|
||||
$"dst=0x{descriptor.DstSelect:X3} " +
|
||||
$"bytes={source.Length} nonzero64={nonZero}");
|
||||
|
||||
var rgba = source;
|
||||
texture = new VulkanGuestDrawTexture(
|
||||
descriptor.Address,
|
||||
@@ -5865,16 +5701,6 @@ public static class AgcExports
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
|
||||
}
|
||||
|
||||
private static void TraceAgc(ref AgcTraceHandler message)
|
||||
{
|
||||
if (!_traceAgc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {message.ToStringAndClear()}");
|
||||
}
|
||||
|
||||
private static void TraceAgcShader(string message)
|
||||
{
|
||||
if (!_traceAgcShader)
|
||||
@@ -5885,62 +5711,6 @@ public static class AgcExports
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
|
||||
}
|
||||
|
||||
private static void TraceAgcShader(ref AgcShaderTraceHandler message)
|
||||
{
|
||||
if (!_traceAgcShader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {message.ToStringAndClear()}");
|
||||
}
|
||||
|
||||
[InterpolatedStringHandler]
|
||||
internal ref struct AgcTraceHandler
|
||||
{
|
||||
private DefaultInterpolatedStringHandler _inner;
|
||||
|
||||
public AgcTraceHandler(int literalLength, int formattedCount, out bool isEnabled)
|
||||
{
|
||||
isEnabled = _traceAgc;
|
||||
_inner = isEnabled
|
||||
? new DefaultInterpolatedStringHandler(literalLength, formattedCount)
|
||||
: default;
|
||||
}
|
||||
|
||||
public void AppendLiteral(string value) => _inner.AppendLiteral(value);
|
||||
|
||||
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
|
||||
|
||||
public void AppendFormatted<T>(T value, string? format) =>
|
||||
_inner.AppendFormatted(value, format);
|
||||
|
||||
public string ToStringAndClear() => _inner.ToStringAndClear();
|
||||
}
|
||||
|
||||
[InterpolatedStringHandler]
|
||||
internal ref struct AgcShaderTraceHandler
|
||||
{
|
||||
private DefaultInterpolatedStringHandler _inner;
|
||||
|
||||
public AgcShaderTraceHandler(int literalLength, int formattedCount, out bool isEnabled)
|
||||
{
|
||||
isEnabled = _traceAgcShader;
|
||||
_inner = isEnabled
|
||||
? new DefaultInterpolatedStringHandler(literalLength, formattedCount)
|
||||
: default;
|
||||
}
|
||||
|
||||
public void AppendLiteral(string value) => _inner.AppendLiteral(value);
|
||||
|
||||
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
|
||||
|
||||
public void AppendFormatted<T>(T value, string? format) =>
|
||||
_inner.AppendFormatted(value, format);
|
||||
|
||||
public string ToStringAndClear() => _inner.ToStringAndClear();
|
||||
}
|
||||
|
||||
private static string FormatShaderDwords(IReadOnlyList<uint> values) =>
|
||||
values.Count == 0
|
||||
? "none"
|
||||
|
||||
@@ -1683,6 +1683,30 @@ public static class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "E6ao34wPw+U",
|
||||
ExportName = "stat",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixStat(CpuContext ctx)
|
||||
{
|
||||
var result = KernelStat(ctx);
|
||||
if (result == (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var errno = result switch
|
||||
{
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT => Einval,
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT => Efault,
|
||||
_ => 2,
|
||||
};
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, errno);
|
||||
ctx[CpuRegister.Rax] = ulong.MaxValue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "gEpBkcwxUjw",
|
||||
ExportName = "sceKernelAprResolveFilepathsToIdsAndFileSizes",
|
||||
|
||||
@@ -431,6 +431,18 @@ public static class KernelRuntimeCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "HoLVWNanBBc",
|
||||
ExportName = "getpid",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int GetProcessId(CpuContext ctx)
|
||||
{
|
||||
var processId = Environment.ProcessId;
|
||||
ctx[CpuRegister.Rax] = unchecked((uint)processId);
|
||||
return processId;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "fgxnMeTNUtY",
|
||||
ExportName = "sceKernelGetProcessTimeCounter",
|
||||
|
||||
@@ -30,13 +30,17 @@ 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 string? _titleId;
|
||||
private static int _nextTransactionResource;
|
||||
|
||||
public static void ConfigureApplicationInfo(string? titleId)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_titleId = string.IsNullOrWhiteSpace(titleId) ? null : SanitizePathSegment(titleId.Trim());
|
||||
_transactionResources.Clear();
|
||||
_nextTransactionResource = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,26 +255,32 @@ public static class SaveDataExports
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataCreateTransactionResource(CpuContext ctx)
|
||||
{
|
||||
// Gen5 ABI: memory size, resource output, reserved.
|
||||
var memorySize = ctx[CpuRegister.Rdi];
|
||||
var resourceAddress = ctx[CpuRegister.Rsi];
|
||||
var reserved = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (resourceAddress == 0)
|
||||
int resource;
|
||||
lock (_stateGate)
|
||||
{
|
||||
return ctx.SetReturn(OrbisSaveDataErrorParameter);
|
||||
resource = ++_nextTransactionResource;
|
||||
_transactionResources.Add(resource);
|
||||
}
|
||||
|
||||
// Offline HLE operations carry no transaction state.
|
||||
if (!ctx.TryWriteUInt64(resourceAddress, 0))
|
||||
TraceSaveData($"create_transaction_resource memory_size=0x{memorySize:X} resource={resource}");
|
||||
return ctx.SetReturn(resource);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "lJUQuaKqoKY",
|
||||
ExportName = "sceSaveDataDeleteTransactionResource",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataDeleteTransactionResource(CpuContext ctx)
|
||||
{
|
||||
var resource = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
lock (_stateGate)
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
_transactionResources.Remove(resource);
|
||||
}
|
||||
|
||||
TraceSaveData(
|
||||
$"create_transaction_resource memory_size=0x{memorySize:X} reserved=0x{reserved:X} " +
|
||||
$"resource_addr=0x{resourceAddress:X} resource=0x0");
|
||||
|
||||
TraceSaveData($"delete_transaction_resource resource={resource}");
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user