diff --git a/src/SharpEmu.Libs/Ampr/AmprFileRegistry.cs b/src/SharpEmu.Libs/Ampr/AmprFileRegistry.cs index 62dc7a0..efae4a7 100644 --- a/src/SharpEmu.Libs/Ampr/AmprFileRegistry.cs +++ b/src/SharpEmu.Libs/Ampr/AmprFileRegistry.cs @@ -50,6 +50,23 @@ internal static class AmprFileRegistry } } + public static uint RegisterAprResolvedPath(string guestPath, string hostPath) + { + if (TryGetApp0Relative(guestPath, out var relative) && relative.Length != 0) + { + RegisterApp0Relative(relative, hostPath); + } + + // APR file ids are part of the guest ABI: ResolveFilepathsToIds returns + // the 31-bit FNV-1a hash of the guest path. Keep the collision-safe + // process-local handles used by Register() separate from this path so a + // title can compare resolved ids with ids baked into its asset tables. + var fileId = ComputeFileId(guestPath); + _hostPathsById[fileId] = hostPath; + _ambiguousCompatibilityIds.TryRemove(fileId, out _); + return fileId; + } + public static bool TryGetHostPath(uint id, out string hostPath) { if ((id & 0x80000000) != 0) diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index d621740..e14c2f1 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -1740,7 +1740,7 @@ public static partial class KernelMemoryCompatExports return -1; } - var fileId = AmprFileRegistry.Register(guestPath, hostPath); + var fileId = AmprFileRegistry.RegisterAprResolvedPath(guestPath, hostPath); LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}"); if (idsAddress != 0 && @@ -1832,7 +1832,7 @@ public static partial class KernelMemoryCompatExports return -1; } - var fileId = AmprFileRegistry.Register(guestPath, hostPath); + var fileId = AmprFileRegistry.RegisterAprResolvedPath(guestPath, hostPath); LogIoTrace("apr_resolve_with_prefix", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}"); if (idsAddress != 0 && @@ -1873,7 +1873,8 @@ public static partial class KernelMemoryCompatExports // IDs, then hand those IDs to sceAmprAprCommandBufferReadFile. Without it // the paths never register in AmprFileRegistry, so every subsequent // ReadFile fails with NOT_FOUND and the streaming pipeline stalls forever. - // Signature: (const char* const* paths, size_t count, uint32_t* ids). + // Signature: (const char* const* paths, uint32_t count, uint32_t* ids, + // uint32_t* errorIndex). [SysAbiExport( Nid = "WT-5NKy42fw", ExportName = "sceKernelAprResolveFilepathsToIds", @@ -1884,6 +1885,7 @@ public static partial class KernelMemoryCompatExports var pathListAddress = ctx[CpuRegister.Rdi]; var count = ctx[CpuRegister.Rsi]; var idsAddress = ctx[CpuRegister.Rdx]; + var errorIndexAddress = ctx[CpuRegister.Rcx]; if (pathListAddress == 0 || count == 0 || idsAddress == 0 || count > 1024) { KernelRuntimeCompatExports.TrySetErrno(ctx, Einval); @@ -1908,11 +1910,19 @@ public static partial class KernelMemoryCompatExports if (!TryGetAprFileSize(hostPath, out _)) { LogIoTrace("apr_resolve_ids", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found"); + if (errorIndexAddress != 0 && + !TryWriteUInt32Compat(ctx, errorIndexAddress, (uint)i)) + { + KernelRuntimeCompatExports.TrySetErrno(ctx, Efault); + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + KernelRuntimeCompatExports.TrySetErrno(ctx, 2); - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + ctx[CpuRegister.Rax] = ulong.MaxValue; + return -1; } - var fileId = AmprFileRegistry.Register(guestPath, hostPath); + var fileId = AmprFileRegistry.RegisterAprResolvedPath(guestPath, hostPath); LogIoTrace("apr_resolve_ids", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8}"); if (!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId)) @@ -4811,6 +4821,17 @@ public static partial class KernelMemoryCompatExports return CombineWithinMount(app0Root, relative); } + if (string.Equals(guestPath, "/contents", StringComparison.OrdinalIgnoreCase)) + { + return CombineWithinMount(app0Root, "contents"); + } + + if (guestPath.StartsWith("/contents/", StringComparison.OrdinalIgnoreCase)) + { + var relative = NormalizeMountRelativePath(guestPath[1..]); + return CombineWithinMount(app0Root, relative); + } + if (!Path.IsPathFullyQualified(guestPath) && !guestPath.StartsWith("/", StringComparison.Ordinal) && !guestPath.StartsWith("\\", StringComparison.Ordinal))