mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 23:46:15 +08:00
348 lines
10 KiB
C#
348 lines
10 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
|
|
namespace SharpEmu.Libs.Kernel;
|
|
|
|
public static class KernelModuleRegistry
|
|
{
|
|
private static readonly object _gate = new();
|
|
private static readonly Dictionary<int, ModuleEntry> _modulesByHandle = new();
|
|
private static readonly Dictionary<string, int> _handleByPath = new(StringComparer.OrdinalIgnoreCase);
|
|
private static readonly Dictionary<string, int> _handleByName = new(StringComparer.OrdinalIgnoreCase);
|
|
private static readonly Dictionary<int, int> _sysmoduleHandleById = new();
|
|
private static int _nextHandle = 1;
|
|
|
|
private static readonly Dictionary<int, string[]> KnownSysmoduleNames = new()
|
|
{
|
|
[0x0006] = new[] { "libSceFiber.sprx", "libSceFiber.prx" },
|
|
[0x0021] = new[] { "libSceRudp.sprx", "libSceRudp.prx" },
|
|
[0x0095] = new[] { "libSceIme.sprx", "libSceIme.prx" },
|
|
[0x0096] = new[] { "libSceImeDialog.sprx", "libSceImeDialog.prx" },
|
|
[0x00A9] = new[] { "libSceMouse.sprx", "libSceMouse.prx" },
|
|
};
|
|
|
|
public readonly record struct ModuleEntry(
|
|
int Handle,
|
|
string Name,
|
|
string Path,
|
|
ulong BaseAddress,
|
|
ulong EndAddress,
|
|
ulong EntryPoint,
|
|
bool IsMain,
|
|
bool IsSystemModule);
|
|
|
|
public static void Reset()
|
|
{
|
|
lock (_gate)
|
|
{
|
|
_modulesByHandle.Clear();
|
|
_handleByPath.Clear();
|
|
_handleByName.Clear();
|
|
_sysmoduleHandleById.Clear();
|
|
_nextHandle = 1;
|
|
}
|
|
}
|
|
|
|
public static int RegisterModule(
|
|
string? modulePath,
|
|
ulong baseAddress,
|
|
ulong size,
|
|
ulong entryPoint,
|
|
bool isMain,
|
|
bool isSystemModule = false)
|
|
{
|
|
var normalizedPath = NormalizePath(modulePath);
|
|
lock (_gate)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(normalizedPath) &&
|
|
_handleByPath.TryGetValue(normalizedPath, out var existingHandle) &&
|
|
_modulesByHandle.TryGetValue(existingHandle, out var existing))
|
|
{
|
|
var updated = existing with
|
|
{
|
|
BaseAddress = baseAddress,
|
|
EndAddress = ComputeEnd(baseAddress, size),
|
|
EntryPoint = entryPoint,
|
|
IsMain = existing.IsMain || isMain,
|
|
IsSystemModule = existing.IsSystemModule || isSystemModule,
|
|
};
|
|
_modulesByHandle[existingHandle] = updated;
|
|
_handleByName[updated.Name] = existingHandle;
|
|
return existingHandle;
|
|
}
|
|
|
|
var handle = _nextHandle++;
|
|
var name = ResolveName(normalizedPath, handle);
|
|
var entry = new ModuleEntry(
|
|
Handle: handle,
|
|
Name: name,
|
|
Path: normalizedPath,
|
|
BaseAddress: baseAddress,
|
|
EndAddress: ComputeEnd(baseAddress, size),
|
|
EntryPoint: entryPoint,
|
|
IsMain: isMain,
|
|
IsSystemModule: isSystemModule);
|
|
_modulesByHandle[handle] = entry;
|
|
if (!string.IsNullOrWhiteSpace(normalizedPath))
|
|
{
|
|
_handleByPath[normalizedPath] = handle;
|
|
}
|
|
|
|
_handleByName[name] = handle;
|
|
return handle;
|
|
}
|
|
}
|
|
|
|
public static int RegisterSyntheticModule(string moduleName, bool isSystemModule)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
if (TryResolveHandleByNameLocked(moduleName, out var existingHandle))
|
|
{
|
|
var existing = _modulesByHandle[existingHandle];
|
|
_modulesByHandle[existingHandle] = existing with { IsSystemModule = existing.IsSystemModule || isSystemModule };
|
|
return existingHandle;
|
|
}
|
|
|
|
var handle = _nextHandle++;
|
|
var fallbackName = string.IsNullOrWhiteSpace(moduleName)
|
|
? $"module_{handle:X4}.sprx"
|
|
: moduleName;
|
|
var entry = new ModuleEntry(
|
|
Handle: handle,
|
|
Name: fallbackName,
|
|
Path: string.Empty,
|
|
BaseAddress: 0,
|
|
EndAddress: 0,
|
|
EntryPoint: 0,
|
|
IsMain: false,
|
|
IsSystemModule: isSystemModule);
|
|
_modulesByHandle[handle] = entry;
|
|
_handleByName[fallbackName] = handle;
|
|
return handle;
|
|
}
|
|
}
|
|
|
|
public static int MarkSysmoduleLoaded(int sysmoduleId)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
if (_sysmoduleHandleById.TryGetValue(sysmoduleId, out var existingHandle) &&
|
|
_modulesByHandle.ContainsKey(existingHandle))
|
|
{
|
|
return existingHandle;
|
|
}
|
|
|
|
var handle = TryResolveKnownSysmoduleHandleLocked(sysmoduleId, out var knownHandle)
|
|
? knownHandle
|
|
: RegisterSyntheticModule($"sysmodule_0x{sysmoduleId:X4}.sprx", isSystemModule: true);
|
|
_sysmoduleHandleById[sysmoduleId] = handle;
|
|
return handle;
|
|
}
|
|
}
|
|
|
|
public static void MarkSysmoduleUnloaded(int sysmoduleId)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
_sysmoduleHandleById.Remove(sysmoduleId);
|
|
}
|
|
}
|
|
|
|
public static bool IsSysmoduleLoaded(int sysmoduleId)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
return _sysmoduleHandleById.ContainsKey(sysmoduleId);
|
|
}
|
|
}
|
|
|
|
public static bool TryGetModuleByAddress(ulong address, out ModuleEntry module)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
ModuleEntry? best = null;
|
|
foreach (var entry in _modulesByHandle.Values)
|
|
{
|
|
if (entry.BaseAddress == 0 || entry.EndAddress <= entry.BaseAddress)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (address < entry.BaseAddress || address >= entry.EndAddress)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (best is null || (entry.EndAddress - entry.BaseAddress) < (best.Value.EndAddress - best.Value.BaseAddress))
|
|
{
|
|
best = entry;
|
|
}
|
|
}
|
|
|
|
if (best is null)
|
|
{
|
|
module = default;
|
|
return false;
|
|
}
|
|
|
|
module = best.Value;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static bool TryGetModuleByHandle(int handle, out ModuleEntry module)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
return _modulesByHandle.TryGetValue(handle, out module);
|
|
}
|
|
}
|
|
|
|
public static bool TryFindByPathOrName(string? modulePathOrName, out ModuleEntry module)
|
|
{
|
|
module = default;
|
|
if (string.IsNullOrWhiteSpace(modulePathOrName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var normalizedPath = NormalizePath(modulePathOrName);
|
|
var fileName = ResolveName(normalizedPath, 0);
|
|
lock (_gate)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(normalizedPath) &&
|
|
_handleByPath.TryGetValue(normalizedPath, out var handleByPath) &&
|
|
_modulesByHandle.TryGetValue(handleByPath, out module))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (TryResolveHandleByNameLocked(fileName, out var handleByName) &&
|
|
_modulesByHandle.TryGetValue(handleByName, out module))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static int[] GetModuleHandles(bool includeSystemModules)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
return _modulesByHandle.Values
|
|
.Where(entry => includeSystemModules || !entry.IsSystemModule)
|
|
.OrderBy(entry => entry.Handle)
|
|
.Select(entry => entry.Handle)
|
|
.ToArray();
|
|
}
|
|
}
|
|
|
|
public static bool TryGetFirstModule(out ModuleEntry module)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
if (_modulesByHandle.Count == 0)
|
|
{
|
|
module = default;
|
|
return false;
|
|
}
|
|
|
|
var first = _modulesByHandle.Values.OrderBy(entry => entry.Handle).First();
|
|
module = first;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static bool TryResolveKnownSysmoduleHandleLocked(int sysmoduleId, out int handle)
|
|
{
|
|
handle = 0;
|
|
if (!KnownSysmoduleNames.TryGetValue(sysmoduleId, out var candidates))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var candidate in candidates)
|
|
{
|
|
if (TryResolveHandleByNameLocked(candidate, out handle))
|
|
{
|
|
var existing = _modulesByHandle[handle];
|
|
_modulesByHandle[handle] = existing with { IsSystemModule = true };
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryResolveHandleByNameLocked(string moduleName, out int handle)
|
|
{
|
|
handle = 0;
|
|
if (string.IsNullOrWhiteSpace(moduleName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_handleByName.TryGetValue(moduleName, out handle))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var altName = moduleName.EndsWith(".sprx", StringComparison.OrdinalIgnoreCase)
|
|
? moduleName[..^5] + ".prx"
|
|
: (moduleName.EndsWith(".prx", StringComparison.OrdinalIgnoreCase)
|
|
? moduleName[..^4] + ".sprx"
|
|
: string.Empty);
|
|
return !string.IsNullOrWhiteSpace(altName) && _handleByName.TryGetValue(altName, out handle);
|
|
}
|
|
|
|
private static ulong ComputeEnd(ulong baseAddress, ulong size)
|
|
{
|
|
if (size == 0)
|
|
{
|
|
return baseAddress;
|
|
}
|
|
|
|
return unchecked(baseAddress + size);
|
|
}
|
|
|
|
private static string NormalizePath(string? modulePath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(modulePath))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
try
|
|
{
|
|
return Path.GetFullPath(modulePath).Replace('\\', '/');
|
|
}
|
|
catch
|
|
{
|
|
return modulePath.Replace('\\', '/');
|
|
}
|
|
}
|
|
|
|
private static string ResolveName(string normalizedPath, int handleHint)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(normalizedPath))
|
|
{
|
|
var fileName = Path.GetFileName(normalizedPath);
|
|
if (!string.IsNullOrWhiteSpace(fileName))
|
|
{
|
|
return fileName;
|
|
}
|
|
}
|
|
|
|
return handleHint <= 0
|
|
? "module.sprx"
|
|
: $"module_{handleHint:X4}.sprx";
|
|
}
|
|
}
|