mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
fix(kernel): implement APR ResolveFilepathsWithPrefixToIdsAndFileSizes (#534)
* fix(kernel): implement APR ResolveFilepathsWithPrefixToIdsAndFileSizes Resource streamers resolve relative paths against a shared prefix; without this HLE every call returned NOT_FOUND and assets never got real ids/sizes. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retrigger gameplay CI for PR #534 Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1773,6 +1773,113 @@ public static partial class KernelMemoryCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
// WithPrefix sibling of sceKernelAprResolveFilepathsToIdsAndFileSizes.
|
||||
// Resource streamers resolve relative asset paths against a shared directory
|
||||
// prefix. Without HLE, every call returned the generic NOT_FOUND sentinel
|
||||
// and no asset received a real file id/size. Signature inferred from
|
||||
// observed guest registers (rdi=prefix, rsi=path list, rdx=count, rcx=ids,
|
||||
// r8=sizes, r9=error index): the no-prefix sibling's args shifted right by
|
||||
// one with a leading `const char* prefix`.
|
||||
[SysAbiExport(
|
||||
Nid = "w5fcCG+t31g",
|
||||
ExportName = "sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizes",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(CpuContext ctx)
|
||||
{
|
||||
var prefixAddress = ctx[CpuRegister.Rdi];
|
||||
var pathListAddress = ctx[CpuRegister.Rsi];
|
||||
var count = ctx[CpuRegister.Rdx];
|
||||
var idsAddress = ctx[CpuRegister.Rcx];
|
||||
var sizesAddress = ctx[CpuRegister.R8];
|
||||
var errorIndexAddress = ctx[CpuRegister.R9];
|
||||
if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024)
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var prefix = string.Empty;
|
||||
if (prefixAddress != 0)
|
||||
{
|
||||
_ = TryReadNullTerminatedUtf8(ctx, prefixAddress, MaxGuestStringLength, out prefix);
|
||||
}
|
||||
|
||||
for (ulong i = 0; i < count; i++)
|
||||
{
|
||||
if (idsAddress != 0 &&
|
||||
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), uint.MaxValue))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var relativePath))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var guestPath = CombineAprPrefixedPath(prefix, relativePath);
|
||||
var hostPath = ResolveGuestPath(guestPath);
|
||||
if (!TryGetAprFileSize(hostPath, out var fileSize))
|
||||
{
|
||||
LogIoTrace("apr_resolve_with_prefix", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found");
|
||||
if (sizesAddress != 0 &&
|
||||
!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), 0))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (errorIndexAddress != 0 &&
|
||||
!TryWriteUInt32Compat(ctx, errorIndexAddress, (uint)i))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, 2); // ENOENT
|
||||
ctx[CpuRegister.Rax] = ulong.MaxValue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
var fileId = AmprFileRegistry.Register(guestPath, hostPath);
|
||||
LogIoTrace("apr_resolve_with_prefix", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}");
|
||||
|
||||
if (idsAddress != 0 &&
|
||||
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static string CombineAprPrefixedPath(string prefix, string relative)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefix))
|
||||
{
|
||||
return relative;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(relative))
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
return $"{prefix.TrimEnd('/')}/{relative.TrimStart('/')}";
|
||||
}
|
||||
|
||||
// The IDs-only sibling of sceKernelAprResolveFilepathsToIdsAndFileSizes.
|
||||
// Games that stream via AMPR APR call this to turn asset paths into file
|
||||
// IDs, then hand those IDs to sceAmprAprCommandBufferReadFile. Without it
|
||||
|
||||
@@ -105,6 +105,92 @@ public sealed class AprStreamingContractTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsWithPrefixToIdsAndFileSizes_CombinesPrefixAndResolvesRealFile()
|
||||
{
|
||||
// Resource streamers call WithPrefix to join a directory prefix with a
|
||||
// relative asset path. Without HLE every call returned NOT_FOUND and no
|
||||
// asset received a real file id/size.
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong prefixAddress = memoryBase + 0x80;
|
||||
const ulong pathListAddress = memoryBase + 0x100;
|
||||
const ulong pathAddress = memoryBase + 0x200;
|
||||
const ulong idsAddress = memoryBase + 0x800;
|
||||
const ulong sizesAddress = memoryBase + 0x880;
|
||||
byte[] fileContents = [1, 2, 3, 4, 5, 6];
|
||||
var mountRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"sharpemu-apr-prefix-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(mountRoot);
|
||||
var mountPoint = $"/sharpemu_apr_prefix_mnt_{Guid.NewGuid():N}";
|
||||
const string fileName = "asset.bin";
|
||||
var hostPath = Path.Combine(mountRoot, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(hostPath, fileContents);
|
||||
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, mountRoot);
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x4000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
memory.WriteCString(prefixAddress, mountPoint);
|
||||
memory.WriteCString(pathAddress, fileName);
|
||||
WriteUInt64(memory, pathListAddress, pathAddress);
|
||||
|
||||
context[CpuRegister.Rdi] = prefixAddress;
|
||||
context[CpuRegister.Rsi] = pathListAddress;
|
||||
context[CpuRegister.Rdx] = 1;
|
||||
context[CpuRegister.Rcx] = idsAddress;
|
||||
context[CpuRegister.R8] = sizesAddress;
|
||||
context[CpuRegister.R9] = 0;
|
||||
|
||||
Assert.Equal(
|
||||
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
||||
KernelMemoryCompatExports.KernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(context));
|
||||
Assert.NotEqual(uint.MaxValue, ReadUInt32(memory, idsAddress));
|
||||
Assert.Equal((ulong)fileContents.Length, ReadUInt64(memory, sizesAddress));
|
||||
}
|
||||
finally
|
||||
{
|
||||
KernelMemoryCompatExports.UnregisterGuestPathMount(mountPoint);
|
||||
if (Directory.Exists(mountRoot))
|
||||
{
|
||||
Directory.Delete(mountRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsWithPrefixToIdsAndFileSizes_MissingFile_FailsFastWithErrorIndex()
|
||||
{
|
||||
const ulong memoryBase = 0x1_0000_0000;
|
||||
const ulong prefixAddress = memoryBase + 0x80;
|
||||
const ulong pathListAddress = memoryBase + 0x100;
|
||||
const ulong pathAddress = memoryBase + 0x200;
|
||||
const ulong idsAddress = memoryBase + 0x800;
|
||||
const ulong sizesAddress = memoryBase + 0x880;
|
||||
const ulong errorIndexAddress = memoryBase + 0x8F0;
|
||||
var memory = new FakeCpuMemory(memoryBase, 0x4000);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
memory.WriteCString(prefixAddress, "/does-not-exist-prefix");
|
||||
memory.WriteCString(pathAddress, $"missing-{Guid.NewGuid():N}.bin");
|
||||
WriteUInt64(memory, pathListAddress, pathAddress);
|
||||
|
||||
context[CpuRegister.Rdi] = prefixAddress;
|
||||
context[CpuRegister.Rsi] = pathListAddress;
|
||||
context[CpuRegister.Rdx] = 1;
|
||||
context[CpuRegister.Rcx] = idsAddress;
|
||||
context[CpuRegister.R8] = sizesAddress;
|
||||
context[CpuRegister.R9] = errorIndexAddress;
|
||||
|
||||
Assert.Equal(
|
||||
-1,
|
||||
KernelMemoryCompatExports.KernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(context));
|
||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||
Assert.Equal(uint.MaxValue, ReadUInt32(memory, idsAddress));
|
||||
Assert.Equal(0ul, ReadUInt64(memory, sizesAddress));
|
||||
Assert.Equal(0u, ReadUInt32(memory, errorIndexAddress));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFilepathsToIdsAndFileSizes_MissingFile_FailsFastWithErrorIndex()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user