Astro Bot shader type 4, pthread_cond_timedwait, and HLE/memory/cpu bug fixes (#40)

* [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 <hermesatlas@example.com>
This commit is contained in:
kostyaff
2026-07-10 21:48:50 +03:00
committed by GitHub
parent 7337683c16
commit c0fd6a80e8
7 changed files with 91 additions and 26 deletions
@@ -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)
{