From c0fd6a80e844d6b519d656c461e311abcb3164c9 Mon Sep 17 00:00:00 2001 From: kostyaff Date: Fri, 10 Jul 2026 21:48:50 +0300 Subject: [PATCH] Astro Bot shader type 4, pthread_cond_timedwait, and HLE/memory/cpu bug fixes (#40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [agc] Add shader type 4 (GS) and register defaults v13 support Astro Bot (#11) crashes on boot due to two missing GPU features: 1. Shader type 4 (Geometry Shader) — SPI_SHADER_PGM_LO/HI register offsets 0x8A/0x8B were missing. Added constants and switch cases for shader type 4 in GetExpectedSpiShaderPgmLo/Hi. Also added type 4 to IsEsGeometryShaderType (2 or 4 or 6). 2. Register defaults version 13 — was not recognized as supported. Added RegisterDefaultsVersion13 constant and included it in IsSupportedRegisterDefaultsVersion. * [kernel] Add POSIX pthread_cond_timedwait export SILENT HILL (#4) and Poppy Playtime (#3) crash on boot due to missing POSIX pthread_cond_timedwait (NID 27bAgiJmOh0). The Sony wrapper scePthreadCondTimedwait (NID BmMjYxmew1w) was already implemented, but the raw POSIX symbol was not exported. Added [SysAbiExport] for pthread_cond_timedwait delegating to existing PthreadCondWaitCore with timed: true. * [memory] Fix FlushInstructionCache null process handle PhysicalVirtualMemory.cs called FlushInstructionCache with null as the process handle in two places (SetProtection and TryWriteExclusive). On Windows, a null handle does not reliably resolve to the current process — the correct call is GetCurrentProcess() (pseudo-handle -1). Also corrected the P/Invoke signature: - Changed return type from void to bool with [return: MarshalAs(Bool)] - Added SetLastError = true - Added GetCurrentProcess() P/Invoke import This matches the pattern already used in DirectExecutionBackend.cs which correctly passes GetCurrentProcess() to all FlushInstructionCache calls. * [hle] Distinguish NOT_FOUND from NOT_IMPLEMENTED and log duplicate NIDs Three diagnostic improvements to the HLE dispatch path: 1. ModuleManager.RegisterFromAssembly — duplicate NID registration was silently skipped (dispatchTable first-wins, exportTable last-wins, causing metadata divergence). Now logs a warning with the NID and export name so conflicts are visible. 2. ModuleManager.TryDispatch — generation mismatch returned ORBIS_GEN2_ERROR_NOT_FOUND, conflating 'function does not exist' with 'function exists but not for this generation'. Now returns ORBIS_GEN2_ERROR_NOT_IMPLEMENTED for generation mismatch, matching the existing convention in CpuDispatcher. Also adds debug logging for both NOT_FOUND and NOT_IMPLEMENTED paths. 3. DirectExecutionBackend.Imports.cs — the import dispatch else-branch (the actual hot path that bypasses ModuleManager.TyDispatch via cached export) had the same conflation. Split into: - else if (export exists but generation mismatch) → NOT_IMPLEMENTED - else (no export at all) → NOT_FOUND This makes runtime diagnostics correctly distinguish missing exports from generation-unsupported exports. * [cpu] Check VirtualProtect return values in all stub creation paths 9 VirtualProtect calls in DirectExecutionBackend.cs had unchecked return values. If VirtualProtect silently fails, memory protection remains incorrect — stubs allocated with PAGE_EXECUTE_READWRITE (0x40) never get downgraded to PAGE_EXECUTE_READ (0x20), or guest thread entry stubs never get upgraded to writable. This causes access violations on next execution or silent data corruption. Fixed all 9 sites with proper error handling: - 6 stub creation methods (return 0 on failure + log error) - 2 guest thread entry methods (set reason + return Exception) - 1 guest entry method (set LastError + return MEMORY_FAULT) Stub creation sites fixed: - CreateImportDispatchStub (line ~1683) - EnsureTlsHandler (void, log + return) - CreateUnresolvedReturnStub (return 0) - CreateGuestReturnStub (return 0) - CreateExceptionHandlerTrampoline (return 0) - CreateTlsStoreHelperStub (return 0) Guest thread entry sites fixed: - StartGuestThreadNativeCall (return Exception) - StartGuestContinuationNativeCall (return Exception) - RunGuestEntryPoint (return MEMORY_FAULT) * [kernel] Remove unused duplicate _nextFileDescriptor field KernelExports.cs declared _nextFileDescriptor but never used it. The actual field used for file descriptor allocation lives in KernelMemoryCompatExports.cs (lines 1314, 1337). This was a dead duplicate causing CS0414 warning. Build is now 0 errors, 0 warnings. --------- Co-authored-by: Hermes Atlas --- .../Native/DirectExecutionBackend.Imports.cs | 21 +++++-- .../Cpu/Native/DirectExecutionBackend.cs | 61 +++++++++++++++---- .../Memory/PhysicalVirtualMemory.cs | 10 ++- src/SharpEmu.HLE/ModuleManager.cs | 8 ++- src/SharpEmu.Libs/Agc/AgcExports.cs | 10 ++- src/SharpEmu.Libs/Kernel/KernelExports.cs | 1 - .../Kernel/KernelPthreadCompatExports.cs | 6 ++ 7 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 596609c..04b567b 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -358,12 +358,21 @@ public sealed partial class DirectExecutionBackend } orbisGen2Result = (OrbisGen2Result)returnValue; } - else - { - dispatchResolved = false; - orbisGen2Result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; - cpuContext[CpuRegister.Rax] = unchecked((ulong)(int)orbisGen2Result); - } + else if (importStubEntry.Export is { } mismatchedExport) + { + dispatchResolved = true; + orbisGen2Result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED; + cpuContext[CpuRegister.Rax] = unchecked((ulong)(int)orbisGen2Result); + Console.Error.WriteLine( + $"[LOADER][WARN] Import#{num} not implemented for generation {cpuContext.TargetGeneration}: " + + $"nid={importStubEntry.Nid} targets={mismatchedExport.Target} ret=0x{num7:X16}"); + } + else + { + dispatchResolved = false; + orbisGen2Result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + cpuContext[CpuRegister.Rax] = unchecked((ulong)(int)orbisGen2Result); + } } finally { diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index b3e56f6..c7a9839 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -1679,10 +1679,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[num++] = 65; ptr2[num++] = 95; ptr2[num++] = 195; - uint num2 = default(uint); - VirtualProtect(ptr, 192u, 32u, &num2); - FlushInstructionCache(GetCurrentProcess(), ptr, 192u); - return (nint)ptr; + uint num2 = default(uint); + if (!VirtualProtect(ptr, 192u, 32u, &num2)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for import dispatch stub at 0x{(nint)ptr:X16}"); + return 0; + } + FlushInstructionCache(GetCurrentProcess(), ptr, 192u); + return (nint)ptr; } catch { @@ -1763,7 +1767,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I tlsHandlerAddress[num++] = 195; _tlsPatchStubOffset = (num + 15) & ~15; uint num2 = default(uint); - VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2); + if (!VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for TLS handler at 0x{_tlsHandlerAddress:X16}"); + return; + } FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize); Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}"); } @@ -1784,7 +1792,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[i] = 144; } uint num = default(uint); - VirtualProtect(ptr, 4096u, 32u, &num); + if (!VirtualProtect(ptr, 4096u, 32u, &num)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for unresolved return stub at 0x{(nint)ptr:X16}"); + return 0; + } FlushInstructionCache(GetCurrentProcess(), ptr, 16u); return (nint)ptr; } @@ -1831,7 +1843,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I EmitByte(code, ref offset, 0xC3); uint oldProtect = default; - VirtualProtect(ptr, stubSize, 32u, &oldProtect); + if (!VirtualProtect(ptr, stubSize, 32u, &oldProtect)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for guest return stub at 0x{(nint)ptr:X16}"); + return 0; + } FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); return (nint)ptr; } @@ -1923,7 +1939,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I *(int*)(code + guestRestoreJump) = restoreOffset - (guestRestoreJump + sizeof(int)); uint oldProtect = default; - VirtualProtect(ptr, stubSize, 32u, &oldProtect); + if (!VirtualProtect(ptr, stubSize, 32u, &oldProtect)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for exception handler trampoline at 0x{(nint)ptr:X16}"); + return 0; + } FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); return (nint)ptr; } @@ -2254,7 +2274,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr[num2++] = 144; } uint flNewProtect = default(uint); - VirtualProtect((void*)num, 32u, 32u, &flNewProtect); + if (!VirtualProtect((void*)num, 32u, 32u, &flNewProtect)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for TLS store helper at 0x{num:X16}"); + return 0; + } FlushInstructionCache(GetCurrentProcess(), (void*)num, 32u); return num; } @@ -3591,7 +3615,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } uint oldProtect = default(uint); - VirtualProtect(ptr, stubSize, 64u, &oldProtect); + if (!VirtualProtect(ptr, stubSize, 64u, &oldProtect)) + { + reason = $"VirtualProtect failed for guest thread entry stub at 0x{(nint)ptr:X16}"; + return GuestNativeCallExitReason.Exception; + } FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); TlsSetValue(_hostRspSlotTlsIndex, (nint)hostRspSlot); ActiveGuestThreadYieldRequested = false; @@ -3728,7 +3756,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } uint oldProtect = default(uint); - VirtualProtect(ptr, stubSize, 64u, &oldProtect); + if (!VirtualProtect(ptr, stubSize, 64u, &oldProtect)) + { + reason = $"VirtualProtect failed for guest continuation stub at 0x{(nint)ptr:X16}"; + return GuestNativeCallExitReason.Exception; + } FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); TlsSetValue(_hostRspSlotTlsIndex, (nint)hostRspSlot); ActiveGuestThreadYieldRequested = false; @@ -3988,7 +4020,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return false; } uint num5 = default(uint); - VirtualProtect(ptr, stubSize, 64u, &num5); + if (!VirtualProtect(ptr, stubSize, 64u, &num5)) + { + LastError = $"VirtualProtect failed for guest entry stub at 0x{(nint)ptr:X16}"; + result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + return false; + } FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); if (_hostRspSlotStorage != 0) { diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index 7d7b4b2..0a15ae1 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -55,7 +55,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private static extern nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength); [DllImport("kernel32.dll")] - private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); + private static extern void* GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); public bool TryAllocateAtExact(ulong desiredAddress, ulong size, bool executable, out ulong actualAddress) { @@ -451,7 +455,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA if ((flags & ProgramHeaderFlags.Execute) != 0) { - FlushInstructionCache(null, (void*)address, (nuint)size); + FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)size); } } @@ -699,7 +703,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA VirtualProtect(destPtr, (nuint)source.Length, oldProtect, out _); if (IsExecutableProtection(oldProtect)) { - FlushInstructionCache(null, destPtr, (nuint)source.Length); + FlushInstructionCache(GetCurrentProcess(), destPtr, (nuint)source.Length); } } diff --git a/src/SharpEmu.HLE/ModuleManager.cs b/src/SharpEmu.HLE/ModuleManager.cs index a899634..aaf34b6 100644 --- a/src/SharpEmu.HLE/ModuleManager.cs +++ b/src/SharpEmu.HLE/ModuleManager.cs @@ -47,6 +47,7 @@ public sealed class ModuleManager : IModuleManager var handler = CreateHandler(type, method, instances); if (!_dispatchTable.TryAdd(exportInfo.Value.Nid, handler)) { + Console.Error.WriteLine($"[HLE] Duplicate NID '{exportInfo.Value.Nid}' ({exportInfo.Value.ExportName}) — already registered, skipping."); continue; } @@ -105,6 +106,7 @@ public sealed class ModuleManager : IModuleManager if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export)) { + Console.Error.WriteLine($"[HLE] NID '{nid}' not found in dispatch table."); context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return false; @@ -112,11 +114,13 @@ public sealed class ModuleManager : IModuleManager if ((export.Target & context.TargetGeneration) == 0) { - context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); - result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + Console.Error.WriteLine($"[HLE] NID '{nid}' ({export.Name}) found but not implemented for generation {context.TargetGeneration} (targets: {export.Target})."); + context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED); + result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED; return false; } + context.ClearRaxWriteFlag(); int ret = ((SysAbiFunction)function).Invoke(context); diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index 1ce98b6..d1de0dc 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -58,6 +58,8 @@ public static class AgcExports private const uint SpiShaderPgmHiEs = 0xC9; private const uint SpiShaderPgmLoLs = 0x148; private const uint SpiShaderPgmHiLs = 0x149; + private const uint SpiShaderPgmLoGs = 0x8A; + private const uint SpiShaderPgmHiGs = 0x8B; private const uint SpiPsInputEna = 0x1B3; private const uint SpiPsInputAddr = 0x1B4; private const uint ComputePgmLo = 0x20C; @@ -115,6 +117,7 @@ public static class AgcExports private const uint RegisterDefaultsVersion7 = 7; private const uint RegisterDefaultsVersion8 = 8; private const uint RegisterDefaultsVersion10 = 10; + private const uint RegisterDefaultsVersion13 = 13; private const int RegisterDefaultsSize = 0x40; private const int RegisterDefaultBlockSize = 16 * 8; @@ -5081,6 +5084,7 @@ public static class AgcExports 0 => ComputePgmLo, 1 => SpiShaderPgmLoPs, 2 or 6 => SpiShaderPgmLoEs, + 4 => SpiShaderPgmLoGs, 7 => SpiShaderPgmLoLs, _ => 0u, }; @@ -5089,6 +5093,7 @@ public static class AgcExports 0 => ComputePgmHi, 1 => SpiShaderPgmHiPs, 2 or 6 => SpiShaderPgmHiEs, + 4 => SpiShaderPgmHiGs, 7 => SpiShaderPgmHiLs, _ => 0u, }; @@ -5105,7 +5110,7 @@ public static class AgcExports } private static bool IsEsGeometryShaderType(byte shaderType) => - shaderType is 2 or 6; + shaderType is 2 or 4 or 6; private static int SetIndirectPatchAddress(CpuContext ctx, string registerSpace) { @@ -5252,7 +5257,8 @@ public static class AgcExports return version is RegisterDefaultsVersion7 or RegisterDefaultsVersion8 or - RegisterDefaultsVersion10; + RegisterDefaultsVersion10 or + RegisterDefaultsVersion13; } private static bool TryGetRegisterDefaultsAllocation( diff --git a/src/SharpEmu.Libs/Kernel/KernelExports.cs b/src/SharpEmu.Libs/Kernel/KernelExports.cs index 2edb2e8..cba3c7e 100644 --- a/src/SharpEmu.Libs/Kernel/KernelExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelExports.cs @@ -8,7 +8,6 @@ namespace SharpEmu.Libs.Kernel; public static class KernelExports { - private static int _nextFileDescriptor = 2; private static readonly object _cxaGate = new(); private static readonly List _cxaDestructors = new(); private static readonly object _coredumpGate = new(); diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs index 12f1089..f9b00a0 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs @@ -329,6 +329,12 @@ public static class KernelPthreadCompatExports Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")] public static int PosixPthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false); + [SysAbiExport( + Nid = "27bAgiJmOh0", + ExportName = "pthread_cond_timedwait", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int PosixPthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: unchecked((uint)ctx[CpuRegister.Rdx])); [SysAbiExport( Nid = "m5-2bsNfv7s",