mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-27 05:01:07 +08:00
c0fd6a80e8
* [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>
240 lines
8.5 KiB
C#
240 lines
8.5 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Collections.Concurrent;
|
|
using System.Reflection;
|
|
|
|
namespace SharpEmu.HLE;
|
|
|
|
public sealed class ModuleManager : IModuleManager
|
|
{
|
|
private readonly ConcurrentDictionary<string, Delegate> _dispatchTable = new(StringComparer.Ordinal);
|
|
private readonly ConcurrentDictionary<string, ExportedFunction> _exportTable = new(StringComparer.Ordinal);
|
|
private readonly ConcurrentDictionary<string, ExportedFunction> _exportNameTable = new(StringComparer.Ordinal);
|
|
private readonly object _registrationGate = new();
|
|
private bool _isFrozen;
|
|
|
|
public int RegisterFromAssembly(Assembly assembly, Generation generation, ISymbolCatalog? symbolCatalog = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(assembly);
|
|
|
|
lock (_registrationGate)
|
|
{
|
|
if (_isFrozen)
|
|
{
|
|
throw new InvalidOperationException("Module registration is frozen.");
|
|
}
|
|
|
|
var registeredCount = 0;
|
|
var instances = new Dictionary<Type, object>();
|
|
|
|
foreach (var type in assembly.GetTypes())
|
|
{
|
|
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
|
|
{
|
|
var exportAttribute = method.GetCustomAttribute<SysAbiExportAttribute>(inherit: false);
|
|
if (exportAttribute is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var exportInfo = ResolveExportInfo(exportAttribute, method, generation, symbolCatalog);
|
|
if (exportInfo is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
_exportTable[exportInfo.Value.Nid] = new ExportedFunction(
|
|
exportInfo.Value.LibraryName,
|
|
exportInfo.Value.Nid,
|
|
exportInfo.Value.ExportName,
|
|
exportInfo.Value.Target,
|
|
(SysAbiFunction)handler);
|
|
_exportNameTable.TryAdd(exportInfo.Value.ExportName, _exportTable[exportInfo.Value.Nid]);
|
|
|
|
registeredCount++;
|
|
}
|
|
}
|
|
|
|
return registeredCount;
|
|
}
|
|
}
|
|
|
|
public void Freeze()
|
|
{
|
|
lock (_registrationGate)
|
|
{
|
|
_isFrozen = true;
|
|
}
|
|
}
|
|
|
|
public bool TryGetFunction(string nid, out Delegate function)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
|
|
return _dispatchTable.TryGetValue(nid, out function!);
|
|
}
|
|
|
|
public bool TryGetExport(string nid, out ExportedFunction export)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
|
|
return _exportTable.TryGetValue(nid, out export!);
|
|
}
|
|
|
|
public bool TryGetExportByName(string exportName, out ExportedFunction export)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(exportName);
|
|
return _exportNameTable.TryGetValue(exportName, out export!);
|
|
}
|
|
|
|
public OrbisGen2Result Dispatch(string nid, CpuContext context)
|
|
{
|
|
TryDispatch(nid, context, out var result);
|
|
return result;
|
|
}
|
|
|
|
public bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
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;
|
|
}
|
|
|
|
if ((export.Target & context.TargetGeneration) == 0)
|
|
{
|
|
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);
|
|
|
|
if (!context.WasRaxWritten)
|
|
{
|
|
context[CpuRegister.Rax] = unchecked((ulong)ret);
|
|
}
|
|
|
|
result = (OrbisGen2Result)ret;
|
|
return true;
|
|
}
|
|
|
|
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
|
|
{
|
|
ValidateSignature(method);
|
|
|
|
object? target = null;
|
|
if (!method.IsStatic)
|
|
{
|
|
if (!instances.TryGetValue(ownerType, out target))
|
|
{
|
|
target = Activator.CreateInstance(ownerType)
|
|
?? throw new InvalidOperationException($"Cannot instantiate module type: {ownerType.FullName}");
|
|
instances.Add(ownerType, target);
|
|
}
|
|
}
|
|
|
|
var parameterCount = method.GetParameters().Length;
|
|
if (parameterCount == 0)
|
|
{
|
|
var noArg = method.IsStatic
|
|
? (Func<int>)method.CreateDelegate(typeof(Func<int>))
|
|
: (Func<int>)method.CreateDelegate(typeof(Func<int>), target!);
|
|
|
|
SysAbiFunction adapter = _ => noArg();
|
|
return adapter;
|
|
}
|
|
|
|
return method.IsStatic
|
|
? method.CreateDelegate(typeof(SysAbiFunction))
|
|
: method.CreateDelegate(typeof(SysAbiFunction), target!);
|
|
}
|
|
|
|
private static void ValidateSignature(MethodInfo method)
|
|
{
|
|
if (method.ReturnType != typeof(int))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Method {method.DeclaringType?.FullName}.{method.Name} must return int.");
|
|
}
|
|
|
|
var parameters = method.GetParameters();
|
|
if (parameters.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(CpuContext))
|
|
{
|
|
return;
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
$"Method {method.DeclaringType?.FullName}.{method.Name} must accept no arguments or one {nameof(CpuContext)} argument.");
|
|
}
|
|
|
|
private static ExportInfo? ResolveExportInfo(
|
|
SysAbiExportAttribute exportAttribute,
|
|
MethodInfo method,
|
|
Generation generation,
|
|
ISymbolCatalog? symbolCatalog)
|
|
{
|
|
var target = exportAttribute.Target == Generation.None
|
|
? generation
|
|
: exportAttribute.Target;
|
|
if ((target & generation) == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var nid = exportAttribute.Nid;
|
|
var exportName = exportAttribute.ExportName;
|
|
|
|
if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName) && symbolCatalog?.TryGetByExportName(exportName, out var byName) == true)
|
|
{
|
|
nid = byName.Nid;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(nid) && symbolCatalog?.TryGetByNid(nid, out var byNid) == true)
|
|
{
|
|
exportName = string.IsNullOrWhiteSpace(exportName) ? byNid.ExportName : exportName;
|
|
target = exportAttribute.Target == Generation.None ? byNid.Target : target;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(nid))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Method {method.DeclaringType?.FullName}.{method.Name} must define a NID or match one in symbols catalog.");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(exportName))
|
|
{
|
|
exportName = method.Name;
|
|
}
|
|
|
|
if ((target & generation) == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var libraryName = string.IsNullOrWhiteSpace(exportAttribute.LibraryName) ? "libKernel" : exportAttribute.LibraryName;
|
|
return new ExportInfo(nid, exportName, libraryName, target);
|
|
}
|
|
|
|
private readonly record struct ExportInfo(string Nid, string ExportName, string LibraryName, Generation Target);
|
|
}
|