mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 14:39:42 +08:00
[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)
* [Core] Add POSIX native execution and PS5 SELF support Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases. * [HLE] Expand PS5 service and media compatibility Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT. * [AGC/Vulkan] Extend Gen5 shader and presentation support Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders. * [Core] Align static TLS reservation across hosts * [Pad] Align primary user ID with UserService * [Gpu] Preserve runtime scalar buffers across renderer seam * [AGC] Restore omitted command helper exports * [Vulkan] Reuse primary views for promoted MRT targets * [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
@@ -7,25 +7,21 @@ using SharpEmu.Core.Cpu.Disasm;
|
||||
using SharpEmu.Core.Loader;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using SharpEmu.Libs.AppContent;
|
||||
using SharpEmu.Libs.SaveData;
|
||||
using SharpEmu.Logging;
|
||||
using SharpEmu.Libs.Fiber;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpEmu.Core.Runtime;
|
||||
|
||||
public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Runtime");
|
||||
|
||||
private readonly record struct LoadedModuleImage(string Path, SelfImage Image);
|
||||
private readonly record struct LoadedModuleImage(string Path, SelfImage Image, int Handle, bool StartAtBoot);
|
||||
|
||||
private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -89,19 +85,14 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
moduleManager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4 | Generation.Gen5));
|
||||
moduleManager.Freeze();
|
||||
|
||||
// Resolve the host platform once at the composition root; on unsupported
|
||||
// OSes this throws PlatformNotSupportedException with a clear message
|
||||
// instead of failing on the first native call.
|
||||
var hostPlatform = HostPlatform.Current;
|
||||
|
||||
var virtualMemory = new PhysicalVirtualMemory(hostPlatform.Memory);
|
||||
var virtualMemory = new PhysicalVirtualMemory();
|
||||
|
||||
var fileSystem = new PhysicalFileSystem();
|
||||
|
||||
return new SharpEmuRuntime(
|
||||
new SelfLoader(),
|
||||
virtualMemory,
|
||||
new CpuDispatcher(virtualMemory, moduleManager, hostPlatform: hostPlatform),
|
||||
new CpuDispatcher(virtualMemory, moduleManager),
|
||||
moduleManager,
|
||||
Aerolib.Instance,
|
||||
cpuExecutionOptions,
|
||||
@@ -139,20 +130,20 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
var normalizedEbootPath = Path.GetFullPath(ebootPath);
|
||||
using var app0Binding = BindApp0Root(normalizedEbootPath);
|
||||
Log.Info($"Loading: {ebootPath}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Loading: {ebootPath}");
|
||||
LastExecutionDiagnostics = null;
|
||||
LastExecutionTrace = null;
|
||||
LastSessionSummary = null;
|
||||
LastBasicBlockTrace = null;
|
||||
LastMilestoneLog = null;
|
||||
FiberExports.ResetRuntimeState();
|
||||
KernelModuleRegistry.Reset();
|
||||
var image = LoadImage(normalizedEbootPath);
|
||||
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version, BuildInfo.CommitSha);
|
||||
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version);
|
||||
SaveDataExports.ConfigureApplicationInfo(image.TitleId);
|
||||
LogAppBundleInfo(normalizedEbootPath, image);
|
||||
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||
_ = RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
||||
Log.Info($"Entry: 0x{image.EntryPoint:X16}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Entry: 0x{image.EntryPoint:X16}");
|
||||
var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4;
|
||||
var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs);
|
||||
var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal);
|
||||
@@ -175,7 +166,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
processImageName);
|
||||
if (initializerResult is { } failedInitializerResult)
|
||||
{
|
||||
Log.Error($"Initializer dispatch failed: {failedInitializerResult}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Initializer dispatch failed: {failedInitializerResult}");
|
||||
LastExecutionTrace = _cpuDispatcher.LastImportResolutionTrace;
|
||||
LastMilestoneLog = _cpuDispatcher.LastMilestoneLog;
|
||||
LastSessionSummary = BuildSessionSummary(_cpuDispatcher.LastSessionSummary);
|
||||
@@ -183,8 +174,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return failedInitializerResult;
|
||||
}
|
||||
|
||||
Log.Info($"Dispatching, gen: {generation}");
|
||||
Log.Debug($"About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}");
|
||||
Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}");
|
||||
|
||||
var result = _cpuDispatcher.DispatchEntry(
|
||||
image.EntryPoint,
|
||||
@@ -194,8 +185,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
processImageName,
|
||||
_cpuExecutionOptions);
|
||||
|
||||
Log.Info($"DispatchEntry returned: {result}");
|
||||
Log.Info($"Dispatch result: {result}");
|
||||
Console.Error.WriteLine($"[RUNTIME] DispatchEntry returned: {result}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Dispatch result: {result}");
|
||||
LastExecutionTrace = _cpuDispatcher.LastImportResolutionTrace;
|
||||
LastMilestoneLog = _cpuDispatcher.LastMilestoneLog;
|
||||
LastSessionSummary = BuildSessionSummary(_cpuDispatcher.LastSessionSummary);
|
||||
@@ -366,66 +357,6 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void LogAppBundleInfo(string ebootPath, SelfImage image)
|
||||
{
|
||||
var executableName = Path.GetFileName(ebootPath);
|
||||
if (string.IsNullOrWhiteSpace(executableName))
|
||||
{
|
||||
executableName = "eboot.bin";
|
||||
}
|
||||
|
||||
var displayName = string.IsNullOrWhiteSpace(image.Title) ? "(unknown)" : image.Title!.Trim();
|
||||
var titleId = string.IsNullOrWhiteSpace(image.TitleId) ? "(unknown)" : image.TitleId!.Trim();
|
||||
var version = string.IsNullOrWhiteSpace(image.Version) ? "(unknown)" : image.Version!.Trim();
|
||||
var contentId = ResolveContentId(Path.GetDirectoryName(ebootPath)) ?? "(unknown)";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("App bundle info:");
|
||||
builder.AppendLine($"- Display name: {displayName}");
|
||||
builder.AppendLine($"- Version: {version}");
|
||||
builder.AppendLine($"- Title ID: {titleId}");
|
||||
builder.AppendLine($"- Content ID: {contentId}");
|
||||
builder.AppendLine($"- Executable: {executableName}");
|
||||
builder.Append("- Platform: PlayStation 5");
|
||||
Log.Info(builder.ToString());
|
||||
}
|
||||
|
||||
private static string? ResolveContentId(string? bundleRoot)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bundleRoot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var candidate in new[]
|
||||
{
|
||||
Path.Combine(bundleRoot, "sce_sys", "param.json"),
|
||||
Path.Combine(bundleRoot, "param.json"),
|
||||
})
|
||||
{
|
||||
if (!File.Exists(candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(File.ReadAllBytes(candidate));
|
||||
if (doc.RootElement.TryGetProperty("contentId", out var contentId))
|
||||
{
|
||||
return contentId.GetString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or System.Text.Json.JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static App0BindingScope? BindApp0Root(string normalizedEbootPath)
|
||||
{
|
||||
const string app0VariableName = "SHARPEMU_APP0_DIR";
|
||||
@@ -440,6 +371,20 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dump tools commonly place decrypted executables in an app0/decrypted
|
||||
// sidecar while leaving Unity data in the parent app0 directory. Keep
|
||||
// loading code/modules beside the decrypted eboot, but mount /app0 at
|
||||
// the content-bearing parent so boot.config, metadata and assets resolve.
|
||||
if (string.Equals(Path.GetFileName(app0Root), "decrypted", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parentRoot = Path.GetDirectoryName(app0Root);
|
||||
if (!string.IsNullOrWhiteSpace(parentRoot) &&
|
||||
Directory.Exists(Path.Combine(parentRoot, "Media")))
|
||||
{
|
||||
app0Root = parentRoot;
|
||||
}
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable(app0VariableName, app0Root);
|
||||
return new App0BindingScope(app0VariableName);
|
||||
}
|
||||
@@ -485,6 +430,56 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetEhFrameInfo(
|
||||
SelfImage image,
|
||||
ulong imageSize,
|
||||
out ulong ehFrameHeaderAddress,
|
||||
out ulong ehFrameAddress,
|
||||
out ulong ehFrameSize)
|
||||
{
|
||||
ehFrameHeaderAddress = 0;
|
||||
ehFrameAddress = 0;
|
||||
ehFrameSize = 0;
|
||||
var imageBase = image.EntryPoint >= image.ElfHeader.EntryPoint
|
||||
? image.EntryPoint - image.ElfHeader.EntryPoint
|
||||
: 0UL;
|
||||
for (var i = 0; i < image.ProgramHeaders.Count; i++)
|
||||
{
|
||||
var header = image.ProgramHeaders[i];
|
||||
if (header.HeaderType != ProgramHeaderType.GnuEhFrame || header.MemorySize < 8)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var headerAddress = imageBase + header.VirtualAddress;
|
||||
Span<byte> ehHeader = stackalloc byte[8];
|
||||
if (!_virtualMemory.TryRead(headerAddress, ehHeader) ||
|
||||
ehHeader[0] != 1 ||
|
||||
ehHeader[1] != 0x1B)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativeOffset = BinaryPrimitives.ReadInt32LittleEndian(ehHeader[4..]);
|
||||
ehFrameHeaderAddress = headerAddress;
|
||||
ehFrameAddress = unchecked((ulong)((long)headerAddress + 4 + relativeOffset));
|
||||
if (ehFrameAddress < 0x10000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativeEhFrameAddress = ehFrameAddress - imageBase;
|
||||
ehFrameSize = header.VirtualAddress > relativeEhFrameAddress
|
||||
? header.VirtualAddress - relativeEhFrameAddress
|
||||
: imageSize > header.VirtualAddress
|
||||
? imageSize - header.VirtualAddress
|
||||
: 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private OrbisGen2Result? RunPreloadedModuleInitializers(
|
||||
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
|
||||
Generation generation,
|
||||
@@ -494,20 +489,30 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
for (var i = 0; i < loadedModuleImages.Count; i++)
|
||||
{
|
||||
var loadedModule = loadedModuleImages[i];
|
||||
if (!loadedModule.StartAtBoot)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var initEntryPoint = loadedModule.Image.InitFunctionEntryPoint;
|
||||
if (initEntryPoint < 0x10000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!KernelModuleRegistry.TryBeginModuleStart(loadedModule.Handle, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var moduleName = Path.GetFileName(loadedModule.Path);
|
||||
if (string.IsNullOrWhiteSpace(moduleName))
|
||||
{
|
||||
moduleName = $"module#{i}";
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
$"Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
|
||||
|
||||
var result = _cpuDispatcher.DispatchModuleInitializer(
|
||||
initEntryPoint,
|
||||
@@ -516,10 +521,13 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
activeRuntimeSymbols,
|
||||
moduleName,
|
||||
_cpuExecutionOptions);
|
||||
KernelModuleRegistry.CompleteModuleStart(
|
||||
loadedModule.Handle,
|
||||
result == OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
Log.Error(
|
||||
$"Module start failed: {moduleName} -> {result}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Module start failed: {moduleName} -> {result}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -540,8 +548,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
$"Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
|
||||
|
||||
var result = RunInitializerList(
|
||||
$"{label}:preinit",
|
||||
@@ -580,8 +588,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Debug(
|
||||
$" Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
|
||||
|
||||
var result = _cpuDispatcher.DispatchEntry(
|
||||
initializerAddress,
|
||||
@@ -613,12 +621,17 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
var moduleDirectories = new[]
|
||||
{
|
||||
Path.Combine(ebootDirectory, "sce_module"),
|
||||
Path.Combine(ebootDirectory, "sce_modules"),
|
||||
Path.Combine(ebootDirectory, "Media", "Modules"),
|
||||
(Path: Path.Combine(ebootDirectory, "sce_module"), StartAtBoot: true),
|
||||
(Path: Path.Combine(ebootDirectory, "sce_modules"), StartAtBoot: true),
|
||||
(Path: Path.Combine(ebootDirectory, "Media", "Modules"), StartAtBoot: true),
|
||||
// Unity native plugins are loaded later through sceKernelLoadStartModule. Map
|
||||
// them up front so the HLE loader can return a real module handle and dlsym
|
||||
// can resolve their exports, but defer DT_INIT until the guest requests them.
|
||||
(Path: Path.Combine(ebootDirectory, "Media", "Plugins"), StartAtBoot: false),
|
||||
}
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Where(Directory.Exists)
|
||||
.GroupBy(entry => entry.Path, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => group.First())
|
||||
.Where(entry => Directory.Exists(entry.Path))
|
||||
.ToArray();
|
||||
|
||||
if (moduleDirectories.Length == 0)
|
||||
@@ -628,28 +641,30 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
var allModulePaths = moduleDirectories
|
||||
.SelectMany(directory => Directory
|
||||
.EnumerateFiles(directory)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
|
||||
.Where(path =>
|
||||
.EnumerateFiles(directory.Path)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(path => (Path: path, directory.StartAtBoot)))
|
||||
.Where(entry =>
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
var extension = Path.GetExtension(entry.Path);
|
||||
return string.Equals(extension, ".prx", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase);
|
||||
})
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.GroupBy(entry => entry.Path, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => group.First())
|
||||
.ToArray();
|
||||
|
||||
var modulePaths = allModulePaths
|
||||
.Where(ShouldPreloadModule)
|
||||
.Where(entry => ShouldPreloadModule(entry.Path))
|
||||
.ToArray();
|
||||
var skippedModules = allModulePaths
|
||||
.Where(path => !ShouldPreloadModule(path))
|
||||
.Select(Path.GetFileName)
|
||||
.Where(entry => !ShouldPreloadModule(entry.Path))
|
||||
.Select(entry => Path.GetFileName(entry.Path))
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||
.ToArray();
|
||||
if (skippedModules.Length > 0)
|
||||
{
|
||||
Log.Info($"Skipping {skippedModules.Length} core module(s): {string.Join(", ", skippedModules)}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Skipping {skippedModules.Length} core module(s): {string.Join(", ", skippedModules)}");
|
||||
}
|
||||
|
||||
if (modulePaths.Length == 0)
|
||||
@@ -657,14 +672,15 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
Log.Debug($"Module search directories: {string.Join(", ", moduleDirectories)}");
|
||||
Log.Info($"Loading {modulePaths.Length} module(s)...");
|
||||
Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories.Select(entry => entry.Path))}");
|
||||
Console.Error.WriteLine($"[RUNTIME] Loading {modulePaths.Length} module(s)...");
|
||||
var loadedModules = 0;
|
||||
var failedModules = 0;
|
||||
var mergedImportCount = 0;
|
||||
var mergedSymbolCount = 0;
|
||||
foreach (var modulePath in modulePaths)
|
||||
foreach (var moduleEntry in modulePaths)
|
||||
{
|
||||
var modulePath = moduleEntry.Path;
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(modulePath);
|
||||
@@ -689,25 +705,52 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath);
|
||||
mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols);
|
||||
RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false);
|
||||
loadedImages.Add(new LoadedModuleImage(modulePath, moduleImage));
|
||||
InstallNativePluginCompatibilityHooks(importStubs, moduleImage, modulePath);
|
||||
var moduleHandle = RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false);
|
||||
var moduleName = Path.GetFileName(modulePath);
|
||||
var startAtBoot = moduleEntry.StartAtBoot ||
|
||||
string.Equals(moduleName, "libfmod.prx", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(moduleName, "libfmodstudio.prx", StringComparison.OrdinalIgnoreCase);
|
||||
loadedImages.Add(new LoadedModuleImage(modulePath, moduleImage, moduleHandle, startAtBoot));
|
||||
loadedModules++;
|
||||
|
||||
Log.Info(
|
||||
$"Loaded module {Path.GetFileName(modulePath)}: entry=0x{moduleImage.EntryPoint:X16}, imports={moduleImage.ImportStubs.Count}, symbols={moduleImage.RuntimeSymbols.Count}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Loaded module {Path.GetFileName(modulePath)}: entry=0x{moduleImage.EntryPoint:X16}, imports={moduleImage.ImportStubs.Count}, symbols={moduleImage.RuntimeSymbols.Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failedModules++;
|
||||
Log.Error($"Module load failed: {modulePath} ({ex.GetType().Name}: {ex.Message})", ex);
|
||||
Console.Error.WriteLine($"[RUNTIME] Module load failed: {modulePath} ({ex.GetType().Name}: {ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
$"Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}");
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
private static void InstallNativePluginCompatibilityHooks(
|
||||
IDictionary<ulong, string> importStubs,
|
||||
SelfImage moduleImage,
|
||||
string modulePath)
|
||||
{
|
||||
if (!string.Equals(Path.GetFileName(modulePath), "libfmod.prx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOnlySpan<string> compatibilityNids = ["uPLTdl3psGk"];
|
||||
foreach (var nid in compatibilityNids)
|
||||
{
|
||||
if (moduleImage.RuntimeSymbols.TryGetValue(nid, out var address) && address >= 0x10000)
|
||||
{
|
||||
importStubs[address] = nid;
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Installed FMOD compatibility hook: {nid} -> 0x{address:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RebindImportedDataSymbols(
|
||||
SelfImage mainImage,
|
||||
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
|
||||
@@ -724,8 +767,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
if (rebound != 0 || unresolved != 0)
|
||||
{
|
||||
Log.Info(
|
||||
$"Imported data rebind: rebound={rebound}, unresolved={unresolved}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data rebind: rebound={rebound}, unresolved={unresolved}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,8 +800,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
if (logRebind)
|
||||
{
|
||||
Log.Warning(
|
||||
$"Imported data unresolved: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} addend=0x{unchecked((ulong)relocation.Addend):X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data unresolved: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} addend=0x{unchecked((ulong)relocation.Addend):X16}");
|
||||
}
|
||||
|
||||
unresolved++;
|
||||
@@ -770,8 +813,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
if (logRebind)
|
||||
{
|
||||
Log.Error(
|
||||
$"Imported data write-failed: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data write-failed: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
}
|
||||
|
||||
unresolved++;
|
||||
@@ -780,8 +823,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
if (logRebind)
|
||||
{
|
||||
Log.Debug(
|
||||
$"Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
}
|
||||
|
||||
rebound++;
|
||||
@@ -817,8 +860,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
if (!string.Equals(existingNid, nid, StringComparison.Ordinal))
|
||||
{
|
||||
Log.Warning(
|
||||
$"Import stub conflict at 0x{address:X16}: keep={existingNid}, skip={nid} ({Path.GetFileName(modulePath)})");
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Import stub conflict at 0x{address:X16}: keep={existingNid}, skip={nid} ({Path.GetFileName(modulePath)})");
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -912,7 +955,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return !PreloadSkipModules.Contains(fileName);
|
||||
}
|
||||
|
||||
private static void RegisterLoadedModule(string modulePath, SelfImage image, bool isMain, bool isSystemModule)
|
||||
private int RegisterLoadedModule(string modulePath, SelfImage image, bool isMain, bool isSystemModule)
|
||||
{
|
||||
if (!TryComputeImageRange(image, out var baseAddress, out var size))
|
||||
{
|
||||
@@ -920,15 +963,27 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
size = 0;
|
||||
}
|
||||
|
||||
_ = TryGetEhFrameInfo(
|
||||
image,
|
||||
size,
|
||||
out var ehFrameHeaderAddress,
|
||||
out var ehFrameAddress,
|
||||
out var ehFrameSize);
|
||||
var handle = KernelModuleRegistry.RegisterModule(
|
||||
modulePath,
|
||||
baseAddress,
|
||||
size,
|
||||
image.EntryPoint,
|
||||
image.InitFunctionEntryPoint,
|
||||
ehFrameHeaderAddress,
|
||||
ehFrameAddress,
|
||||
ehFrameSize,
|
||||
isMain,
|
||||
isSystemModule);
|
||||
Log.Info(
|
||||
$"Registered module handle={handle} name={Path.GetFileName(modulePath)} base=0x{baseAddress:X16} size=0x{size:X16}");
|
||||
KernelModuleRegistry.RegisterModuleSymbols(handle, image.RuntimeSymbols);
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Registered module handle={handle} name={Path.GetFileName(modulePath)} base=0x{baseAddress:X16} size=0x{size:X16}");
|
||||
return handle;
|
||||
}
|
||||
|
||||
private static bool TryComputeImageRange(SelfImage image, out ulong baseAddress, out ulong size)
|
||||
|
||||
Reference in New Issue
Block a user