mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d204afd10 | |||
| 7bd1e8dc29 | |||
| fad61e116e | |||
| bc297fa175 | |||
| 79e767ed9f | |||
| 1ea1396979 | |||
| a1dea499bc | |||
| fee6af3136 | |||
| bbf5ff7be8 | |||
| 0c859f04ad | |||
| 994438963d | |||
| 233af123af | |||
| 02eb9b30e9 | |||
| fa46819030 | |||
| 7a915e88dc | |||
| 812879aa81 | |||
| 71ba5cf1db | |||
| 8f108ca01d | |||
| cecf55bf2e | |||
| 0ede7c70a9 | |||
| 8c39a55501 | |||
| 0a4b36ffcb | |||
| 1a1d61b21c | |||
| 3cf96f38de | |||
| d8c44bb64f | |||
| a4b5171586 | |||
| 1f71c970d9 | |||
| 028494a83b | |||
| 73ae502e09 | |||
| 51d204775e | |||
| 7ce6b9e2af | |||
| d4cd4eb96f |
@@ -0,0 +1,59 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Bug Report
|
||||
description: Report a bug in the emulator
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Before creating an issue
|
||||
|
||||
- Do not create issues involving **software piracy**. Our rules specifically prohibit this.
|
||||
Issues related to piracy will be closed and the author may be banned from the repository.
|
||||
|
||||
- The issue tracker is **not for asking questions**.
|
||||
Question-type issues will be closed.
|
||||
|
||||
- Only open an issue if you are reporting an actual **bug in the emulator**.
|
||||
|
||||
- type: input
|
||||
id: emulator_version
|
||||
attributes:
|
||||
label: Emulator Version / Commit
|
||||
description: The emulator version or git commit where the bug occurred
|
||||
placeholder: e.g. v0.0.2 / a1b2c3d
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: Describe the problem and how to reproduce it
|
||||
placeholder: |
|
||||
Steps to reproduce:
|
||||
1. Launch emulator
|
||||
2. Load game
|
||||
3. Crash occurs
|
||||
|
||||
Expected behavior:
|
||||
...
|
||||
|
||||
Actual behavior:
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log File
|
||||
description: |
|
||||
Drag & drop your log file here, or paste the log contents.
|
||||
|
||||
Supported: .log / .txt / .zip
|
||||
Large logs should be uploaded as a file.
|
||||
render: shell
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,144 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Game Compatibility Report
|
||||
description: Report compatibility status of a game
|
||||
labels: ["game-compatibility"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to report the compatibility status of a game.
|
||||
|
||||
Please include the **game name in the issue title**.
|
||||
|
||||
Example title:
|
||||
Demon’s Souls (PPSA01341)
|
||||
|
||||
- type: input
|
||||
id: game_name
|
||||
attributes:
|
||||
label: Game Name
|
||||
placeholder: e.g. Demon's Souls
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: title_id
|
||||
attributes:
|
||||
label: Title ID
|
||||
description: Game title ID (PPSA / CUSA / PCSA etc.)
|
||||
placeholder: e.g. PPSA01341
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: game_version
|
||||
attributes:
|
||||
label: Game Version
|
||||
placeholder: e.g. 1.00 / 1.09
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: status
|
||||
attributes:
|
||||
label: Current Status
|
||||
description: What happens when running the game?
|
||||
options:
|
||||
- Nothing happens
|
||||
- Crashes on boot
|
||||
- Boots to black screen
|
||||
- Shows intro / menus
|
||||
- Ingame but unstable
|
||||
- Playable
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe what happens when running the game
|
||||
placeholder: |
|
||||
Example:
|
||||
- Game boots
|
||||
- Shows intro logos
|
||||
- Crashes when entering gameplay
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: upload
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log File
|
||||
description: Drag and drop your log file here, or click to browse
|
||||
validations:
|
||||
required: false
|
||||
accept: ".log,.txt,.zip"
|
||||
|
||||
- type: dropdown
|
||||
id: operating_system
|
||||
attributes:
|
||||
label: Operating System
|
||||
options:
|
||||
- Windows 11
|
||||
- Windows 10
|
||||
- Linux
|
||||
- macOS
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: e.g. AMD Ryzen 7 5800X3D
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: gpu
|
||||
attributes:
|
||||
label: GPU
|
||||
placeholder: e.g. NVIDIA GeForce RTX 4070 Super
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: ram
|
||||
attributes:
|
||||
label: RAM
|
||||
placeholder: e.g. 32 GB
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: emulator_version
|
||||
attributes:
|
||||
label: Emulator Version / Commit
|
||||
placeholder: e.g. v0.0.1 / a1b2c3d
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: renderer
|
||||
attributes:
|
||||
label: Renderer
|
||||
options:
|
||||
- Vulkan
|
||||
- OpenGL
|
||||
- DirectX
|
||||
- Software
|
||||
- N/A
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Notes
|
||||
description: Screenshots, settings, patches, workarounds, or anything else relevant
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
@@ -1,3 +1,6 @@
|
||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
@@ -79,7 +82,7 @@ jobs:
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
dotnet-version: 10.0.103
|
||||
cache: true
|
||||
cache-dependency-path: |
|
||||
Directory.Packages.props
|
||||
@@ -106,7 +109,7 @@ jobs:
|
||||
Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ needs.init.outputs.artifact-name }}
|
||||
path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }}
|
||||
@@ -123,7 +126,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ needs.init.outputs.artifact-name }}
|
||||
path: release
|
||||
|
||||
@@ -29,6 +29,8 @@ arm64/
|
||||
packages/
|
||||
*.nupkg
|
||||
.nuget/
|
||||
.dotnet-home/
|
||||
.cache/
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -9,5 +9,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Iced" Version="1.21.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+3
-1
@@ -3,10 +3,12 @@ version = 1
|
||||
[[annotations]]
|
||||
path = [
|
||||
"REUSE.toml",
|
||||
"global.json",
|
||||
"**/packages.lock.json",
|
||||
"scripts/ps5_names.txt",
|
||||
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
|
||||
"_logs/**"
|
||||
"_logs/**",
|
||||
".github/images/**"
|
||||
]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.103",
|
||||
"rollForward": "disable"
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,62 @@
|
||||
"resolved": "10.0.3",
|
||||
"contentHash": "0B6nZyCHWXnvmlB559oduOspVdNOnpNXPjhpWVMovLPAsDVG7A4jJR9rzECf67JUzxP8/ee/wA8clwIzJcWNFA=="
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
@@ -23,7 +79,10 @@
|
||||
"sharpemu.libs": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.0, )"
|
||||
"SharpEmu.HLE": "[1.0.0, )",
|
||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
||||
"Silk.NET.Windowing": "[2.23.0, )"
|
||||
}
|
||||
},
|
||||
"sharpemu.logging": {
|
||||
@@ -34,10 +93,57 @@
|
||||
"requested": "[1.21.0, )",
|
||||
"resolved": "1.21.0",
|
||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
||||
},
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net10.0/linux-x64": {},
|
||||
"net10.0/osx-arm64": {},
|
||||
"net10.0/win-x64": {}
|
||||
"net10.0/linux-x64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
},
|
||||
"net10.0/osx-arm64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
},
|
||||
"net10.0/win-x64": {
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
{
|
||||
private enum EntryFrameKind
|
||||
{
|
||||
ProcessEntry,
|
||||
ModuleInitializer,
|
||||
}
|
||||
|
||||
private const ulong StackBaseAddress = 0x7FFF_F000_0000UL;
|
||||
private const ulong StackSize = 0x0020_0000UL;
|
||||
private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL;
|
||||
@@ -88,13 +94,44 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public OrbisGen2Result DispatchModuleInitializer(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string moduleName = "module",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
{
|
||||
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
|
||||
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
|
||||
|
||||
try
|
||||
{
|
||||
return DispatchEntryCore(
|
||||
entryPoint,
|
||||
generation,
|
||||
importStubs,
|
||||
runtimeSymbols,
|
||||
moduleName,
|
||||
executionOptions,
|
||||
EntryFrameKind.ModuleInitializer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private OrbisGen2Result DispatchEntryCore(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
CpuExecutionOptions executionOptions = default,
|
||||
EntryFrameKind frameKind = EntryFrameKind.ProcessEntry)
|
||||
{
|
||||
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
|
||||
|
||||
@@ -156,6 +193,11 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeGuestFrameChainSentinel(context))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeTls(context, tlsBase))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
@@ -164,23 +206,33 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
var effectiveImportStubs = importStubs is null
|
||||
? new Dictionary<ulong, string>()
|
||||
: new Dictionary<ulong, string>(importStubs);
|
||||
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
|
||||
if (programExitHandlerStubAddress == 0)
|
||||
var entryParamsConfigured = false;
|
||||
if (frameKind == EntryFrameKind.ProcessEntry)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (ShouldInjectBootstrapPayload(entryPoint))
|
||||
{
|
||||
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
|
||||
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
|
||||
if (programExitHandlerStubAddress == 0)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
entryParamsConfigured = true;
|
||||
|
||||
if (ShouldInjectBootstrapPayload(entryPoint))
|
||||
{
|
||||
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!InitializeModuleInitializerFrame(context))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var entryFrameDiagnostic = BuildEntryFrameDiagnostic(
|
||||
@@ -188,7 +240,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
context,
|
||||
sentinelEnabled: true,
|
||||
sentinelValue: returnToHostStubAddress,
|
||||
entryParamsConfigured: true);
|
||||
entryParamsConfigured: entryParamsConfigured);
|
||||
|
||||
if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly)
|
||||
{
|
||||
@@ -316,6 +368,23 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||
}
|
||||
|
||||
private static bool InitializeGuestFrameChainSentinel(CpuContext context)
|
||||
{
|
||||
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
|
||||
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
|
||||
var seedRsp = sentinelFrame - sizeof(ulong);
|
||||
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
|
||||
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
|
||||
!context.TryWriteUInt64(seedRsp, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context[CpuRegister.Rbp] = sentinelFrame;
|
||||
context[CpuRegister.Rsp] = seedRsp;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InitializeProcessEntryFrame(
|
||||
CpuContext context,
|
||||
string processImageName,
|
||||
@@ -360,6 +429,17 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InitializeModuleInitializerFrame(CpuContext context)
|
||||
{
|
||||
context[CpuRegister.Rdi] = 0;
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
context[CpuRegister.R8] = 0;
|
||||
context[CpuRegister.R9] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong AlignDown(ulong value, ulong alignment)
|
||||
{
|
||||
return value & ~(alignment - 1);
|
||||
|
||||
@@ -34,4 +34,12 @@ public interface ICpuDispatcher
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default);
|
||||
|
||||
OrbisGen2Result DispatchModuleInitializer(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string moduleName = "module",
|
||||
CpuExecutionOptions executionOptions = default);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.Core.Cpu.Disasm;
|
||||
@@ -254,6 +255,9 @@ public sealed partial class DirectExecutionBackend
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP");
|
||||
}
|
||||
DumpRecentImportTrace();
|
||||
DumpGuestDisasmDiagnostics(rip, rbp);
|
||||
DumpGuestReferenceDiagnostics();
|
||||
DumpGuestPointerWindowDiagnostics();
|
||||
break;
|
||||
case 2147483651u:
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)");
|
||||
@@ -322,6 +326,239 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (rip >= 0x20)
|
||||
{
|
||||
DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ulong frame = rbp;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (frame < 140733193388032L || frame > 140737488355327L)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
|
||||
if (ret >= 0x40)
|
||||
{
|
||||
DumpGuestInstructionStream($"frame#{i}-ret-prelude", ret - 0x40, 24);
|
||||
}
|
||||
|
||||
ulong next = (ulong)Marshal.ReadInt64((nint)frame);
|
||||
if (next <= frame)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
frame = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Could not dump disasm diagnostics.");
|
||||
}
|
||||
|
||||
var extraAddresses = Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM_ADDRS");
|
||||
if (string.IsNullOrWhiteSpace(extraAddresses))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var token in extraAddresses.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? token[2..]
|
||||
: token;
|
||||
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) || address < 0x20)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DumpGuestInstructionStream($"extra-0x{address:X16}", address, 48);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void DumpGuestReferenceDiagnostics()
|
||||
{
|
||||
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_REFSCAN_ADDRS"));
|
||||
if (targetList.Count == 0 || _cpuContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const ulong scanBase = 0x0000000800000000UL;
|
||||
const ulong scanEnd = 0x0000000810000000UL;
|
||||
const int maxHitsPerTarget = 24;
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Ref scan targets: {string.Join(", ", targetList.ConvertAll(static addr => $"0x{addr:X16}"))}");
|
||||
|
||||
var hitCounts = new Dictionary<ulong, int>(targetList.Count);
|
||||
for (var i = 0; i < targetList.Count; i++)
|
||||
{
|
||||
hitCounts[targetList[i]] = 0;
|
||||
}
|
||||
|
||||
ulong address = scanBase;
|
||||
while (address < scanEnd)
|
||||
{
|
||||
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ulong regionBase = mbi.BaseAddress;
|
||||
ulong regionEnd = regionBase + mbi.RegionSize;
|
||||
if (regionEnd <= address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (mbi.State == MEM_COMMIT &&
|
||||
IsReadableProtection(mbi.Protect) &&
|
||||
IsExecutableProtection(mbi.Protect))
|
||||
{
|
||||
ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
|
||||
}
|
||||
|
||||
var allTargetsSatisfied = true;
|
||||
for (var i = 0; i < targetList.Count; i++)
|
||||
{
|
||||
if (hitCounts[targetList[i]] < maxHitsPerTarget)
|
||||
{
|
||||
allTargetsSatisfied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allTargetsSatisfied)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
address = regionEnd;
|
||||
}
|
||||
|
||||
for (var i = 0; i < targetList.Count; i++)
|
||||
{
|
||||
var target = targetList[i];
|
||||
if (!hitCounts.TryGetValue(target, out var count) || count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Ref scan 0x{target:X16}: none");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpGuestPointerWindowDiagnostics()
|
||||
{
|
||||
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS"));
|
||||
if (targetList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var windowSize = 0x80;
|
||||
var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE");
|
||||
if (!string.IsNullOrWhiteSpace(rawWindowSize))
|
||||
{
|
||||
var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? rawWindowSize[2..]
|
||||
: rawWindowSize;
|
||||
if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) &&
|
||||
parsedWindowSize > 0)
|
||||
{
|
||||
windowSize = parsedWindowSize;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanExecutableRegionForTargetReferences(
|
||||
ulong regionBase,
|
||||
ulong regionEnd,
|
||||
IReadOnlyList<ulong> targets,
|
||||
IDictionary<ulong, int> hitCounts,
|
||||
int maxHitsPerTarget)
|
||||
{
|
||||
if (_cpuContext == null || regionEnd <= regionBase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong rip = regionBase;
|
||||
while (rip < regionEnd)
|
||||
{
|
||||
if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
|
||||
!IcedDecoder.TryDecode(rip, bytes, out var instruction) ||
|
||||
instruction.Length <= 0)
|
||||
{
|
||||
rip++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.MemoryAddress is { } memoryAddress)
|
||||
{
|
||||
for (var i = 0; i < targets.Count; i++)
|
||||
{
|
||||
var target = targets[i];
|
||||
if (memoryAddress != target ||
|
||||
!hitCounts.TryGetValue(target, out var count) ||
|
||||
count >= maxHitsPerTarget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hitCounts[target] = count + 1;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
|
||||
}
|
||||
}
|
||||
|
||||
rip += (ulong)instruction.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ulong> ParseDiagnosticAddresses(string? rawValue)
|
||||
{
|
||||
var result = new List<ulong>();
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? token[2..]
|
||||
: token;
|
||||
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.Contains(address))
|
||||
{
|
||||
result.Add(address);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
|
||||
{
|
||||
if (baseAddress < 0x10000 || size <= 0)
|
||||
|
||||
@@ -154,15 +154,57 @@ public sealed partial class DirectExecutionBackend
|
||||
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
|
||||
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;
|
||||
bool flag3 = num >= 1020 && num <= 1040;
|
||||
if (!flag0 && (num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3))
|
||||
bool logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal);
|
||||
string importFilter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FILTER");
|
||||
bool flag4 = !string.IsNullOrWhiteSpace(importFilter);
|
||||
bool flag5 = false;
|
||||
ExportedFunction matchedExport = null;
|
||||
if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export))
|
||||
{
|
||||
if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export))
|
||||
matchedExport = export;
|
||||
if (flag4)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid})");
|
||||
flag5 = export.LibraryName.Contains(importFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|| export.Name.Contains(importFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|| importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (flag4)
|
||||
{
|
||||
flag5 = importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
bool flag6 = logAllImports || flag5;
|
||||
if (!flag0 && (flag6 || num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3))
|
||||
{
|
||||
if (matchedExport != null)
|
||||
{
|
||||
if (flag6)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{num}: {matchedExport.LibraryName}:{matchedExport.Name} ({importStubEntry.Nid}) " +
|
||||
$"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {matchedExport.LibraryName}:{matchedExport.Name} ({importStubEntry.Nid})");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid}");
|
||||
if (flag6)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid} " +
|
||||
$"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid}");
|
||||
}
|
||||
}
|
||||
if (flag6)
|
||||
{
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
if (!flag0)
|
||||
@@ -212,31 +254,10 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
TryBypassStackChkFailTrap(num, num7);
|
||||
}
|
||||
if (importStubEntry.Nid == "9rAeANT2tyE" || importStubEntry.Nid == "1j3S3n-tTW4")
|
||||
{
|
||||
ulong rspDbg = _cpuContext[CpuRegister.Rsp];
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ImportDbg#{num}: nid={importStubEntry.Nid} " +
|
||||
$"ret=0x{num7:X16} rsp=0x{rspDbg:X16} rsp&7=0x{(rspDbg & 7):X} rsp&F=0x{(rspDbg & 0xF):X}");
|
||||
|
||||
if (_cpuContext.TryReadUInt64(rspDbg, out var s0))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x00] = 0x{s0:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 8, out var s8))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x08] = 0x{s8:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 16, out var s10))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x10] = 0x{s10:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 24, out var s18))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x18] = 0x{s18:X16}");
|
||||
|
||||
ProbeReturnRip(num7, num);
|
||||
Console.Error.Flush();
|
||||
}
|
||||
ProbeReturnRip(0x0000000800EA020Eul, 999001);
|
||||
ProbeReturnRip(0x0000000800EA0213ul, 999002);
|
||||
ProbeReturnRip(0x0000000800EA040Aul, 999003);
|
||||
try
|
||||
{
|
||||
OrbisGen2Result orbisGen2Result;
|
||||
bool dispatchResolved = true;
|
||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchBootstrapBridge();
|
||||
@@ -247,34 +268,31 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
else
|
||||
{
|
||||
orbisGen2Result = _moduleManager.Dispatch(importStubEntry.Nid, _cpuContext);
|
||||
dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, _cpuContext, out orbisGen2Result);
|
||||
}
|
||||
switch (orbisGen2Result)
|
||||
if (!dispatchResolved)
|
||||
{
|
||||
case OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND:
|
||||
LastError = "Missing HLE export for NID: " + importStubEntry.Nid;
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}");
|
||||
if (importStubEntry.Nid == "L-Q3LEjIbgA")
|
||||
LastError = "Missing HLE export for NID: " + importStubEntry.Nid;
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}");
|
||||
if (importStubEntry.Nid == "L-Q3LEjIbgA")
|
||||
{
|
||||
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c)
|
||||
{
|
||||
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c)
|
||||
{
|
||||
int num10 = c;
|
||||
return num10.ToString("X2");
|
||||
}));
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]");
|
||||
Delegate function;
|
||||
bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function);
|
||||
ExportedFunction export2;
|
||||
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
|
||||
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
|
||||
break;
|
||||
case OrbisGen2Result.ORBIS_GEN2_OK:
|
||||
break;
|
||||
int num10 = c;
|
||||
return num10.ToString("X2");
|
||||
}));
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]");
|
||||
Delegate function;
|
||||
bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function);
|
||||
ExportedFunction export2;
|
||||
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
|
||||
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
|
||||
}
|
||||
}
|
||||
else if (orbisGen2Result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
|
||||
}
|
||||
_cpuContext[CpuRegister.Rbx] = value3;
|
||||
_cpuContext[CpuRegister.Rbp] = value4;
|
||||
@@ -284,6 +302,23 @@ public sealed partial class DirectExecutionBackend
|
||||
_cpuContext[CpuRegister.R15] = value8;
|
||||
_cpuContext[CpuRegister.Rdi] = value;
|
||||
_cpuContext[CpuRegister.Rsi] = value2;
|
||||
if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitStatus, out var exitReason))
|
||||
{
|
||||
if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, exitStatus))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = unchecked((ulong)exitStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
LastError = $"Failed to complete guest entry after {importStubEntry.Nid}: missing host return sentinel";
|
||||
_cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
}
|
||||
}
|
||||
if (GuestThreadExecution.TryConsumeCurrentThreadBlock(out var blockReason) &&
|
||||
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, blockReason))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 0uL;
|
||||
}
|
||||
if (flag || flag2 || flag3)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||
@@ -324,6 +359,49 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status)
|
||||
{
|
||||
ulong hostExit = _entryReturnSentinelRip;
|
||||
if (hostExit < 65536)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
*(ulong*)(argPackPtr + 96) = hostExit;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Guest entry exit at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={reason} status={status}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason)
|
||||
{
|
||||
ulong hostExit = _entryReturnSentinelRip;
|
||||
if (hostExit < 65536)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
*(ulong*)(argPackPtr + 96) = hostExit;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_guestThreadYieldRequested = true;
|
||||
_guestThreadYieldReason = string.IsNullOrWhiteSpace(reason) ? nid : reason;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={_guestThreadYieldReason}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (dispatchIndex < 1200)
|
||||
@@ -334,7 +412,12 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RecordImportLoopSignature(BuildImportLoopSignature(nid, returnRip, arg0, arg1));
|
||||
if (!_importNidHashCache.TryGetValue(nid, out var value))
|
||||
{
|
||||
value = StableHash64(nid);
|
||||
_importNidHashCache[nid] = value;
|
||||
}
|
||||
RecordImportLoopSignature(value, returnRip, BuildImportLoopSignature(value, returnRip, arg0, arg1));
|
||||
if (!HasRepeatingImportLoopPattern())
|
||||
{
|
||||
if (_importLoopPatternHits > 0)
|
||||
@@ -347,21 +430,18 @@ public sealed partial class DirectExecutionBackend
|
||||
return _importLoopPatternHits >= 6;
|
||||
}
|
||||
|
||||
private ulong BuildImportLoopSignature(string nid, ulong returnRip, ulong arg0, ulong arg1)
|
||||
private ulong BuildImportLoopSignature(ulong nidHash, ulong returnRip, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (!_importNidHashCache.TryGetValue(nid, out var value))
|
||||
{
|
||||
value = StableHash64(nid);
|
||||
_importNidHashCache[nid] = value;
|
||||
}
|
||||
ulong num = returnRip >> 2;
|
||||
ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL);
|
||||
return num ^ value * 11400714819323198485uL ^ num2;
|
||||
return num ^ nidHash * 11400714819323198485uL ^ num2;
|
||||
}
|
||||
|
||||
private void RecordImportLoopSignature(ulong signature)
|
||||
private void RecordImportLoopSignature(ulong nidHash, ulong returnRip, ulong signature)
|
||||
{
|
||||
_importLoopSignatures[_importLoopSignatureWriteIndex] = signature;
|
||||
_importLoopNidHashes[_importLoopSignatureWriteIndex] = nidHash;
|
||||
_importLoopReturnRips[_importLoopSignatureWriteIndex] = returnRip;
|
||||
_importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length;
|
||||
if (_importLoopSignatureCount < _importLoopSignatures.Length)
|
||||
{
|
||||
@@ -405,7 +485,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return IsSevereImportLoopPattern(num);
|
||||
}
|
||||
|
||||
private ulong GetImportLoopSignatureFromTail(int offset)
|
||||
@@ -418,6 +498,64 @@ public sealed partial class DirectExecutionBackend
|
||||
return _importLoopSignatures[num % _importLoopSignatures.Length];
|
||||
}
|
||||
|
||||
private bool IsSevereImportLoopPattern(int sampleCount)
|
||||
{
|
||||
int num = CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, sampleCount, 3);
|
||||
if (num > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3);
|
||||
if (num2 > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num3 = Math.Min(_importLoopSignatureCount, Math.Max(sampleCount * 8, ImportLoopWideDiversityWindow));
|
||||
if (num3 <= sampleCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, num3, 3) > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, num3, 3) <= 2;
|
||||
}
|
||||
|
||||
private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter)
|
||||
{
|
||||
int num = Math.Min(sampleCount, _importLoopSignatureCount);
|
||||
int num2 = 0;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
ulong importLoopValueFromTail = GetImportLoopValueFromTail(source, i);
|
||||
bool flag = false;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
if (GetImportLoopValueFromTail(source, j) == importLoopValueFromTail)
|
||||
{
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag && ++num2 >= stopAfter)
|
||||
{
|
||||
return num2;
|
||||
}
|
||||
}
|
||||
return num2;
|
||||
}
|
||||
|
||||
private ulong GetImportLoopValueFromTail(ulong[] source, int offset)
|
||||
{
|
||||
int num = _importLoopSignatureWriteIndex - 1 - offset;
|
||||
while (num < 0)
|
||||
{
|
||||
num += source.Length;
|
||||
}
|
||||
return source[num % source.Length];
|
||||
}
|
||||
|
||||
private bool ShouldSuppressStrlenTrace(string nid)
|
||||
{
|
||||
return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports;
|
||||
|
||||
@@ -7,12 +7,19 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu;
|
||||
using SharpEmu.Core.Loader;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable
|
||||
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IGuestThreadScheduler, IDisposable
|
||||
{
|
||||
private const int ImportLoopHistoryLength = 2048;
|
||||
|
||||
private const int ImportLoopWideDiversityWindow = 768;
|
||||
|
||||
private readonly struct ImportStubEntry
|
||||
{
|
||||
public ulong Address { get; }
|
||||
@@ -83,12 +90,24 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private const ulong GuestImageScanEnd = 36507222016uL;
|
||||
|
||||
private const ulong GuestThreadStackBaseAddress = 0x7FFF_E000_0000UL;
|
||||
|
||||
private const ulong GuestThreadTlsBaseAddress = 0x7FFE_0000_0000UL;
|
||||
|
||||
private const ulong GuestThreadStackSize = 0x0020_0000UL;
|
||||
|
||||
private const ulong GuestThreadTlsSize = 0x0001_0000UL;
|
||||
|
||||
private const ulong GuestThreadRegionStride = 0x0100_0000UL;
|
||||
|
||||
private const uint PAGE_EXECUTE_READWRITE = 64u;
|
||||
|
||||
private const uint PAGE_READWRITE = 4u;
|
||||
|
||||
private const uint PAGE_EXECUTE_READ = 32u;
|
||||
|
||||
private const int TlsHandlerRegionSize = 16384;
|
||||
|
||||
private const ulong TlsModuleAllocStart = 140726751354880uL;
|
||||
|
||||
private const ulong TlsModuleAllocStride = 65536uL;
|
||||
@@ -99,8 +118,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private nint _tlsBaseAddress;
|
||||
|
||||
private nint _ownedTlsBaseAddress;
|
||||
|
||||
private bool _ownsTlsBaseAddress;
|
||||
|
||||
private int _tlsPatchStubOffset;
|
||||
|
||||
private nint _unresolvedReturnStub;
|
||||
|
||||
private nint _rawExceptionHandler;
|
||||
@@ -175,7 +198,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private ulong _entryReturnSentinelRip;
|
||||
|
||||
private readonly ulong[] _importLoopSignatures = new ulong[192];
|
||||
private readonly ulong[] _importLoopSignatures = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private readonly ulong[] _importLoopNidHashes = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private readonly ulong[] _importLoopReturnRips = new ulong[ImportLoopHistoryLength];
|
||||
|
||||
private int _importLoopSignatureCount;
|
||||
|
||||
@@ -185,6 +212,52 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private readonly Dictionary<string, ulong> _importNidHashCache = new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||
|
||||
private enum GuestThreadRunState
|
||||
{
|
||||
Ready,
|
||||
Running,
|
||||
Blocked,
|
||||
Exited,
|
||||
Faulted,
|
||||
}
|
||||
|
||||
private enum GuestNativeCallExitReason
|
||||
{
|
||||
Returned,
|
||||
Blocked,
|
||||
ForcedExit,
|
||||
Exception,
|
||||
}
|
||||
|
||||
private sealed class GuestThreadState
|
||||
{
|
||||
public ulong ThreadHandle { get; init; }
|
||||
|
||||
public ulong EntryPoint { get; init; }
|
||||
|
||||
public ulong Argument { get; init; }
|
||||
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
public CpuContext Context { get; init; } = null!;
|
||||
|
||||
public GuestThreadRunState State { get; set; }
|
||||
|
||||
public string? BlockReason { get; set; }
|
||||
}
|
||||
|
||||
private readonly object _guestThreadGate = new object();
|
||||
|
||||
private readonly Queue<GuestThreadState> _readyGuestThreads = new Queue<GuestThreadState>();
|
||||
|
||||
private readonly Dictionary<ulong, GuestThreadState> _guestThreads = new Dictionary<ulong, GuestThreadState>();
|
||||
|
||||
private int _guestThreadPumpDepth;
|
||||
|
||||
private bool _guestThreadYieldRequested;
|
||||
|
||||
private string? _guestThreadYieldReason;
|
||||
|
||||
private bool _forcedGuestExit;
|
||||
|
||||
private ulong _lastAvTraceRip;
|
||||
@@ -207,7 +280,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private nint _selfHandlePtr;
|
||||
|
||||
private static readonly byte[] TlsPattern = new byte[9] { 100, 72, 139, 4, 37, 0, 0, 0, 0 };
|
||||
private const int MinTlsPatchInstructionBytes = 9;
|
||||
|
||||
private delegate ulong ImportGatewayDelegate(nint backendHandle, int importIndex, nint argPackPtr);
|
||||
private delegate int RawExceptionHandlerDelegate(void* exceptionInfo);
|
||||
@@ -324,6 +397,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate TLS base");
|
||||
}
|
||||
_ownedTlsBaseAddress = _tlsBaseAddress;
|
||||
_ownsTlsBaseAddress = true;
|
||||
SeedTlsLayout(_tlsBaseAddress);
|
||||
_hostRspSlotStorage = (nint)VirtualAlloc(null, 4096u, 12288u, 4u);
|
||||
@@ -364,12 +438,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
_importLoopSignatureWriteIndex = 0;
|
||||
_importLoopPatternHits = 0;
|
||||
_importNidHashCache.Clear();
|
||||
ClearGuestThreads();
|
||||
_contextualUnresolvedReturnSites.Clear();
|
||||
_stallWatchdogTriggered = 0;
|
||||
_stallWatchdogStop = false;
|
||||
_patchedEa020eLookupCall = false;
|
||||
MarkExecutionProgress();
|
||||
BindTlsBase(context);
|
||||
var previousGuestThreadScheduler = GuestThreadExecution.Scheduler;
|
||||
GuestThreadExecution.Scheduler = this;
|
||||
try
|
||||
{
|
||||
if (!SetupImportStubs(importStubs))
|
||||
@@ -395,6 +472,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
}
|
||||
finally
|
||||
{
|
||||
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
||||
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
||||
}
|
||||
}
|
||||
@@ -509,6 +587,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} not in HLE table, checking runtime symbols...");
|
||||
|
||||
if (TryResolveRuntimeSymbolAddress(nid, out var directValue) && IsDirectImportTargetUsable(directValue))
|
||||
{
|
||||
targetAddress = directValue;
|
||||
resolvedSymbol = nid;
|
||||
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} -> runtime symbol 0x{targetAddress:X16}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Aerolib.Instance.TryGetByNid(nid, out var symbolByNid))
|
||||
{
|
||||
if (!PreferLleForLibcExport(symbolByNid.ExportName))
|
||||
@@ -585,14 +671,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
return exportName switch
|
||||
{
|
||||
"_init_env" or
|
||||
"atexit" or
|
||||
"strlen" or
|
||||
"strnlen" or
|
||||
"strcmp" or
|
||||
"strncmp" or
|
||||
"strcpy" or
|
||||
"strncpy" or
|
||||
"memcpy" or
|
||||
"memmove" or
|
||||
"memset" or
|
||||
@@ -633,18 +711,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
}
|
||||
if (num != _tlsBaseAddress)
|
||||
{
|
||||
if (_ownsTlsBaseAddress && _tlsBaseAddress != 0)
|
||||
{
|
||||
VirtualFree((void*)_tlsBaseAddress, 0u, 32768u);
|
||||
_ownsTlsBaseAddress = false;
|
||||
}
|
||||
_tlsBaseAddress = num;
|
||||
_ownsTlsBaseAddress = _tlsBaseAddress == _ownedTlsBaseAddress;
|
||||
}
|
||||
if (_tlsBaseAddress != 0)
|
||||
{
|
||||
context.FsBase = (ulong)_tlsBaseAddress;
|
||||
context.GsBase = (ulong)_tlsBaseAddress;
|
||||
SeedTlsLayout(_tlsBaseAddress);
|
||||
UpdateTlsHandlerBase(_tlsBaseAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,6 +732,30 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
*(ulong*)(tlsBase + 96) = num;
|
||||
}
|
||||
|
||||
private unsafe void UpdateTlsHandlerBase(nint tlsBase)
|
||||
{
|
||||
if (_tlsHandlerAddress == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint oldProtect = default;
|
||||
if (!VirtualProtect((void*)_tlsHandlerAddress, 16u, 64u, &oldProtect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
*(long*)((byte*)_tlsHandlerAddress + 2) = tlsBase;
|
||||
}
|
||||
finally
|
||||
{
|
||||
VirtualProtect((void*)_tlsHandlerAddress, 16u, oldProtect, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 16u);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe nint CreateImportHandlerTrampoline(int importIndex)
|
||||
{
|
||||
void* ptr = VirtualAlloc(null, 192u, 12288u, 64u);
|
||||
@@ -838,10 +937,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
private unsafe void CreateTlsHandler()
|
||||
{
|
||||
_tlsHandlerAddress = (nint)TryAllocateNearEntry(256u);
|
||||
_tlsHandlerAddress = (nint)TryAllocateNearEntry(TlsHandlerRegionSize);
|
||||
if (_tlsHandlerAddress == 0)
|
||||
{
|
||||
_tlsHandlerAddress = (nint)VirtualAlloc(null, 256u, 12288u, 64u);
|
||||
_tlsHandlerAddress = (nint)VirtualAlloc(null, TlsHandlerRegionSize, 12288u, 64u);
|
||||
}
|
||||
if (_tlsHandlerAddress == 0)
|
||||
{
|
||||
@@ -854,9 +953,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
*(long*)(tlsHandlerAddress + num) = _tlsBaseAddress;
|
||||
num += 8;
|
||||
tlsHandlerAddress[num++] = 195;
|
||||
_tlsPatchStubOffset = (num + 15) & ~15;
|
||||
uint num2 = default(uint);
|
||||
VirtualProtect((void*)_tlsHandlerAddress, 256u, 32u, &num2);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 256u);
|
||||
VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}");
|
||||
}
|
||||
|
||||
@@ -936,6 +1036,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
ulong num2 = num + MaxScanBytes;
|
||||
int num3 = 0;
|
||||
int num4 = 0;
|
||||
int num9 = 0;
|
||||
while (num < num2)
|
||||
{
|
||||
if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0)
|
||||
@@ -952,17 +1053,21 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
uint num7 = lpBuffer.Protect & 0xFF;
|
||||
bool flag = lpBuffer.State == 4096 && (lpBuffer.Protect & PAGE_GUARD) == 0 && num7 != PAGE_NOACCESS;
|
||||
bool flag2 = num7 == PAGE_EXECUTE || num7 == 32 || num7 == 64 || num7 == PAGE_EXECUTE_WRITECOPY;
|
||||
if (flag && flag2 && num6 > num5 + (ulong)TlsPattern.Length)
|
||||
if (flag && flag2 && num6 > num5 + MinTlsPatchInstructionBytes)
|
||||
{
|
||||
byte* ptr = (byte*)num5;
|
||||
int num8 = (int)(num6 - num5) - TlsPattern.Length;
|
||||
for (int i = 0; i <= num8; i++)
|
||||
int scanBytes = (int)(num6 - num5);
|
||||
for (int i = 0; i <= scanBytes - MinTlsPatchInstructionBytes; i++)
|
||||
{
|
||||
nint address = (nint)(ptr + i);
|
||||
if (IsPatternMatch(ptr + i, TlsPattern))
|
||||
int remainingBytes = scanBytes - i;
|
||||
if (TryPatchTlsLoadInstruction(address, ptr + i, remainingBytes))
|
||||
{
|
||||
num3++;
|
||||
PatchTlsInstruction(address);
|
||||
}
|
||||
else if (remainingBytes >= 12 && TryPatchTlsImmediateStoreInstruction(address, ptr + i))
|
||||
{
|
||||
num9++;
|
||||
}
|
||||
else if (TryPatchStackCanaryInstruction(address, ptr + i))
|
||||
{
|
||||
@@ -972,7 +1077,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
}
|
||||
num = num6 > num ? num6 : num + 4096uL;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS patterns, {num4} stack-canary accesses");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS loads, {num9} TLS stores, {num4} stack-canary accesses");
|
||||
}
|
||||
|
||||
private unsafe bool IsPatternMatch(byte* ptr, byte[] pattern)
|
||||
@@ -1053,12 +1158,71 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void PatchTlsInstruction(nint address)
|
||||
private unsafe bool TryPatchTlsLoadInstruction(nint address, byte* source, int availableLength)
|
||||
{
|
||||
if (availableLength < MinTlsPatchInstructionBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
while (offset < availableLength && source[offset] == 0x66)
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (offset >= availableLength || source[offset] != 0x64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
offset++;
|
||||
if (offset >= availableLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var rex = (byte)0;
|
||||
if (source[offset] >= 0x40 && source[offset] <= 0x4F)
|
||||
{
|
||||
rex = source[offset];
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (offset + 7 > availableLength || source[offset] != 0x8B)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var modRm = source[offset + 1];
|
||||
var sib = source[offset + 2];
|
||||
if ((modRm >> 6) != 0 || (modRm & 7) != 4 || sib != 0x25)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var displacement = *(int*)(source + offset + 3);
|
||||
if (displacement != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var destinationRegister = ((modRm >> 3) & 7) | (((rex & 4) != 0) ? 8 : 0);
|
||||
var instructionLength = offset + 7;
|
||||
if (instructionLength < MinTlsPatchInstructionBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return PatchTlsLoadInstruction(address, instructionLength, destinationRegister);
|
||||
}
|
||||
|
||||
private unsafe bool PatchTlsLoadInstruction(nint address, int instructionLength, int destinationRegister)
|
||||
{
|
||||
uint flNewProtect = default(uint);
|
||||
if (!VirtualProtect((void*)address, 9u, 64u, &flNewProtect))
|
||||
if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -1069,21 +1233,139 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
if (num3 < int.MinValue || num3 > int.MaxValue)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
*(int*)(address + 1) = (int)num3;
|
||||
var offset = 5;
|
||||
if (destinationRegister != 0)
|
||||
{
|
||||
*(int*)(address + 1) = (int)num3;
|
||||
*(sbyte*)(address + 5) = 72;
|
||||
*(sbyte*)(address + 6) = -119;
|
||||
*(sbyte*)(address + 7) = -64;
|
||||
*(sbyte*)(address + 8) = -112;
|
||||
*(byte*)(address + offset++) = (byte)(0x48 | (destinationRegister >= 8 ? 1 : 0));
|
||||
*(byte*)(address + offset++) = 0x89;
|
||||
*(byte*)(address + offset++) = (byte)(0xC0 | (destinationRegister & 7));
|
||||
}
|
||||
|
||||
while (offset < instructionLength)
|
||||
{
|
||||
*(byte*)(address + offset++) = 0x90;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryPatchTlsImmediateStoreInstruction(nint address, byte* source)
|
||||
{
|
||||
if (source[0] != 100 || source[1] != 199 || source[2] != 4 || source[3] != 37)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int tlsOffset = *(int*)(source + 4);
|
||||
int immediateValue = *(int*)(source + 8);
|
||||
nint num = CreateTlsImmediateStoreHelper(tlsOffset, immediateValue);
|
||||
if (num == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return PatchCallSite(address, 12, num);
|
||||
}
|
||||
|
||||
private unsafe nint CreateTlsImmediateStoreHelper(int tlsOffset, int immediateValue)
|
||||
{
|
||||
nint num = AllocateTlsPatchStub(32);
|
||||
if (num == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
byte* ptr = (byte*)num;
|
||||
int num2 = 0;
|
||||
ptr[num2++] = 80;
|
||||
ptr[num2++] = 232;
|
||||
long num3 = _tlsHandlerAddress - (num + num2 + 4);
|
||||
if (num3 < int.MinValue || num3 > int.MaxValue)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] TLS store helper out of rel32 range at 0x{num:X16}");
|
||||
return 0;
|
||||
}
|
||||
*(int*)(ptr + num2) = (int)num3;
|
||||
num2 += 4;
|
||||
ptr[num2++] = 199;
|
||||
ptr[num2++] = 128;
|
||||
*(int*)(ptr + num2) = tlsOffset;
|
||||
num2 += 4;
|
||||
*(int*)(ptr + num2) = immediateValue;
|
||||
num2 += 4;
|
||||
ptr[num2++] = 88;
|
||||
ptr[num2++] = 195;
|
||||
while (num2 < 32)
|
||||
{
|
||||
ptr[num2++] = 144;
|
||||
}
|
||||
uint flNewProtect = default(uint);
|
||||
VirtualProtect((void*)num, 32u, 32u, &flNewProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)num, 32u);
|
||||
return num;
|
||||
}
|
||||
|
||||
private unsafe nint AllocateTlsPatchStub(int size)
|
||||
{
|
||||
if (_tlsHandlerAddress == 0 || size <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num = (size + 15) & -16;
|
||||
if (_tlsPatchStubOffset + num > TlsHandlerRegionSize)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][WARNING] TLS patch stub region exhausted.");
|
||||
return 0;
|
||||
}
|
||||
nint result = _tlsHandlerAddress + _tlsPatchStubOffset;
|
||||
_tlsPatchStubOffset += num;
|
||||
uint flNewProtect = default(uint);
|
||||
if (!VirtualProtect((void*)result, (nuint)num, 64u, &flNewProtect))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private unsafe bool PatchCallSite(nint address, int instructionLength, nint target)
|
||||
{
|
||||
if (instructionLength < 5)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint flNewProtect = default(uint);
|
||||
if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
long num = target - (address + 5);
|
||||
if (num < int.MinValue || num > int.MaxValue)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
|
||||
return false;
|
||||
}
|
||||
*(byte*)address = 232;
|
||||
*(int*)(address + 1) = (int)num;
|
||||
for (int i = 5; i < instructionLength; i++)
|
||||
{
|
||||
*(byte*)(address + i) = 144;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
VirtualProtect((void*)address, 9u, flNewProtect, &flNewProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)address, 9u);
|
||||
VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size)
|
||||
@@ -1141,6 +1423,439 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error)
|
||||
{
|
||||
error = null;
|
||||
if (request.ThreadHandle == 0 || request.EntryPoint < 65536)
|
||||
{
|
||||
error = $"invalid thread start request: handle=0x{request.ThreadHandle:X16} entry=0x{request.EntryPoint:X16}";
|
||||
return false;
|
||||
}
|
||||
if (!TryCreateGuestThreadState(creatorContext, request, out var thread, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
_guestThreads[request.ThreadHandle] = thread;
|
||||
_readyGuestThreads.Enqueue(thread);
|
||||
}
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Scheduled guest thread '{thread.Name}' handle=0x{thread.ThreadHandle:X16} entry=0x{thread.EntryPoint:X16} arg=0x{thread.Argument:X16}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Pump(CpuContext callerContext, string reason)
|
||||
{
|
||||
_ = callerContext;
|
||||
if (_guestThreadPumpDepth != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_guestThreadPumpDepth++;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
GuestThreadState? thread = null;
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
while (_readyGuestThreads.Count > 0)
|
||||
{
|
||||
var candidate = _readyGuestThreads.Dequeue();
|
||||
if (candidate.State == GuestThreadRunState.Ready)
|
||||
{
|
||||
thread = candidate;
|
||||
thread.State = GuestThreadRunState.Running;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (thread == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RunGuestThread(thread, reason);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_guestThreadPumpDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearGuestThreads()
|
||||
{
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
_readyGuestThreads.Clear();
|
||||
_guestThreads.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCreateGuestThreadState(CpuContext creatorContext, GuestThreadStartRequest request, out GuestThreadState thread, out string? error)
|
||||
{
|
||||
thread = null!;
|
||||
if (!TryGetVirtualMemory(creatorContext, out var virtualMemory))
|
||||
{
|
||||
error = "creator context memory is not backed by IVirtualMemory";
|
||||
return false;
|
||||
}
|
||||
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadStackBaseAddress, GuestThreadStackSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out var stackBase, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadTlsBaseAddress, GuestThreadTlsSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out var tlsBase, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var trackedMemory = new TrackedCpuMemory(virtualMemory);
|
||||
var context = new CpuContext(trackedMemory, creatorContext.TargetGeneration)
|
||||
{
|
||||
Rip = request.EntryPoint,
|
||||
Rflags = 0x202,
|
||||
FsBase = tlsBase,
|
||||
GsBase = tlsBase,
|
||||
};
|
||||
context[CpuRegister.Rsp] = stackBase + GuestThreadStackSize - sizeof(ulong);
|
||||
context[CpuRegister.Rdi] = request.Argument;
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
context[CpuRegister.R8] = 0;
|
||||
context[CpuRegister.R9] = 0;
|
||||
if (!InitializeGuestThreadFrame(context) || !InitializeGuestThreadTls(context, tlsBase))
|
||||
{
|
||||
error = "failed to initialize guest thread stack/TLS";
|
||||
return false;
|
||||
}
|
||||
|
||||
thread = new GuestThreadState
|
||||
{
|
||||
ThreadHandle = request.ThreadHandle,
|
||||
EntryPoint = request.EntryPoint,
|
||||
Argument = request.Argument,
|
||||
Name = string.IsNullOrWhiteSpace(request.Name) ? $"Thread-{request.ThreadHandle:X}" : request.Name,
|
||||
Context = context,
|
||||
State = GuestThreadRunState.Ready,
|
||||
};
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetVirtualMemory(CpuContext context, out IVirtualMemory virtualMemory)
|
||||
{
|
||||
if (context.Memory is IVirtualMemory directMemory)
|
||||
{
|
||||
virtualMemory = directMemory;
|
||||
return true;
|
||||
}
|
||||
if (context.Memory is TrackedCpuMemory trackedMemory && trackedMemory.Inner is IVirtualMemory trackedInner)
|
||||
{
|
||||
virtualMemory = trackedInner;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtualMemory = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryMapGuestThreadRegion(
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong baseAddress,
|
||||
ulong size,
|
||||
ProgramHeaderFlags protection,
|
||||
out ulong mappedBase,
|
||||
out string? error)
|
||||
{
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
var candidateBase = baseAddress - ((ulong)i * GuestThreadRegionStride);
|
||||
if (!IsGuestThreadRegionFree(virtualMemory, candidateBase, size))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
virtualMemory.Map(
|
||||
candidateBase,
|
||||
size,
|
||||
fileOffset: 0,
|
||||
fileData: ReadOnlySpan<byte>.Empty,
|
||||
protection: protection);
|
||||
mappedBase = candidateBase;
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
mappedBase = 0;
|
||||
error = $"failed to map guest thread region near 0x{baseAddress:X16}";
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsGuestThreadRegionFree(IVirtualMemory virtualMemory, ulong candidateBase, ulong size)
|
||||
{
|
||||
var candidateEnd = candidateBase + size;
|
||||
foreach (var region in virtualMemory.SnapshotRegions())
|
||||
{
|
||||
var regionStart = region.VirtualAddress;
|
||||
var regionEnd = regionStart + region.MemorySize;
|
||||
if (candidateBase < regionEnd && regionStart < candidateEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InitializeGuestThreadFrame(CpuContext context)
|
||||
{
|
||||
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
|
||||
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
|
||||
var seedRsp = sentinelFrame - sizeof(ulong);
|
||||
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
|
||||
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
|
||||
!context.TryWriteUInt64(seedRsp, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context[CpuRegister.Rbp] = sentinelFrame;
|
||||
context[CpuRegister.Rsp] = seedRsp;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InitializeGuestThreadTls(CpuContext context, ulong tlsBase)
|
||||
{
|
||||
return context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x10, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||
}
|
||||
|
||||
private void RunGuestThread(GuestThreadState thread, string reason)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Pumping guest thread '{thread.Name}' reason={reason} entry=0x{thread.EntryPoint:X16}");
|
||||
var previousContext = _cpuContext;
|
||||
var previousTlsBase = _tlsBaseAddress;
|
||||
var previousOwnsTlsBase = _ownsTlsBaseAddress;
|
||||
var previousForcedGuestExit = _forcedGuestExit;
|
||||
var previousLastError = LastError;
|
||||
var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle);
|
||||
try
|
||||
{
|
||||
_cpuContext = thread.Context;
|
||||
_forcedGuestExit = false;
|
||||
LastError = null;
|
||||
BindTlsBase(thread.Context);
|
||||
var exitReason = ExecuteGuestThreadEntry(thread.Context, thread.EntryPoint, thread.Name, out var blockReason);
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
switch (exitReason)
|
||||
{
|
||||
case GuestNativeCallExitReason.Returned:
|
||||
thread.State = GuestThreadRunState.Exited;
|
||||
break;
|
||||
case GuestNativeCallExitReason.Blocked:
|
||||
thread.State = GuestThreadRunState.Blocked;
|
||||
thread.BlockReason = blockReason;
|
||||
break;
|
||||
default:
|
||||
thread.State = GuestThreadRunState.Faulted;
|
||||
thread.BlockReason = blockReason;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Guest thread '{thread.Name}' state={thread.State} reason={blockReason ?? "none"}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
GuestThreadExecution.RestoreGuestThread(previousGuestThreadHandle);
|
||||
_cpuContext = previousContext;
|
||||
_tlsBaseAddress = previousTlsBase;
|
||||
_ownsTlsBaseAddress = previousOwnsTlsBase;
|
||||
if (_tlsBaseAddress != 0)
|
||||
{
|
||||
SeedTlsLayout(_tlsBaseAddress);
|
||||
UpdateTlsHandlerBase(_tlsBaseAddress);
|
||||
}
|
||||
_forcedGuestExit = previousForcedGuestExit;
|
||||
LastError = previousLastError;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe GuestNativeCallExitReason ExecuteGuestThreadEntry(CpuContext context, ulong entryPoint, string name, out string? reason)
|
||||
{
|
||||
reason = null;
|
||||
if (context[CpuRegister.Rsp] == 0)
|
||||
{
|
||||
reason = "guest thread stack pointer is zero";
|
||||
return GuestNativeCallExitReason.Exception;
|
||||
}
|
||||
void* ptr = VirtualAlloc(null, 256u, 12288u, 64u);
|
||||
if (ptr == null)
|
||||
{
|
||||
reason = "failed to allocate executable memory for guest thread stub";
|
||||
return GuestNativeCallExitReason.Exception;
|
||||
}
|
||||
var previousSentinel = _entryReturnSentinelRip;
|
||||
var previousHostRspSlotValue = _hostRspSlotStorage != 0 ? *(ulong*)_hostRspSlotStorage : 0;
|
||||
var previousYieldRequested = _guestThreadYieldRequested;
|
||||
var previousYieldReason = _guestThreadYieldReason;
|
||||
try
|
||||
{
|
||||
byte* ptr2 = (byte*)ptr;
|
||||
ulong hostRspSlot = (ulong)ptr + 224uL;
|
||||
int offset = 0;
|
||||
ptr2[offset++] = 83;
|
||||
ptr2[offset++] = 85;
|
||||
ptr2[offset++] = 87;
|
||||
ptr2[offset++] = 86;
|
||||
ptr2[offset++] = 73;
|
||||
ptr2[offset++] = 186;
|
||||
*(ulong*)(ptr2 + offset) = hostRspSlot;
|
||||
offset += 8;
|
||||
ptr2[offset++] = 73;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 34;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rsp];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 196;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 131;
|
||||
ptr2[offset++] = 236;
|
||||
ptr2[offset++] = 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 189;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rbp];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rdi];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 199;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rsi];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 198;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rdx];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 194;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rcx];
|
||||
offset += 8;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 137;
|
||||
ptr2[offset++] = 193;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 184;
|
||||
*(ulong*)(ptr2 + offset) = entryPoint;
|
||||
offset += 8;
|
||||
ptr2[offset++] = byte.MaxValue;
|
||||
ptr2[offset++] = 208;
|
||||
int sentinelOffset = offset + 4;
|
||||
ptr2[offset++] = 72;
|
||||
ptr2[offset++] = 131;
|
||||
ptr2[offset++] = 196;
|
||||
ptr2[offset++] = 8;
|
||||
ptr2[offset++] = 73;
|
||||
ptr2[offset++] = 186;
|
||||
*(ulong*)(ptr2 + offset) = hostRspSlot;
|
||||
offset += 8;
|
||||
ptr2[offset++] = 73;
|
||||
ptr2[offset++] = 139;
|
||||
ptr2[offset++] = 34;
|
||||
ptr2[offset++] = 94;
|
||||
ptr2[offset++] = 95;
|
||||
ptr2[offset++] = 93;
|
||||
ptr2[offset++] = 91;
|
||||
ptr2[offset++] = 195;
|
||||
ulong sentinel = (ulong)ptr + (ulong)sentinelOffset;
|
||||
_entryReturnSentinelRip = sentinel;
|
||||
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], sentinel))
|
||||
{
|
||||
reason = $"failed to patch guest thread return sentinel at 0x{context[CpuRegister.Rsp]:X16}";
|
||||
return GuestNativeCallExitReason.Exception;
|
||||
}
|
||||
uint oldProtect = default(uint);
|
||||
VirtualProtect(ptr, 256u, 64u, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), ptr, 256u);
|
||||
if (_hostRspSlotStorage != 0)
|
||||
{
|
||||
*(ulong*)_hostRspSlotStorage = hostRspSlot;
|
||||
}
|
||||
_guestThreadYieldRequested = false;
|
||||
_guestThreadYieldReason = null;
|
||||
try
|
||||
{
|
||||
var nativeReturn = Marshal.GetDelegateForFunctionPointer<NativeEntryDelegate>((nint)ptr)();
|
||||
if (_guestThreadYieldRequested)
|
||||
{
|
||||
reason = _guestThreadYieldReason ?? "guest thread blocked";
|
||||
return GuestNativeCallExitReason.Blocked;
|
||||
}
|
||||
if (_forcedGuestExit)
|
||||
{
|
||||
reason = LastError ?? "guest thread forced exit";
|
||||
return GuestNativeCallExitReason.ForcedExit;
|
||||
}
|
||||
reason = $"returned 0x{nativeReturn:X8}";
|
||||
return GuestNativeCallExitReason.Returned;
|
||||
}
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
reason = "access violation: " + ex.Message;
|
||||
return GuestNativeCallExitReason.Exception;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reason = ex.GetType().Name + ": " + ex.Message;
|
||||
return GuestNativeCallExitReason.Exception;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entryReturnSentinelRip = previousSentinel;
|
||||
if (_hostRspSlotStorage != 0)
|
||||
{
|
||||
*(ulong*)_hostRspSlotStorage = previousHostRspSlotValue;
|
||||
}
|
||||
_guestThreadYieldRequested = previousYieldRequested;
|
||||
_guestThreadYieldReason = previousYieldReason;
|
||||
VirtualFree(ptr, 0u, 32768u);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong AlignDown(ulong value, ulong alignment)
|
||||
{
|
||||
if (alignment == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return value & ~(alignment - 1);
|
||||
}
|
||||
|
||||
private unsafe bool ExecuteEntry(CpuContext context, ulong entryPoint, out OrbisGen2Result result)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] ExecuteEntry starting at 0x{entryPoint:X16}");
|
||||
@@ -1188,6 +1903,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
ptr2[num3++] = 236;
|
||||
ptr2[num3++] = 8;
|
||||
ptr2[num3++] = 72;
|
||||
ptr2[num3++] = 189;
|
||||
*(ulong*)(ptr2 + num3) = context[CpuRegister.Rbp];
|
||||
num3 += 8;
|
||||
ptr2[num3++] = 72;
|
||||
ptr2[num3++] = 184;
|
||||
*(ulong*)(ptr2 + num3) = context[CpuRegister.Rdi];
|
||||
num3 += 8;
|
||||
@@ -1266,6 +1985,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
num6 = Marshal.GetDelegateForFunctionPointer<NativeEntryDelegate>((nint)ptr)();
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Guest returned: {num6}");
|
||||
Pump(context, "entry_return");
|
||||
}
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
@@ -1483,11 +2203,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
_selfHandle.Free();
|
||||
_selfHandlePtr = 0;
|
||||
}
|
||||
if (_ownsTlsBaseAddress && _tlsBaseAddress != 0)
|
||||
if (_ownedTlsBaseAddress != 0)
|
||||
{
|
||||
VirtualFree((void*)_tlsBaseAddress, 0u, 32768u);
|
||||
VirtualFree((void*)_ownedTlsBaseAddress, 0u, 32768u);
|
||||
_ownedTlsBaseAddress = 0;
|
||||
}
|
||||
_tlsBaseAddress = 0;
|
||||
_ownsTlsBaseAddress = false;
|
||||
if (_tlsModuleBases.Count > 0)
|
||||
{
|
||||
foreach (var (_, num3) in _tlsModuleBases)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Loader;
|
||||
|
||||
public readonly record struct ImportedSymbolRelocation(
|
||||
ulong TargetAddress,
|
||||
long Addend,
|
||||
string Nid,
|
||||
bool IsData);
|
||||
@@ -16,6 +16,10 @@ public sealed class SelfImage
|
||||
IReadOnlyList<VirtualMemoryRegion> mappedRegions,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null,
|
||||
IReadOnlyList<ulong>? preInitializerFunctions = null,
|
||||
IReadOnlyList<ulong>? initializerFunctions = null,
|
||||
ulong initFunctionEntryPoint = 0,
|
||||
ulong imageBase = 0,
|
||||
ulong procParamAddress = 0)
|
||||
{
|
||||
@@ -28,6 +32,10 @@ public sealed class SelfImage
|
||||
MappedRegions = mappedRegions;
|
||||
ImportStubs = importStubs ?? new Dictionary<ulong, string>();
|
||||
RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||
ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>();
|
||||
PreInitializerFunctions = preInitializerFunctions ?? Array.Empty<ulong>();
|
||||
InitializerFunctions = initializerFunctions ?? Array.Empty<ulong>();
|
||||
InitFunctionEntryPoint = initFunctionEntryPoint;
|
||||
_imageBase = imageBase;
|
||||
ProcParamAddress = procParamAddress;
|
||||
}
|
||||
@@ -44,6 +52,14 @@ public sealed class SelfImage
|
||||
|
||||
public IReadOnlyDictionary<string, ulong> RuntimeSymbols { get; }
|
||||
|
||||
public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; }
|
||||
|
||||
public IReadOnlyList<ulong> PreInitializerFunctions { get; }
|
||||
|
||||
public IReadOnlyList<ulong> InitializerFunctions { get; }
|
||||
|
||||
public ulong InitFunctionEntryPoint { get; }
|
||||
|
||||
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
|
||||
|
||||
public ulong ProcParamAddress { get; }
|
||||
|
||||
@@ -29,6 +29,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
private const int ElfRelocationSize = 24;
|
||||
private const int ElfSectionHeaderSize = 64;
|
||||
private const uint SectionTypeSymbolTable = 2;
|
||||
private const uint SectionTypeRela = 4;
|
||||
|
||||
private const long DtNull = 0;
|
||||
private const long DtPltRelSize = 0x02;
|
||||
@@ -37,8 +38,13 @@ public sealed class SelfLoader : ISelfLoader
|
||||
private const long DtSymTab = 0x06;
|
||||
private const long DtRela = 0x07;
|
||||
private const long DtRelaSize = 0x08;
|
||||
private const long DtInit = 0x0C;
|
||||
private const long DtStrSize = 0x0A;
|
||||
private const long DtJmpRel = 0x17;
|
||||
private const long DtInitArray = 0x19;
|
||||
private const long DtInitArraySize = 0x1B;
|
||||
private const long DtPreInitArray = 0x20;
|
||||
private const long DtPreInitArraySize = 0x21;
|
||||
private const long DtSceJmpRel = 0x61000029;
|
||||
private const long DtScePltRelSize = 0x6100002D;
|
||||
private const long DtSceRela = 0x6100002F;
|
||||
@@ -60,11 +66,12 @@ public sealed class SelfLoader : ISelfLoader
|
||||
private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL;
|
||||
private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL;
|
||||
private const ulong ModulePlacementStep = 0x00200000UL;
|
||||
private const ulong FocusRelocGuestStart = 0x00000008030FC300UL;
|
||||
private const ulong FocusRelocGuestEnd = 0x00000008030FC3F0UL;
|
||||
private const ulong FocusRelocGuestStart = 0x0000000807BA25B0UL;
|
||||
private const ulong FocusRelocGuestEnd = 0x0000000807BA2608UL;
|
||||
private const byte SymbolBindLocal = 0;
|
||||
private const byte SymbolBindGlobal = 1;
|
||||
private const byte SymbolBindWeak = 2;
|
||||
private const byte SymbolTypeObject = 1;
|
||||
|
||||
private IModuleManager? _moduleManager;
|
||||
private uint _nextTlsModuleId = 1;
|
||||
@@ -72,6 +79,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>();
|
||||
private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols =
|
||||
new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||
private static readonly IReadOnlyList<ulong> EmptyInitializerFunctions = Array.Empty<ulong>();
|
||||
private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>();
|
||||
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
|
||||
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
|
||||
@@ -190,11 +198,13 @@ public sealed class SelfLoader : ISelfLoader
|
||||
var importStubs = ResolveAndPatchImportStubs(
|
||||
imageData,
|
||||
loadContext,
|
||||
elfHeader,
|
||||
programHeaders,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
_moduleManager,
|
||||
tlsModuleId);
|
||||
tlsModuleId,
|
||||
out var importedRelocations);
|
||||
var effectiveImportStubs = importStubs.Count == 0
|
||||
? new Dictionary<ulong, string>()
|
||||
: new Dictionary<ulong, string>(importStubs);
|
||||
@@ -214,6 +224,15 @@ public sealed class SelfLoader : ISelfLoader
|
||||
var finalizedRuntimeSymbols = runtimeSymbols.Count == 0
|
||||
? EmptyRuntimeSymbols
|
||||
: runtimeSymbols;
|
||||
CollectInitializerFunctions(
|
||||
imageData,
|
||||
loadContext,
|
||||
programHeaders,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
out var initFunctionEntryPoint,
|
||||
out var preInitializerFunctions,
|
||||
out var initializerFunctions);
|
||||
var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase);
|
||||
|
||||
Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}");
|
||||
@@ -246,6 +265,10 @@ public sealed class SelfLoader : ISelfLoader
|
||||
virtualMemory.SnapshotRegions(),
|
||||
finalizedImportStubs,
|
||||
finalizedRuntimeSymbols,
|
||||
importedRelocations,
|
||||
preInitializerFunctions,
|
||||
initializerFunctions,
|
||||
initFunctionEntryPoint,
|
||||
imageBase,
|
||||
procParamAddress);
|
||||
}
|
||||
@@ -426,12 +449,15 @@ public sealed class SelfLoader : ISelfLoader
|
||||
private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
ElfHeader elfHeader,
|
||||
IReadOnlyList<ProgramHeader> programHeaders,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
IModuleManager? moduleManager,
|
||||
uint tlsModuleId)
|
||||
uint tlsModuleId,
|
||||
out IReadOnlyList<ImportedSymbolRelocation> importedRelocations)
|
||||
{
|
||||
importedRelocations = Array.Empty<ImportedSymbolRelocation>();
|
||||
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex))
|
||||
{
|
||||
return EmptyImportStubs;
|
||||
@@ -447,10 +473,18 @@ public sealed class SelfLoader : ISelfLoader
|
||||
throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported.");
|
||||
}
|
||||
|
||||
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
|
||||
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize);
|
||||
if (!TryLoadDynamicTableBytes(
|
||||
imageData,
|
||||
loadContext,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicHeader,
|
||||
dynamicHeaderIndex,
|
||||
out var dynamicTable))
|
||||
{
|
||||
return EmptyImportStubs;
|
||||
}
|
||||
|
||||
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize);
|
||||
var elfData = imageData;
|
||||
|
||||
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
||||
@@ -463,13 +497,6 @@ public sealed class SelfLoader : ISelfLoader
|
||||
Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}");
|
||||
Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}");
|
||||
|
||||
if (!dynamicInfo.HasImportMetadata)
|
||||
{
|
||||
Console.WriteLine($"[LOADER] No import metadata found in ELF!");
|
||||
return EmptyImportStubs;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
|
||||
var relocations = new List<ElfRelocation>(512);
|
||||
|
||||
if (dynamicInfo.RelaSize != 0 &&
|
||||
@@ -484,13 +511,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
CollectRelocations(jmpRelBytes, relocations);
|
||||
}
|
||||
|
||||
if (relocations.Count == 0)
|
||||
if (!dynamicInfo.HasImportMetadata)
|
||||
{
|
||||
Console.WriteLine($"[LOADER] No relocations found!");
|
||||
return EmptyImportStubs;
|
||||
Console.WriteLine($"[LOADER] No import metadata found in ELF!");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations...");
|
||||
if (relocations.Count != 0)
|
||||
{
|
||||
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
|
||||
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations...");
|
||||
}
|
||||
|
||||
uint maxSymbolIndex = 0;
|
||||
foreach (var relocation in relocations)
|
||||
@@ -544,164 +574,37 @@ public sealed class SelfLoader : ISelfLoader
|
||||
var descriptors = new List<RelocationDescriptor>(256);
|
||||
var orderedImportNids = new List<string>(128);
|
||||
var seenImportNids = new HashSet<string>(StringComparer.Ordinal);
|
||||
var skippedUnsupported = 0;
|
||||
var skippedUnmapped = 0;
|
||||
var skippedSymbolRead = 0;
|
||||
var skippedNonImportBind = 0;
|
||||
var skippedNameRead = 0;
|
||||
var skippedEmptyNid = 0;
|
||||
foreach (var relocation in relocations)
|
||||
AppendRelocationDescriptors(
|
||||
relocations,
|
||||
symbolTable,
|
||||
stringTable,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
tlsModuleId,
|
||||
descriptors,
|
||||
orderedImportNids,
|
||||
seenImportNids);
|
||||
|
||||
if (descriptors.Count == 0)
|
||||
{
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
var sectionFallbackRelocCount = AppendSectionRelocationDescriptors(
|
||||
imageData,
|
||||
loadContext,
|
||||
elfHeader,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
tlsModuleId,
|
||||
descriptors,
|
||||
orderedImportNids,
|
||||
seenImportNids);
|
||||
if (sectionFallbackRelocCount != 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
|
||||
Console.WriteLine(
|
||||
$"[LOADER] Section relocation fallback recovered {sectionFallbackRelocCount} relocation entries, {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
|
||||
}
|
||||
|
||||
if (!IsSupportedRelocationType(relocation.Type))
|
||||
{
|
||||
skippedUnsupported++;
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
|
||||
{
|
||||
skippedUnmapped++;
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relocation.Type == RelocationTypeRelative)
|
||||
{
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
relocation.Addend,
|
||||
null,
|
||||
imageBase,
|
||||
RelocationValueKind.Pointer));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relocation.Type == RelocationTypeTlsModuleId)
|
||||
{
|
||||
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
0,
|
||||
null,
|
||||
dtpmodValue,
|
||||
RelocationValueKind.TlsModuleId));
|
||||
continue;
|
||||
}
|
||||
|
||||
var symbolIndex = relocation.SymbolIndex;
|
||||
ElfSymbol symbol;
|
||||
if (symbolIndex == 0)
|
||||
{
|
||||
symbol = default;
|
||||
}
|
||||
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
|
||||
{
|
||||
skippedSymbolRead++;
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
|
||||
var symbolBind = GetSymbolBind(symbol.Info);
|
||||
if (symbolBind == SymbolBindLocal)
|
||||
{
|
||||
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
||||
if (symbolAddress == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
|
||||
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
null,
|
||||
symbolAddress,
|
||||
RelocationValueKind.Pointer));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbol.Value != 0)
|
||||
{
|
||||
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
||||
if (symbolAddress == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
|
||||
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
null,
|
||||
symbolAddress,
|
||||
RelocationValueKind.Pointer));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
|
||||
{
|
||||
skippedNonImportBind++;
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
|
||||
{
|
||||
skippedNameRead++;
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var nid = ExtractNid(symbolName);
|
||||
if (string.IsNullOrWhiteSpace(nid))
|
||||
{
|
||||
skippedEmptyNid++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seenImportNids.Add(nid))
|
||||
{
|
||||
orderedImportNids.Add(nid);
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
nid,
|
||||
0,
|
||||
RelocationValueKind.Pointer));
|
||||
}
|
||||
|
||||
Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
|
||||
Console.WriteLine(
|
||||
$"[LOADER] Reloc skip stats: unsupported={skippedUnsupported}, unmapped={skippedUnmapped}, symbolRead={skippedSymbolRead}, nonImportBind={skippedNonImportBind}, nameRead={skippedNameRead}, emptyNid={skippedEmptyNid}");
|
||||
|
||||
if (descriptors.Count == 0)
|
||||
{
|
||||
@@ -709,6 +612,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
return EmptyImportStubs;
|
||||
}
|
||||
|
||||
importedRelocations = BuildImportedRelocations(descriptors);
|
||||
|
||||
var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids);
|
||||
Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs");
|
||||
|
||||
@@ -784,6 +689,237 @@ public sealed class SelfLoader : ISelfLoader
|
||||
return stubsByAddress;
|
||||
}
|
||||
|
||||
private static int AppendSectionRelocationDescriptors(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
ElfHeader elfHeader,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
uint tlsModuleId,
|
||||
ICollection<RelocationDescriptor> descriptors,
|
||||
IList<string> orderedImportNids,
|
||||
ISet<string> seenImportNids)
|
||||
{
|
||||
if (elfHeader.SectionHeaderOffset == 0 ||
|
||||
elfHeader.SectionHeaderCount == 0 ||
|
||||
elfHeader.SectionHeaderEntrySize < ElfSectionHeaderSize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var appendedRelocations = 0;
|
||||
for (var sectionIndex = 0; sectionIndex < elfHeader.SectionHeaderCount; sectionIndex++)
|
||||
{
|
||||
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, sectionIndex, out var relocationHeader) ||
|
||||
relocationHeader.Type != SectionTypeRela ||
|
||||
relocationHeader.Size == 0 ||
|
||||
relocationHeader.EntrySize < ElfRelocationSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryReadElfRelativeSlice(imageData, loadContext, relocationHeader.Offset, relocationHeader.Size, out var relocationTable))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relocations = new List<ElfRelocation>(checked((int)(relocationHeader.Size / relocationHeader.EntrySize)));
|
||||
CollectRelocations(relocationTable, relocations);
|
||||
if (relocations.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> symbolTable = ReadOnlySpan<byte>.Empty;
|
||||
ReadOnlySpan<byte> stringTable = ReadOnlySpan<byte>.Empty;
|
||||
if (relocationHeader.Link < elfHeader.SectionHeaderCount &&
|
||||
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)relocationHeader.Link, out var symbolHeader) &&
|
||||
symbolHeader.Size != 0 &&
|
||||
symbolHeader.EntrySize >= ElfSymbolSize &&
|
||||
TryReadElfRelativeSlice(imageData, loadContext, symbolHeader.Offset, symbolHeader.Size, out symbolTable) &&
|
||||
symbolHeader.Link < elfHeader.SectionHeaderCount &&
|
||||
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)symbolHeader.Link, out var stringHeader) &&
|
||||
stringHeader.Size != 0 &&
|
||||
TryReadElfRelativeSlice(imageData, loadContext, stringHeader.Offset, stringHeader.Size, out stringTable))
|
||||
{
|
||||
}
|
||||
|
||||
AppendRelocationDescriptors(
|
||||
relocations,
|
||||
symbolTable,
|
||||
stringTable,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
tlsModuleId,
|
||||
descriptors,
|
||||
orderedImportNids,
|
||||
seenImportNids);
|
||||
appendedRelocations += relocations.Count;
|
||||
}
|
||||
|
||||
return appendedRelocations;
|
||||
}
|
||||
|
||||
private static void AppendRelocationDescriptors(
|
||||
IReadOnlyList<ElfRelocation> relocations,
|
||||
ReadOnlySpan<byte> symbolTable,
|
||||
ReadOnlySpan<byte> stringTable,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
uint tlsModuleId,
|
||||
ICollection<RelocationDescriptor> descriptors,
|
||||
IList<string> orderedImportNids,
|
||||
ISet<string> seenImportNids)
|
||||
{
|
||||
foreach (var relocation in relocations)
|
||||
{
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
|
||||
}
|
||||
|
||||
if (!IsSupportedRelocationType(relocation.Type))
|
||||
{
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
|
||||
{
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relocation.Type == RelocationTypeRelative)
|
||||
{
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
relocation.Addend,
|
||||
null,
|
||||
imageBase,
|
||||
RelocationValueKind.Pointer,
|
||||
IsDataImport: false));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relocation.Type == RelocationTypeTlsModuleId)
|
||||
{
|
||||
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
0,
|
||||
null,
|
||||
dtpmodValue,
|
||||
RelocationValueKind.TlsModuleId,
|
||||
IsDataImport: false));
|
||||
continue;
|
||||
}
|
||||
|
||||
var symbolIndex = relocation.SymbolIndex;
|
||||
ElfSymbol symbol;
|
||||
if (symbolIndex == 0)
|
||||
{
|
||||
symbol = default;
|
||||
}
|
||||
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
|
||||
var symbolBind = GetSymbolBind(symbol.Info);
|
||||
if (symbolBind == SymbolBindLocal)
|
||||
{
|
||||
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
||||
if (symbolAddress == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
|
||||
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
null,
|
||||
symbolAddress,
|
||||
RelocationValueKind.Pointer,
|
||||
IsDataImport: false));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbol.Value != 0)
|
||||
{
|
||||
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
||||
if (symbolAddress == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
|
||||
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
null,
|
||||
symbolAddress,
|
||||
RelocationValueKind.Pointer,
|
||||
IsDataImport: false));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var nid = ExtractNid(symbolName);
|
||||
if (string.IsNullOrWhiteSpace(nid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seenImportNids.Add(nid))
|
||||
{
|
||||
orderedImportNids.Add(nid);
|
||||
}
|
||||
|
||||
descriptors.Add(new RelocationDescriptor(
|
||||
targetAddress,
|
||||
addend,
|
||||
nid,
|
||||
0,
|
||||
RelocationValueKind.Pointer,
|
||||
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject));
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterRuntimeSymbolsAndHooks(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
@@ -811,6 +947,159 @@ public sealed class SelfLoader : ISelfLoader
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectInitializerFunctions(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
IReadOnlyList<ProgramHeader> programHeaders,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
out ulong initFunctionEntryPoint,
|
||||
out IReadOnlyList<ulong> preInitializerFunctions,
|
||||
out IReadOnlyList<ulong> initializerFunctions)
|
||||
{
|
||||
initFunctionEntryPoint = 0;
|
||||
preInitializerFunctions = EmptyInitializerFunctions;
|
||||
initializerFunctions = EmptyInitializerFunctions;
|
||||
|
||||
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex) ||
|
||||
dynamicHeader.FileSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryLoadDynamicTableBytes(
|
||||
imageData,
|
||||
loadContext,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicHeader,
|
||||
dynamicHeaderIndex,
|
||||
out var dynamicTable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
||||
var preInitializers = new List<ulong>(4);
|
||||
var initializers = new List<ulong>(8);
|
||||
initFunctionEntryPoint = ResolveMappedAddressOrFallback(virtualMemory, dynamicInfo.InitOffset, imageBase);
|
||||
if (initFunctionEntryPoint < 0x10000)
|
||||
{
|
||||
initFunctionEntryPoint = 0;
|
||||
}
|
||||
|
||||
AppendInitializerArrayEntries(
|
||||
preInitializers,
|
||||
imageData,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicInfo.PreInitArrayOffset,
|
||||
dynamicInfo.PreInitArraySize);
|
||||
|
||||
AppendResolvedInitializer(initializers, dynamicInfo.InitOffset, virtualMemory, imageBase);
|
||||
AppendInitializerArrayEntries(
|
||||
initializers,
|
||||
imageData,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicInfo.InitArrayOffset,
|
||||
dynamicInfo.InitArraySize);
|
||||
|
||||
if (preInitializers.Count != 0)
|
||||
{
|
||||
preInitializerFunctions = preInitializers;
|
||||
}
|
||||
|
||||
if (initializers.Count != 0)
|
||||
{
|
||||
initializerFunctions = initializers;
|
||||
}
|
||||
|
||||
if (preInitializers.Count != 0 || initializers.Count != 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendInitializerArrayEntries(
|
||||
ICollection<ulong> destination,
|
||||
ReadOnlySpan<byte> imageData,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
ulong arrayOffset,
|
||||
ulong arraySize)
|
||||
{
|
||||
if (arrayOffset == 0 || arraySize < sizeof(ulong))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryLoadTableBytes(imageData, virtualMemory, imageBase, arrayOffset, arraySize, out var arrayBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entryCount = arrayBytes.Length / sizeof(ulong);
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entryOffset = i * sizeof(ulong);
|
||||
var entryAddress = BinaryPrimitives.ReadUInt64LittleEndian(arrayBytes.AsSpan(entryOffset, sizeof(ulong)));
|
||||
AppendResolvedInitializer(destination, entryAddress, virtualMemory, imageBase);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendResolvedInitializer(
|
||||
ICollection<ulong> destination,
|
||||
ulong functionAddress,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase)
|
||||
{
|
||||
var resolvedAddress = ResolveMappedAddressOrFallback(virtualMemory, functionAddress, imageBase);
|
||||
if (resolvedAddress < 0x10000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var existing in destination)
|
||||
{
|
||||
if (existing == resolvedAddress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
destination.Add(resolvedAddress);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
|
||||
IReadOnlyList<RelocationDescriptor> descriptors)
|
||||
{
|
||||
if (descriptors.Count == 0)
|
||||
{
|
||||
return Array.Empty<ImportedSymbolRelocation>();
|
||||
}
|
||||
|
||||
var importedRelocations = new List<ImportedSymbolRelocation>(descriptors.Count);
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
if (descriptor.ImportNid is null || descriptor.ValueKind != RelocationValueKind.Pointer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
importedRelocations.Add(new ImportedSymbolRelocation(
|
||||
descriptor.TargetAddress,
|
||||
descriptor.Addend,
|
||||
descriptor.ImportNid,
|
||||
descriptor.IsDataImport));
|
||||
}
|
||||
|
||||
return importedRelocations.Count == 0
|
||||
? Array.Empty<ImportedSymbolRelocation>()
|
||||
: importedRelocations;
|
||||
}
|
||||
|
||||
private static int RegisterSectionRuntimeSymbols(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
@@ -909,9 +1198,18 @@ public sealed class SelfLoader : ISelfLoader
|
||||
return 0;
|
||||
}
|
||||
|
||||
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
|
||||
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize);
|
||||
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize);
|
||||
if (!TryLoadDynamicTableBytes(
|
||||
imageData,
|
||||
loadContext,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicHeader,
|
||||
dynamicHeaderIndex,
|
||||
out var dynamicTable))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
||||
if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0)
|
||||
{
|
||||
@@ -1044,6 +1342,37 @@ public sealed class SelfLoader : ISelfLoader
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLoadDynamicTableBytes(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
IVirtualMemory virtualMemory,
|
||||
ulong imageBase,
|
||||
ProgramHeader dynamicHeader,
|
||||
int dynamicHeaderIndex,
|
||||
out ReadOnlySpan<byte> dynamicTable)
|
||||
{
|
||||
if (TryLoadTableBytes(
|
||||
imageData,
|
||||
virtualMemory,
|
||||
imageBase,
|
||||
dynamicHeader.VirtualAddress,
|
||||
dynamicHeader.FileSize,
|
||||
out var loadedDynamicTable))
|
||||
{
|
||||
dynamicTable = loadedDynamicTable;
|
||||
return true;
|
||||
}
|
||||
|
||||
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
|
||||
if (!TrySlice(imageData, dynamicOffset, dynamicHeader.FileSize, out dynamicTable))
|
||||
{
|
||||
dynamicTable = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadElfRelativeSlice(
|
||||
ReadOnlySpan<byte> imageData,
|
||||
LoadContext loadContext,
|
||||
@@ -1185,11 +1514,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
ulong strTabSize = 0;
|
||||
ulong symTabOffset = 0;
|
||||
ulong symTabSize = 0;
|
||||
ulong initOffset = 0;
|
||||
ulong relaOffset = 0;
|
||||
ulong relaSize = 0;
|
||||
ulong jmpRelOffset = 0;
|
||||
ulong jmpRelSize = 0;
|
||||
ulong pltGotOffset = 0;
|
||||
ulong initArrayOffset = 0;
|
||||
ulong initArraySize = 0;
|
||||
ulong preInitArrayOffset = 0;
|
||||
ulong preInitArraySize = 0;
|
||||
|
||||
for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize)
|
||||
{
|
||||
@@ -1236,6 +1570,13 @@ public sealed class SelfLoader : ISelfLoader
|
||||
relaSize = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtInit:
|
||||
if (initOffset == 0)
|
||||
{
|
||||
initOffset = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtJmpRel:
|
||||
if (jmpRelOffset == 0)
|
||||
@@ -1243,6 +1584,34 @@ public sealed class SelfLoader : ISelfLoader
|
||||
jmpRelOffset = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtInitArray:
|
||||
if (initArrayOffset == 0)
|
||||
{
|
||||
initArrayOffset = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtInitArraySize:
|
||||
if (initArraySize == 0)
|
||||
{
|
||||
initArraySize = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtPreInitArray:
|
||||
if (preInitArrayOffset == 0)
|
||||
{
|
||||
preInitArrayOffset = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtPreInitArraySize:
|
||||
if (preInitArraySize == 0)
|
||||
{
|
||||
preInitArraySize = value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DtPltRelSize:
|
||||
if (jmpRelSize == 0)
|
||||
@@ -1290,11 +1659,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
strTabSize,
|
||||
symTabOffset,
|
||||
symTabSize,
|
||||
initOffset,
|
||||
relaOffset,
|
||||
relaSize,
|
||||
jmpRelOffset,
|
||||
jmpRelSize,
|
||||
pltGotOffset);
|
||||
pltGotOffset,
|
||||
initArrayOffset,
|
||||
initArraySize,
|
||||
preInitArrayOffset,
|
||||
preInitArraySize);
|
||||
}
|
||||
|
||||
private static bool IsSupportedRelocationType(uint relocationType)
|
||||
@@ -1512,6 +1886,11 @@ public sealed class SelfLoader : ISelfLoader
|
||||
return (byte)(info >> 4);
|
||||
}
|
||||
|
||||
private static byte GetSymbolType(byte info)
|
||||
{
|
||||
return (byte)(info & 0x0F);
|
||||
}
|
||||
|
||||
private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase)
|
||||
{
|
||||
if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd)
|
||||
@@ -1896,11 +2275,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
ulong StrTabSize,
|
||||
ulong SymTabOffset,
|
||||
ulong SymTabSize,
|
||||
ulong InitOffset,
|
||||
ulong RelaOffset,
|
||||
ulong RelaSize,
|
||||
ulong JmpRelOffset,
|
||||
ulong JmpRelSize,
|
||||
ulong PltGotOffset)
|
||||
ulong PltGotOffset,
|
||||
ulong InitArrayOffset,
|
||||
ulong InitArraySize,
|
||||
ulong PreInitArrayOffset,
|
||||
ulong PreInitArraySize)
|
||||
{
|
||||
public bool HasImportMetadata =>
|
||||
StrTabOffset != 0 &&
|
||||
@@ -1947,7 +2331,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
long Addend,
|
||||
string? ImportNid,
|
||||
ulong SymbolValue,
|
||||
RelocationValueKind ValueKind);
|
||||
RelocationValueKind ValueKind,
|
||||
bool IsDataImport);
|
||||
|
||||
private enum SelfSegmentResolveStatus
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly List<MemoryRegion> _regions = new();
|
||||
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
||||
private bool _disposed;
|
||||
private const ulong PageSize = 0x1000;
|
||||
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
|
||||
@@ -24,6 +25,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
private const uint PAGE_EXECUTE_READWRITE = 0x40;
|
||||
private const uint PAGE_EXECUTE = 0x10;
|
||||
private const uint PAGE_EXECUTE_WRITECOPY = 0x80;
|
||||
private const uint PAGE_NOACCESS = 0x01;
|
||||
private const uint PAGE_READWRITE = 0x04;
|
||||
private const uint PAGE_READONLY = 0x02;
|
||||
|
||||
@@ -38,6 +40,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
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);
|
||||
|
||||
@@ -124,7 +129,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
|
||||
Console.Error.WriteLine($"[VMEM] Could not allocate at 0x{desiredAddress:X16}, trying any address...");
|
||||
result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
|
||||
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
if (!executable)
|
||||
@@ -219,6 +224,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
|
||||
}
|
||||
_regions.Clear();
|
||||
_pageProtections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,20 +270,35 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
NativeMemory.Clear((void*)(virtualAddress + (ulong)fileData.Length), (nuint)zeroFillSize);
|
||||
}
|
||||
|
||||
SetProtection(mapStart, mapSize, protection);
|
||||
ApplySegmentProtection(mapStart, mapEnd, protection);
|
||||
|
||||
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySegmentProtection(ulong mapStart, ulong mapEnd, ProgramHeaderFlags flags)
|
||||
{
|
||||
for (var pageAddress = mapStart; pageAddress < mapEnd; pageAddress += PageSize)
|
||||
{
|
||||
_pageProtections.TryGetValue(pageAddress, out var existingFlags);
|
||||
var mergedFlags = existingFlags | flags;
|
||||
_pageProtections[pageAddress] = mergedFlags;
|
||||
SetProtection(pageAddress, PageSize, mergedFlags);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags)
|
||||
{
|
||||
uint protection;
|
||||
|
||||
if ((flags & ProgramHeaderFlags.Execute) != 0)
|
||||
|
||||
if (flags == ProgramHeaderFlags.None)
|
||||
{
|
||||
protection = (flags & ProgramHeaderFlags.Write) != 0
|
||||
? PAGE_EXECUTE_READWRITE
|
||||
protection = PAGE_NOACCESS;
|
||||
}
|
||||
else if ((flags & ProgramHeaderFlags.Execute) != 0)
|
||||
{
|
||||
protection = (flags & ProgramHeaderFlags.Write) != 0
|
||||
? PAGE_EXECUTE_READWRITE
|
||||
: PAGE_EXECUTE_READ;
|
||||
}
|
||||
else if ((flags & ProgramHeaderFlags.Write) != 0)
|
||||
@@ -309,10 +330,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
{
|
||||
var r = _regions[i];
|
||||
snapshot[i] = new VirtualMemoryRegion(
|
||||
r.VirtualAddress,
|
||||
r.Size,
|
||||
0,
|
||||
r.Size,
|
||||
r.VirtualAddress,
|
||||
r.Size,
|
||||
0,
|
||||
r.Size,
|
||||
r.IsExecutable ? ProgramHeaderFlags.Execute | ProgramHeaderFlags.Read : ProgramHeaderFlags.Read);
|
||||
}
|
||||
return snapshot;
|
||||
@@ -328,10 +349,33 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
|
||||
{
|
||||
var srcPtr = (void*)(region.VirtualAddress + offset);
|
||||
fixed (byte* destPtr = destination)
|
||||
if (destination.IsEmpty)
|
||||
{
|
||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
fixed (byte* destPtr = destination)
|
||||
{
|
||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestorePageProtections(touchedPages);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,6 +397,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
|
||||
{
|
||||
return false;
|
||||
@@ -394,7 +443,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
{
|
||||
foreach (var region in _regions)
|
||||
{
|
||||
if (virtualAddress >= region.VirtualAddress &&
|
||||
if (virtualAddress >= region.VirtualAddress &&
|
||||
virtualAddress < region.VirtualAddress + region.Size)
|
||||
{
|
||||
return (void*)virtualAddress;
|
||||
@@ -458,6 +507,83 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
private static uint GetCommitProtection(MemoryRegion region)
|
||||
{
|
||||
return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
}
|
||||
|
||||
private static unsafe bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region)
|
||||
{
|
||||
if (size == 0 || !region.IsReservedOnly)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var startPage = AlignDown(address, PageSize);
|
||||
var endPage = AlignUp(address + size, PageSize);
|
||||
var commitProtection = GetCommitProtection(region);
|
||||
|
||||
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
|
||||
{
|
||||
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.State == MEM_COMMIT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.State != MEM_RESERVE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VirtualAlloc((void*)pageAddress, (nuint)PageSize, MEM_COMMIT, commitProtection) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTemporarilyProtectForRead(
|
||||
ulong address,
|
||||
ulong size,
|
||||
MemoryRegion region,
|
||||
out List<(ulong Address, uint Protection)> touchedPages)
|
||||
{
|
||||
touchedPages = new List<(ulong Address, uint Protection)>();
|
||||
|
||||
var startPage = AlignDown(address, PageSize);
|
||||
var endPage = AlignUp(address + size, PageSize);
|
||||
var temporaryProtection = region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
|
||||
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
|
||||
{
|
||||
if (!VirtualProtect((void*)pageAddress, (nuint)PageSize, temporaryProtection, out var oldProtection))
|
||||
{
|
||||
RestorePageProtections(touchedPages);
|
||||
touchedPages.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
touchedPages.Add((pageAddress, oldProtection));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void RestorePageProtections(List<(ulong Address, uint Protection)> touchedPages)
|
||||
{
|
||||
foreach (var (pageAddress, protection) in touchedPages)
|
||||
{
|
||||
VirtualProtect((void*)pageAddress, (nuint)PageSize, protection, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong AlignDown(ulong value, ulong alignment)
|
||||
{
|
||||
var mask = alignment - 1;
|
||||
@@ -487,4 +613,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
|
||||
public bool IsReservedOnly { get; set; }
|
||||
public uint Protection { get; set; }
|
||||
}
|
||||
|
||||
private struct MemoryBasicInformation64
|
||||
{
|
||||
public ulong BaseAddress;
|
||||
public ulong AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public uint Alignment1;
|
||||
public ulong RegionSize;
|
||||
public uint State;
|
||||
public uint Protect;
|
||||
public uint Type;
|
||||
public uint Alignment2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace SharpEmu.Core.Runtime;
|
||||
|
||||
public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
private readonly record struct LoadedModuleImage(string Path, SelfImage Image);
|
||||
|
||||
private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"libkernel.prx",
|
||||
@@ -123,6 +125,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
public OrbisGen2Result Run(string ebootPath)
|
||||
{
|
||||
var normalizedEbootPath = Path.GetFullPath(ebootPath);
|
||||
using var app0Binding = BindApp0Root(normalizedEbootPath);
|
||||
Console.Error.WriteLine($"[RUNTIME] Loading: {ebootPath}");
|
||||
LastExecutionDiagnostics = null;
|
||||
LastExecutionTrace = null;
|
||||
@@ -130,21 +134,40 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
LastBasicBlockTrace = null;
|
||||
LastMilestoneLog = null;
|
||||
KernelModuleRegistry.Reset();
|
||||
var image = LoadImage(ebootPath);
|
||||
var normalizedEbootPath = Path.GetFullPath(ebootPath);
|
||||
var image = LoadImage(normalizedEbootPath);
|
||||
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
||||
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);
|
||||
LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
|
||||
var processImageName = Path.GetFileName(ebootPath);
|
||||
if (string.IsNullOrWhiteSpace(processImageName))
|
||||
{
|
||||
processImageName = "eboot.bin";
|
||||
}
|
||||
|
||||
HleDataSymbols.ConfigureProcessImageName(processImageName);
|
||||
MergeKnownHleDataSymbols(activeRuntimeSymbols);
|
||||
var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
|
||||
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
|
||||
var initializerResult = RunAllInitializers(
|
||||
image,
|
||||
loadedModuleImages,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols,
|
||||
processImageName);
|
||||
if (initializerResult is { } failedInitializerResult)
|
||||
{
|
||||
Console.Error.WriteLine($"[RUNTIME] Initializer dispatch failed: {failedInitializerResult}");
|
||||
LastExecutionTrace = _cpuDispatcher.LastImportResolutionTrace;
|
||||
LastMilestoneLog = _cpuDispatcher.LastMilestoneLog;
|
||||
LastSessionSummary = BuildSessionSummary(_cpuDispatcher.LastSessionSummary);
|
||||
LastBasicBlockTrace = _cpuDispatcher.LastBasicBlockTrace;
|
||||
return failedInitializerResult;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}");
|
||||
Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}");
|
||||
|
||||
@@ -328,15 +351,189 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return result;
|
||||
}
|
||||
|
||||
private void LoadAdjacentSceModules(
|
||||
private static App0BindingScope? BindApp0Root(string normalizedEbootPath)
|
||||
{
|
||||
const string app0VariableName = "SHARPEMU_APP0_DIR";
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(app0VariableName)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var app0Root = Path.GetDirectoryName(normalizedEbootPath);
|
||||
if (string.IsNullOrWhiteSpace(app0Root))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable(app0VariableName, app0Root);
|
||||
return new App0BindingScope(app0VariableName);
|
||||
}
|
||||
|
||||
private sealed class App0BindingScope(string variableName) : IDisposable
|
||||
{
|
||||
private readonly string _variableName = variableName;
|
||||
private bool _disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable(_variableName, null);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private OrbisGen2Result? RunAllInitializers(
|
||||
SelfImage mainImage,
|
||||
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string> activeImportStubs,
|
||||
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
|
||||
string processImageName)
|
||||
{
|
||||
var moduleStartResult = RunPreloadedModuleInitializers(
|
||||
loadedModuleImages,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols);
|
||||
if (moduleStartResult is not null)
|
||||
{
|
||||
return moduleStartResult;
|
||||
}
|
||||
|
||||
// On current PS5 dumps DT_INIT commonly resolves to imageBase+0x10, which is inside
|
||||
// the mapped ELF header rather than a callable guest routine. Startup must remain
|
||||
// guest-driven until the PS5 init/module ABI is identified precisely.
|
||||
return null;
|
||||
}
|
||||
|
||||
private OrbisGen2Result? RunPreloadedModuleInitializers(
|
||||
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string> activeImportStubs,
|
||||
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols)
|
||||
{
|
||||
for (var i = 0; i < loadedModuleImages.Count; i++)
|
||||
{
|
||||
var loadedModule = loadedModuleImages[i];
|
||||
var initEntryPoint = loadedModule.Image.InitFunctionEntryPoint;
|
||||
if (initEntryPoint < 0x10000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var moduleName = Path.GetFileName(loadedModule.Path);
|
||||
if (string.IsNullOrWhiteSpace(moduleName))
|
||||
{
|
||||
moduleName = $"module#{i}";
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
|
||||
|
||||
var result = _cpuDispatcher.DispatchModuleInitializer(
|
||||
initEntryPoint,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols,
|
||||
moduleName,
|
||||
_cpuExecutionOptions);
|
||||
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Module start failed: {moduleName} -> {result}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OrbisGen2Result? RunImageInitializers(
|
||||
string label,
|
||||
SelfImage image,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string> activeImportStubs,
|
||||
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
|
||||
string processImageName)
|
||||
{
|
||||
if (image.PreInitializerFunctions.Count == 0 && image.InitializerFunctions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
|
||||
|
||||
var result = RunInitializerList(
|
||||
$"{label}:preinit",
|
||||
image.PreInitializerFunctions,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols,
|
||||
processImageName);
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return RunInitializerList(
|
||||
$"{label}:init",
|
||||
image.InitializerFunctions,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols,
|
||||
processImageName);
|
||||
}
|
||||
|
||||
private OrbisGen2Result? RunInitializerList(
|
||||
string label,
|
||||
IReadOnlyList<ulong> initializerFunctions,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string> activeImportStubs,
|
||||
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
|
||||
string processImageName)
|
||||
{
|
||||
for (var i = 0; i < initializerFunctions.Count; i++)
|
||||
{
|
||||
var initializerAddress = initializerFunctions[i];
|
||||
if (initializerAddress < 0x10000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
|
||||
|
||||
var result = _cpuDispatcher.DispatchEntry(
|
||||
initializerAddress,
|
||||
generation,
|
||||
activeImportStubs,
|
||||
activeRuntimeSymbols,
|
||||
processImageName,
|
||||
_cpuExecutionOptions);
|
||||
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<LoadedModuleImage> LoadAdjacentSceModules(
|
||||
string ebootPath,
|
||||
IDictionary<ulong, string> importStubs,
|
||||
IDictionary<string, ulong> runtimeSymbols)
|
||||
{
|
||||
var loadedImages = new List<LoadedModuleImage>();
|
||||
var ebootDirectory = Path.GetDirectoryName(ebootPath);
|
||||
if (string.IsNullOrWhiteSpace(ebootDirectory))
|
||||
{
|
||||
return;
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
var moduleDirectories = new[]
|
||||
@@ -350,7 +547,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
if (moduleDirectories.Length == 0)
|
||||
{
|
||||
return;
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
var allModulePaths = moduleDirectories
|
||||
@@ -380,7 +577,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
if (modulePaths.Length == 0)
|
||||
{
|
||||
return;
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories)}");
|
||||
@@ -416,6 +613,7 @@ 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));
|
||||
loadedModules++;
|
||||
|
||||
Console.Error.WriteLine(
|
||||
@@ -430,6 +628,104 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}");
|
||||
return loadedImages;
|
||||
}
|
||||
|
||||
private void RebindImportedDataSymbols(
|
||||
SelfImage mainImage,
|
||||
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
|
||||
IReadOnlyDictionary<string, ulong> runtimeSymbols)
|
||||
{
|
||||
var rebound = 0;
|
||||
var unresolved = 0;
|
||||
|
||||
rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved);
|
||||
for (var i = 0; i < loadedModuleImages.Count; i++)
|
||||
{
|
||||
rebound += RebindImportedDataSymbols(loadedModuleImages[i].Image, runtimeSymbols, ref unresolved);
|
||||
}
|
||||
|
||||
if (rebound != 0 || unresolved != 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data rebind: rebound={rebound}, unresolved={unresolved}");
|
||||
}
|
||||
}
|
||||
|
||||
private int RebindImportedDataSymbols(
|
||||
SelfImage image,
|
||||
IReadOnlyDictionary<string, ulong> runtimeSymbols,
|
||||
ref int unresolved)
|
||||
{
|
||||
if (image.ImportedRelocations.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var rebound = 0;
|
||||
var logRebind = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_DATA_REBIND"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
for (var i = 0; i < image.ImportedRelocations.Count; i++)
|
||||
{
|
||||
var relocation = image.ImportedRelocations[i];
|
||||
if (!relocation.IsData)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!runtimeSymbols.TryGetValue(relocation.Nid, out var symbolAddress) ||
|
||||
!IsUsableRuntimeSymbolAddress(symbolAddress))
|
||||
{
|
||||
if (logRebind)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data unresolved: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} addend=0x{unchecked((ulong)relocation.Addend):X16}");
|
||||
}
|
||||
|
||||
unresolved++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var reboundValue = AddSigned(symbolAddress, relocation.Addend);
|
||||
if (!TryWriteUInt64(_virtualMemory, relocation.TargetAddress, reboundValue))
|
||||
{
|
||||
if (logRebind)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data write-failed: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
}
|
||||
|
||||
unresolved++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (logRebind)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[RUNTIME] Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
|
||||
}
|
||||
|
||||
rebound++;
|
||||
}
|
||||
|
||||
return rebound;
|
||||
}
|
||||
|
||||
private static void MergeKnownHleDataSymbols(IDictionary<string, ulong> runtimeSymbols)
|
||||
{
|
||||
foreach (var nid in HleDataSymbols.EnumerateKnownNids())
|
||||
{
|
||||
if (runtimeSymbols.ContainsKey(nid) ||
|
||||
!HleDataSymbols.TryGetAddress(nid, out var symbolAddress) ||
|
||||
!IsUsableRuntimeSymbolAddress(symbolAddress))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
runtimeSymbols[nid] = symbolAddress;
|
||||
}
|
||||
}
|
||||
|
||||
private static int MergeImportStubs(
|
||||
@@ -498,6 +794,24 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
return address >= 0x10000 && !IsUnresolvedRuntimeSentinel(address);
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt64(IVirtualMemory virtualMemory, ulong address, ulong value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
|
||||
return virtualMemory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static ulong AddSigned(ulong value, long addend)
|
||||
{
|
||||
if (addend >= 0)
|
||||
{
|
||||
return unchecked(value + (ulong)addend);
|
||||
}
|
||||
|
||||
var magnitude = unchecked((ulong)(-(addend + 1))) + 1;
|
||||
return unchecked(value - magnitude);
|
||||
}
|
||||
|
||||
private static bool IsUnresolvedRuntimeSentinel(ulong value)
|
||||
{
|
||||
return value == 0xFFFEUL ||
|
||||
|
||||
@@ -8,17 +8,105 @@
|
||||
"resolved": "1.21.0",
|
||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.hle": {
|
||||
"type": "Project"
|
||||
},
|
||||
"sharpemu.libs": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.0, )"
|
||||
"SharpEmu.HLE": "[1.0.0, )",
|
||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
||||
"Silk.NET.Windowing": "[2.23.0, )"
|
||||
}
|
||||
},
|
||||
"sharpemu.logging": {
|
||||
"type": "Project"
|
||||
},
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE;
|
||||
|
||||
public readonly record struct GuestThreadStartRequest(
|
||||
ulong ThreadHandle,
|
||||
ulong EntryPoint,
|
||||
ulong Argument,
|
||||
ulong AttributeAddress,
|
||||
string Name);
|
||||
|
||||
public interface IGuestThreadScheduler
|
||||
{
|
||||
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
||||
|
||||
void Pump(CpuContext callerContext, string reason);
|
||||
}
|
||||
|
||||
public static class GuestThreadExecution
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static ulong _currentGuestThreadHandle;
|
||||
|
||||
[ThreadStatic]
|
||||
private static string? _pendingBlockReason;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool _pendingEntryExit;
|
||||
|
||||
[ThreadStatic]
|
||||
private static int _pendingEntryExitStatus;
|
||||
|
||||
[ThreadStatic]
|
||||
private static string? _pendingEntryExitReason;
|
||||
|
||||
public static IGuestThreadScheduler? Scheduler { get; set; }
|
||||
|
||||
public static bool IsGuestThread => _currentGuestThreadHandle != 0;
|
||||
|
||||
public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle;
|
||||
|
||||
public static ulong EnterGuestThread(ulong threadHandle)
|
||||
{
|
||||
var previous = _currentGuestThreadHandle;
|
||||
_currentGuestThreadHandle = threadHandle;
|
||||
_pendingBlockReason = null;
|
||||
_pendingEntryExit = false;
|
||||
_pendingEntryExitStatus = 0;
|
||||
_pendingEntryExitReason = null;
|
||||
return previous;
|
||||
}
|
||||
|
||||
public static void RestoreGuestThread(ulong previousThreadHandle)
|
||||
{
|
||||
_currentGuestThreadHandle = previousThreadHandle;
|
||||
_pendingBlockReason = null;
|
||||
_pendingEntryExit = false;
|
||||
_pendingEntryExitStatus = 0;
|
||||
_pendingEntryExitReason = null;
|
||||
}
|
||||
|
||||
public static bool RequestCurrentThreadBlock(string reason)
|
||||
{
|
||||
if (!IsGuestThread)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingBlockReason = string.IsNullOrWhiteSpace(reason) ? "guest_thread_blocked" : reason;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryConsumeCurrentThreadBlock(out string reason)
|
||||
{
|
||||
reason = _pendingBlockReason ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingBlockReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void RequestCurrentEntryExit(string reason, int status)
|
||||
{
|
||||
_pendingEntryExit = true;
|
||||
_pendingEntryExitStatus = status;
|
||||
_pendingEntryExitReason = string.IsNullOrWhiteSpace(reason) ? "guest_entry_exit" : reason;
|
||||
}
|
||||
|
||||
public static bool TryConsumeCurrentEntryExit(out int status, out string reason)
|
||||
{
|
||||
status = _pendingEntryExitStatus;
|
||||
reason = _pendingEntryExitReason ?? string.Empty;
|
||||
if (!_pendingEntryExit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingEntryExit = false;
|
||||
_pendingEntryExitStatus = 0;
|
||||
_pendingEntryExitReason = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpEmu.HLE;
|
||||
|
||||
public static class HleDataSymbols
|
||||
{
|
||||
private const string StackChkGuardNid = "f7uOxY9mM1U";
|
||||
private const string ProgNameNid = "djxxOmW6-aw";
|
||||
private const string LibcNeedFlagNid = "P330P3dFF68";
|
||||
private const string LibcInternalNeedFlagNid = "ZT4ODD2Ts9o";
|
||||
private const int ProgNameMaxBytes = 511;
|
||||
private const ulong StackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
|
||||
|
||||
private static readonly object _gate = new();
|
||||
private static readonly nint _stackChkGuardAddress = Allocate(sizeof(ulong) * 2);
|
||||
private static readonly nint _progNameBufferAddress = Allocate(ProgNameMaxBytes + 1);
|
||||
private static readonly nint _progNamePointerAddress = Allocate(nint.Size);
|
||||
private static readonly nint _libcNeedFlagAddress = Allocate(sizeof(uint));
|
||||
private static readonly nint _libcInternalNeedFlagAddress = Allocate(sizeof(uint));
|
||||
|
||||
static HleDataSymbols()
|
||||
{
|
||||
if (_stackChkGuardAddress != 0)
|
||||
{
|
||||
Marshal.WriteInt64(_stackChkGuardAddress, unchecked((long)StackChkGuardValue));
|
||||
Marshal.WriteInt64(
|
||||
IntPtr.Add(_stackChkGuardAddress, sizeof(ulong)),
|
||||
unchecked((long)StackChkGuardValue));
|
||||
}
|
||||
|
||||
if (_libcNeedFlagAddress != 0)
|
||||
{
|
||||
Marshal.WriteInt32(_libcNeedFlagAddress, 1);
|
||||
}
|
||||
|
||||
if (_libcInternalNeedFlagAddress != 0)
|
||||
{
|
||||
Marshal.WriteInt32(_libcInternalNeedFlagAddress, 1);
|
||||
}
|
||||
|
||||
ConfigureProcessImageName("eboot.bin");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> EnumerateKnownNids()
|
||||
{
|
||||
yield return StackChkGuardNid;
|
||||
yield return ProgNameNid;
|
||||
yield return LibcNeedFlagNid;
|
||||
yield return LibcInternalNeedFlagNid;
|
||||
}
|
||||
|
||||
public static void ConfigureProcessImageName(string? processImageName)
|
||||
{
|
||||
var effectiveName = string.IsNullOrWhiteSpace(processImageName)
|
||||
? "eboot.bin"
|
||||
: processImageName;
|
||||
var encodedName = Encoding.UTF8.GetBytes(effectiveName);
|
||||
var byteCount = Math.Min(encodedName.Length, ProgNameMaxBytes);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (_progNameBufferAddress == 0 || _progNamePointerAddress == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i <= ProgNameMaxBytes; i++)
|
||||
{
|
||||
Marshal.WriteByte(_progNameBufferAddress, i, 0);
|
||||
}
|
||||
|
||||
Marshal.Copy(encodedName, 0, _progNameBufferAddress, byteCount);
|
||||
WritePointer(_progNamePointerAddress, _progNameBufferAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetAddress(string nid, out ulong address)
|
||||
{
|
||||
var pointer = nid switch
|
||||
{
|
||||
StackChkGuardNid => _stackChkGuardAddress,
|
||||
ProgNameNid => _progNamePointerAddress,
|
||||
LibcNeedFlagNid => _libcNeedFlagAddress,
|
||||
LibcInternalNeedFlagNid => _libcInternalNeedFlagAddress,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if (pointer == 0)
|
||||
{
|
||||
address = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
address = unchecked((ulong)pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static nint Allocate(int size)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = Marshal.AllocHGlobal(size);
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
Marshal.WriteByte(memory, i, 0);
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WritePointer(nint target, nint value)
|
||||
{
|
||||
if (nint.Size == sizeof(int))
|
||||
{
|
||||
Marshal.WriteInt32(target, value.ToInt32());
|
||||
return;
|
||||
}
|
||||
|
||||
Marshal.WriteInt64(target, value.ToInt64());
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,7 @@ public interface IModuleManager
|
||||
|
||||
bool TryGetExportByName(string exportName, out ExportedFunction export);
|
||||
|
||||
bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result);
|
||||
|
||||
OrbisGen2Result Dispatch(string nid, CpuContext context);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,12 @@ public sealed class ModuleManager : IModuleManager
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -100,13 +106,15 @@ public sealed class ModuleManager : IModuleManager
|
||||
if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export))
|
||||
{
|
||||
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((export.Target & context.TargetGeneration) == 0)
|
||||
{
|
||||
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
|
||||
context.ClearRaxWriteFlag();
|
||||
@@ -117,7 +125,8 @@ public sealed class ModuleManager : IModuleManager
|
||||
context[CpuRegister.Rax] = unchecked((ulong)ret);
|
||||
}
|
||||
|
||||
return (OrbisGen2Result)ret;
|
||||
result = (OrbisGen2Result)ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
|
||||
|
||||
@@ -15,6 +15,11 @@ public enum OrbisGen2Result : int
|
||||
/// </summary>
|
||||
ORBIS_GEN2_OK = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the operation is not permitted for the calling thread.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_PERMISSION_DENIED = unchecked((int)0x80020001),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the requested export was not found.
|
||||
/// </summary>
|
||||
@@ -30,11 +35,31 @@ public enum OrbisGen2Result : int
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that completing the operation would deadlock.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_DEADLOCK = unchecked((int)0x8002000B),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the target resource is busy.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the operation should be retried later.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that behavior is recognized but not implemented yet.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the operation timed out.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_TIMED_OUT = unchecked((int)0x8002003C),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that memory access failed.
|
||||
/// </summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,641 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace SharpEmu.Libs.Ampr;
|
||||
|
||||
public static class AmprExports
|
||||
{
|
||||
private const int CommandBufferHeaderSize = 0x28;
|
||||
private const ulong CommandBufferSelfOffset = 0x00;
|
||||
private const ulong CommandBufferDataOffset = 0x08;
|
||||
private const ulong CommandBufferSizeOffset = 0x10;
|
||||
private const ulong CommandBufferAux0Offset = 0x18;
|
||||
private const ulong CommandBufferAux1Offset = 0x20;
|
||||
private const ulong ReadFileRecordSize = 0x30;
|
||||
private const ulong KernelEventQueueRecordSize = 0x30;
|
||||
private const uint ReadFileRecordType = 1;
|
||||
private const uint KernelEventQueueRecordType = 2;
|
||||
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
||||
|
||||
private sealed class CommandBufferState
|
||||
{
|
||||
public ulong Buffer;
|
||||
public ulong Size;
|
||||
public ulong WriteOffset;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "8aI7R7WaOlc",
|
||||
ExportName = "sceAmprCommandBufferConstructor",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferConstructor(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var buffer = ctx[CpuRegister.Rsi];
|
||||
var size = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer, size, aux0: 0, aux1: 0, clear: true))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "ctor", commandBuffer, buffer, size);
|
||||
ctx[CpuRegister.Rax] = commandBuffer;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "a8uLzYY--tM",
|
||||
ExportName = "sceAmprAprCommandBufferConstructor",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int AprCommandBufferConstructor(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var aux0 = ctx[CpuRegister.Rsi];
|
||||
var aux1 = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
var buffer = 0UL;
|
||||
var size = 0UL;
|
||||
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer);
|
||||
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size);
|
||||
|
||||
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer, size, aux0, aux1, clear: false))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "apr_ctor", commandBuffer, aux0, aux1);
|
||||
ctx[CpuRegister.Rax] = commandBuffer;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Qs1xtplKo0U",
|
||||
ExportName = "sceAmprAprCommandBufferDestructor",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int AprCommandBufferDestructor(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, 0) ||
|
||||
!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "apr_dtor", commandBuffer, 0, 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "GuchCTefuZw",
|
||||
ExportName = "sceAmprCommandBufferDestructor",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferDestructor(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer: 0, size: 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_commandBuffers.TryRemove(commandBuffer, out _);
|
||||
TraceAmpr(ctx, "dtor", commandBuffer, 0, 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "N-FSPA4S3nI",
|
||||
ExportName = "sceAmprCommandBufferSetBuffer",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferSetBuffer(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var buffer = ctx[CpuRegister.Rsi];
|
||||
var size = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "set_buffer", commandBuffer, buffer, size);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "baQO9ez2gL4",
|
||||
ExportName = "sceAmprCommandBufferReset",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferReset(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out var buffer) ||
|
||||
!ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out var size) ||
|
||||
!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "reset", commandBuffer, buffer, size);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ULvXMDz56po",
|
||||
ExportName = "sceAmprCommandBufferClearBuffer",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferClearBuffer(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out var buffer, out var size, out _) ||
|
||||
!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer: 0, size: 0))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_commandBuffers.TryRemove(commandBuffer, out _);
|
||||
TraceAmpr(ctx, "clear_buffer", commandBuffer, buffer, size);
|
||||
ctx[CpuRegister.Rax] = buffer;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "mQ16-QdKv7k",
|
||||
ExportName = "sceAmprAprCommandBufferReadFile",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int AprCommandBufferReadFile(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var fileId = unchecked((uint)ctx[CpuRegister.Rcx]);
|
||||
var destination = ctx[CpuRegister.R8];
|
||||
var size = ctx[CpuRegister.R9];
|
||||
|
||||
if (commandBuffer == 0 || (destination == 0 && size != 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out var fileOffset))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath) || !File.Exists(hostPath))
|
||||
{
|
||||
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var result = TryReadFileToGuestMemory(ctx, hostPath, fileOffset, destination, size, out var bytesRead);
|
||||
if (result != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead, hostPath, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!AppendReadFileRecord(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vWU-odnS+fU",
|
||||
ExportName = "sceAmprMeasureCommandSizeReadFile",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int MeasureCommandSizeReadFile(CpuContext ctx)
|
||||
{
|
||||
TraceAmpr(ctx, "measure_read_file", 0, ReadFileRecordSize, 0);
|
||||
ctx[CpuRegister.Rax] = ReadFileRecordSize;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "sSAUCCU1dv4",
|
||||
ExportName = "sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int MeasureCommandSizeWriteKernelEventQueue0400(CpuContext ctx)
|
||||
{
|
||||
TraceAmpr(ctx, "measure_write_equeue", 0, KernelEventQueueRecordSize, 0);
|
||||
ctx[CpuRegister.Rax] = KernelEventQueueRecordSize;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "tZDDEo2tE5k",
|
||||
ExportName = "sceAmprCommandBufferGetSize",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferGetSize(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out var size, out _))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "get_size", commandBuffer, size, 0);
|
||||
ctx[CpuRegister.Rax] = size;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "GnxKOHEawhk",
|
||||
ExportName = "sceAmprCommandBufferGetCurrentOffset",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferGetCurrentOffset(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryGetCommandBufferOffset(ctx, commandBuffer, out var offset))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "get_offset", commandBuffer, offset, 0);
|
||||
ctx[CpuRegister.Rax] = offset;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "H896Pt-yB4I",
|
||||
ExportName = "sceAmprCommandBufferWriteKernelEventQueue_04_00",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferWriteKernelEventQueue0400(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var equeue = ctx[CpuRegister.Rsi];
|
||||
var ident = ctx[CpuRegister.Rdx];
|
||||
var filter = ctx[CpuRegister.Rcx];
|
||||
var userData = ctx[CpuRegister.R8];
|
||||
var data = ctx[CpuRegister.R9];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var extra = 0UL;
|
||||
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out extra);
|
||||
|
||||
if (!AppendKernelEventQueueRecord(ctx, commandBuffer, equeue, ident, filter, userData, data, extra))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "write_equeue", commandBuffer, equeue, ident);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool InitializeCommandBuffer(
|
||||
CpuContext ctx,
|
||||
ulong commandBuffer,
|
||||
ulong buffer,
|
||||
ulong size,
|
||||
ulong aux0,
|
||||
ulong aux1,
|
||||
bool clear)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
Span<byte> header = stackalloc byte[CommandBufferHeaderSize];
|
||||
header.Clear();
|
||||
if (!ctx.Memory.TryWrite(commandBuffer, header))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
|
||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, aux0) &&
|
||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, aux1) &&
|
||||
WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0);
|
||||
}
|
||||
|
||||
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
||||
{
|
||||
return WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0);
|
||||
}
|
||||
|
||||
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size, ulong writeOffset)
|
||||
{
|
||||
if (!WriteVisibleCommandBufferPointers(ctx, commandBuffer, buffer, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
||||
lock (state)
|
||||
{
|
||||
state.Buffer = buffer;
|
||||
state.Size = size;
|
||||
state.WriteOffset = writeOffset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool WriteVisibleCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
||||
{
|
||||
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
|
||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferDataOffset, buffer) &&
|
||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferSizeOffset, size);
|
||||
}
|
||||
|
||||
private static bool TryGetCommandBufferState(
|
||||
CpuContext ctx,
|
||||
ulong commandBuffer,
|
||||
out ulong buffer,
|
||||
out ulong size,
|
||||
out CommandBufferState? state)
|
||||
{
|
||||
if (_commandBuffers.TryGetValue(commandBuffer, out state))
|
||||
{
|
||||
lock (state)
|
||||
{
|
||||
buffer = state.Buffer;
|
||||
size = state.Size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer) &&
|
||||
ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size))
|
||||
{
|
||||
state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
||||
lock (state)
|
||||
{
|
||||
state.Buffer = buffer;
|
||||
state.Size = size;
|
||||
state.WriteOffset = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer = 0;
|
||||
size = 0;
|
||||
state = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetCommandBufferOffset(CpuContext ctx, ulong commandBuffer, out ulong offset)
|
||||
{
|
||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
|
||||
{
|
||||
offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (state)
|
||||
{
|
||||
offset = state.WriteOffset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int TryReadFileToGuestMemory(
|
||||
CpuContext ctx,
|
||||
string hostPath,
|
||||
ulong fileOffset,
|
||||
ulong destination,
|
||||
ulong size,
|
||||
out ulong bytesRead)
|
||||
{
|
||||
bytesRead = 0;
|
||||
if (size == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (fileOffset > long.MaxValue)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
const int ChunkSize = 64 * 1024;
|
||||
var buffer = new byte[(int)Math.Min((ulong)ChunkSize, size)];
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = new FileStream(hostPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
if (fileOffset >= (ulong)stream.Length)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
stream.Position = unchecked((long)fileOffset);
|
||||
|
||||
while (bytesRead < size)
|
||||
{
|
||||
var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead);
|
||||
var read = stream.Read(buffer, 0, request);
|
||||
if (read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.Memory.TryWrite(destination + bytesRead, buffer.AsSpan(0, read)))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
bytesRead += (ulong)read;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool AppendReadFileRecord(
|
||||
CpuContext ctx,
|
||||
ulong commandBuffer,
|
||||
uint fileId,
|
||||
ulong destination,
|
||||
ulong size,
|
||||
ulong fileOffset,
|
||||
ulong bytesRead)
|
||||
{
|
||||
Span<byte> record = stackalloc byte[(int)ReadFileRecordSize];
|
||||
record.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], ReadFileRecordType);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], fileId);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], destination);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], size);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], fileOffset);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], bytesRead);
|
||||
|
||||
return AppendCommandBufferRecord(ctx, commandBuffer, record);
|
||||
}
|
||||
|
||||
private static bool AppendKernelEventQueueRecord(
|
||||
CpuContext ctx,
|
||||
ulong commandBuffer,
|
||||
ulong equeue,
|
||||
ulong ident,
|
||||
ulong filter,
|
||||
ulong userData,
|
||||
ulong data,
|
||||
ulong extra)
|
||||
{
|
||||
Span<byte> record = stackalloc byte[(int)KernelEventQueueRecordSize];
|
||||
record.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], KernelEventQueueRecordType);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], unchecked((uint)filter));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], equeue);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], ident);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], userData);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], data);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x28..], extra);
|
||||
|
||||
return AppendCommandBufferRecord(ctx, commandBuffer, record);
|
||||
}
|
||||
|
||||
private static bool AppendCommandBufferRecord(CpuContext ctx, ulong commandBuffer, ReadOnlySpan<byte> record)
|
||||
{
|
||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var recordSize = (ulong)record.Length;
|
||||
lock (state)
|
||||
{
|
||||
if (state.Buffer == 0 ||
|
||||
state.WriteOffset > state.Size ||
|
||||
recordSize > state.Size - state.WriteOffset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ctx.Memory.TryWrite(state.Buffer + state.WriteOffset, record))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
state.WriteOffset += recordSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TraceAmpr(CpuContext ctx, string operation, ulong commandBuffer, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var returnRip = 0UL;
|
||||
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ampr.{operation}: cmd=0x{commandBuffer:X16} arg0=0x{arg0:X16} arg1=0x{arg1:X16} ret=0x{returnRip:X16}");
|
||||
}
|
||||
|
||||
private static void TraceAmprRead(
|
||||
CpuContext ctx,
|
||||
ulong commandBuffer,
|
||||
uint fileId,
|
||||
ulong destination,
|
||||
ulong size,
|
||||
ulong fileOffset,
|
||||
ulong bytesRead,
|
||||
string? hostPath,
|
||||
int result)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var returnRip = 0UL;
|
||||
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ampr.read_file: cmd=0x{commandBuffer:X16} id=0x{fileId:X8} dst=0x{destination:X16} size=0x{size:X16} offset=0x{fileOffset:X16} read=0x{bytesRead:X16} result=0x{result:X8} path='{hostPath ?? string.Empty}' ret=0x{returnRip:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace SharpEmu.Libs.Ampr;
|
||||
|
||||
internal static class AmprFileRegistry
|
||||
{
|
||||
private static readonly ConcurrentDictionary<uint, string> _hostPathsById = new();
|
||||
|
||||
public static uint Register(string guestPath, string hostPath)
|
||||
{
|
||||
var id = ComputeFileId(guestPath);
|
||||
_hostPathsById[id] = hostPath;
|
||||
return id;
|
||||
}
|
||||
|
||||
public static bool TryGetHostPath(uint id, out string hostPath)
|
||||
{
|
||||
return _hostPathsById.TryGetValue(id, out hostPath!);
|
||||
}
|
||||
|
||||
private static uint ComputeFileId(string guestPath)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(guestPath);
|
||||
|
||||
const uint offsetBasis = 2166136261;
|
||||
const uint prime = 16777619;
|
||||
|
||||
var hash = offsetBasis;
|
||||
foreach (var b in bytes)
|
||||
{
|
||||
hash ^= b;
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,41 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpEmu.Libs.AppContent;
|
||||
|
||||
public static class AppContentExports
|
||||
{
|
||||
private const ulong BootParamAttrOffset = 4;
|
||||
private const string Temp0MountPoint = "/temp0";
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "R9lA82OraNs",
|
||||
ExportName = "sceAppContentInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAppContent")]
|
||||
public static int AppContentInitialize(CpuContext ctx)
|
||||
{
|
||||
var initParamAddress = ctx[CpuRegister.Rdi];
|
||||
var bootParamAddress = ctx[CpuRegister.Rsi];
|
||||
if (initParamAddress == 0 || bootParamAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Span<byte> attrBytes = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attrBytes, 0);
|
||||
if (!ctx.Memory.TryWrite(bootParamAddress + BootParamAttrOffset, attrBytes))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "xnd8BJzAxmk",
|
||||
ExportName = "sceAppContentGetAddcontInfoList",
|
||||
@@ -14,7 +44,67 @@ public static class AppContentExports
|
||||
LibraryName = "libSceAppContent")]
|
||||
public static int AppContentGetAddcontInfoList(CpuContext ctx)
|
||||
{
|
||||
_ = ctx;
|
||||
var hitCountAddress = ctx[CpuRegister.Rcx];
|
||||
if (hitCountAddress != 0)
|
||||
{
|
||||
Span<byte> hitCountBytes = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(hitCountBytes, 0);
|
||||
if (!ctx.Memory.TryWrite(hitCountAddress, hitCountBytes))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "buYbeLOGWmA",
|
||||
ExportName = "sceAppContentTemporaryDataMount2",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAppContent")]
|
||||
public static int AppContentTemporaryDataMount2(CpuContext ctx)
|
||||
{
|
||||
var mountPointAddress = ctx[CpuRegister.Rsi];
|
||||
if (mountPointAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(ResolveTemp0Root());
|
||||
var mountPointBytes = Encoding.ASCII.GetBytes($"{Temp0MountPoint}\0");
|
||||
if (!ctx.Memory.TryWrite(mountPointAddress, mountPointBytes))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static string ResolveTemp0Root()
|
||||
{
|
||||
const string temp0VariableName = "SHARPEMU_TEMP0_DIR";
|
||||
var configuredRoot = Environment.GetEnvironmentVariable(temp0VariableName);
|
||||
if (!string.IsNullOrWhiteSpace(configuredRoot))
|
||||
{
|
||||
return Path.GetFullPath(configuredRoot);
|
||||
}
|
||||
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
var appName = string.IsNullOrWhiteSpace(app0Root)
|
||||
? "default"
|
||||
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
|
||||
if (string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
appName = "default";
|
||||
}
|
||||
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
|
||||
var root = Path.Combine(Path.GetTempPath(), "SharpEmu", appName, "temp0");
|
||||
Environment.SetEnvironmentVariable(temp0VariableName, root);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,14 @@ namespace SharpEmu.Libs.CxxAbi;
|
||||
|
||||
public static class CxaGuardExports
|
||||
{
|
||||
private const ulong GuardCompleteValue = 0x0000_0000_0000_0001;
|
||||
private const ulong GuardPendingValue = 0x0000_0000_0000_0100;
|
||||
private const ulong GuardStateMask = 0x0000_0000_0000_FFFF;
|
||||
|
||||
private sealed class GuardState
|
||||
{
|
||||
public int OwnerThreadId { get; set; }
|
||||
public int RecursionDepth { get; set; }
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
|
||||
@@ -35,13 +40,13 @@ public static class CxaGuardExports
|
||||
var spinner = new SpinWait();
|
||||
while (true)
|
||||
{
|
||||
if (!TryReadGuardInitialized(ctx, guardPtr, out var initialized))
|
||||
if (!TryReadGuardState(ctx, guardPtr, out _, out var initialized, out var inProgress))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
LogGuardState(ctx, "guard_acquire", guardPtr, initialized);
|
||||
LogGuardState(ctx, "guard_acquire", guardPtr, initialized, inProgress);
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
@@ -53,9 +58,17 @@ public static class CxaGuardExports
|
||||
var newState = new GuardState
|
||||
{
|
||||
OwnerThreadId = currentThreadId,
|
||||
RecursionDepth = 1,
|
||||
};
|
||||
if (_inProgress.TryAdd(guardPtr, newState))
|
||||
{
|
||||
if (!TryWriteGuardState(ctx, guardPtr, GuardPendingValue))
|
||||
{
|
||||
_inProgress.TryRemove(guardPtr, out _);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 1;
|
||||
LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -101,14 +114,28 @@ public static class CxaGuardExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true))
|
||||
if (state is not null)
|
||||
{
|
||||
lock (state)
|
||||
{
|
||||
if (state.RecursionDepth > 1)
|
||||
{
|
||||
state.RecursionDepth--;
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
LogGuardResult("guard_release", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryWriteGuardState(ctx, guardPtr, GuardCompleteValue))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
_inProgress.TryRemove(guardPtr, out _);
|
||||
LogGuardState(ctx, "guard_release", guardPtr, initialized: true);
|
||||
LogGuardState(ctx, "guard_release", guardPtr, initialized: true, inProgress: false);
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -136,60 +163,50 @@ public static class CxaGuardExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
_ = TryWriteGuardInitialized(ctx, guardPtr, initialized: false);
|
||||
_ = TryWriteGuardState(ctx, guardPtr, 0);
|
||||
_inProgress.TryRemove(guardPtr, out _);
|
||||
LogGuardState(ctx, "guard_abort", guardPtr, initialized: false);
|
||||
LogGuardState(ctx, "guard_abort", guardPtr, initialized: false, inProgress: false);
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool TryReadGuardInitialized(CpuContext ctx, ulong guardPtr, out bool initialized)
|
||||
private static bool TryReadGuardState(CpuContext ctx, ulong guardPtr, out ulong word, out bool initialized, out bool inProgress)
|
||||
{
|
||||
word = 0;
|
||||
initialized = false;
|
||||
|
||||
var aligned = guardPtr & ~7UL;
|
||||
var shift = (int)((guardPtr & 7UL) * 8);
|
||||
|
||||
if (!ctx.TryReadUInt64(aligned, out var word))
|
||||
inProgress = false;
|
||||
if (!ctx.TryReadUInt64(guardPtr, out word))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var b0 = (byte)((word >> shift) & 0xFF);
|
||||
initialized = (b0 & 0x01) != 0;
|
||||
initialized = (word & GuardCompleteValue) != 0;
|
||||
inProgress = (word & 0x0000_0000_0000_FF00) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteGuardInitialized(CpuContext ctx, ulong guardPtr, bool initialized)
|
||||
private static bool TryWriteGuardState(CpuContext ctx, ulong guardPtr, ulong stateValue)
|
||||
{
|
||||
var aligned = guardPtr & ~7UL;
|
||||
var shift = (int)((guardPtr & 7UL) * 8);
|
||||
var mask = 0xFFUL << shift;
|
||||
|
||||
if (!ctx.TryReadUInt64(aligned, out var word))
|
||||
if (!ctx.TryReadUInt64(guardPtr, out var word))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var b0 = (byte)((word >> shift) & 0xFF);
|
||||
b0 = initialized ? (byte)(b0 | 0x01) : (byte)(b0 & ~0x01);
|
||||
|
||||
var newWord = (word & ~mask) | ((ulong)b0 << shift);
|
||||
return ctx.TryWriteUInt64(aligned, newWord);
|
||||
var newWord = (word & ~GuardStateMask) | (stateValue & GuardStateMask);
|
||||
return ctx.TryWriteUInt64(guardPtr, newWord);
|
||||
}
|
||||
|
||||
private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized)
|
||||
private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized, bool inProgress)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var aligned = guardPtr & ~7UL;
|
||||
var readable = ctx.TryReadUInt64(aligned, out var word);
|
||||
var readable = ctx.TryReadUInt64(guardPtr, out var word);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} aligned=0x{aligned:X16} init={initialized} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
|
||||
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} init={initialized} in_progress={inProgress} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
|
||||
}
|
||||
|
||||
private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelAprCompatExports
|
||||
{
|
||||
private static readonly ConcurrentDictionary<uint, ulong> _submittedCommandBuffers = new();
|
||||
private static int _nextSubmissionId;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ASoW5WE-UPo",
|
||||
ExportName = "sceKernelAprSubmitCommandBufferAndGetResult",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAprSubmitCommandBufferAndGetResult(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var priority = ctx[CpuRegister.Rsi];
|
||||
var resultAddress = ctx[CpuRegister.Rdx];
|
||||
var outSubmissionId = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
|
||||
if (submissionId == 0)
|
||||
{
|
||||
submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
|
||||
}
|
||||
|
||||
_submittedCommandBuffers[submissionId] = commandBuffer;
|
||||
|
||||
if (outSubmissionId != 0 && !TryWriteUInt32(ctx, outSubmissionId, submissionId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceApr(ctx, "submit_get_result", submissionId, commandBuffer, priority, resultAddress);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "rqwFKI4PAiM",
|
||||
ExportName = "sceKernelAprWaitCommandBuffer",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAprWaitCommandBuffer(CpuContext ctx)
|
||||
{
|
||||
var submissionId = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var priority = ctx[CpuRegister.Rsi];
|
||||
var resultAddress = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (!_submittedCommandBuffers.TryRemove(submissionId, out var commandBuffer))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceApr(ctx, "wait", submissionId, commandBuffer, priority, resultAddress);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "eE4Szl8sil8",
|
||||
ExportName = "sceKernelAprSubmitCommandBuffer",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAprSubmitCommandBuffer(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
|
||||
_submittedCommandBuffers[submissionId] = commandBuffer;
|
||||
|
||||
TraceApr(ctx, "submit", submissionId, commandBuffer, ctx[CpuRegister.Rsi], 0);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "qvMUCyyaCSI",
|
||||
ExportName = "sceKernelAprSubmitCommandBufferAndGetId",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAprSubmitCommandBufferAndGetId(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var outSubmissionId = ctx[CpuRegister.Rdx];
|
||||
if (commandBuffer == 0 || outSubmissionId == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
|
||||
_submittedCommandBuffers[submissionId] = commandBuffer;
|
||||
|
||||
if (!TryWriteUInt32(ctx, outSubmissionId, submissionId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceApr(ctx, "submit_get_id", submissionId, commandBuffer, ctx[CpuRegister.Rsi], outSubmissionId);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool TryWriteAprResult(CpuContext ctx, ulong resultAddress)
|
||||
{
|
||||
Span<byte> result = stackalloc byte[sizeof(ulong)];
|
||||
result.Clear();
|
||||
return ctx.Memory.TryWrite(resultAddress, result);
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static void TraceApr(
|
||||
CpuContext ctx,
|
||||
string operation,
|
||||
uint submissionId,
|
||||
ulong commandBuffer,
|
||||
ulong priority,
|
||||
ulong aux)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var returnRip = 0UL;
|
||||
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] apr.{operation}: id=0x{submissionId:X8} cmd=0x{commandBuffer:X16} priority=0x{priority:X16} aux=0x{aux:X16} ret=0x{returnRip:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelEventQueueCompatExports
|
||||
{
|
||||
private const int KernelEventSize = 0x20;
|
||||
|
||||
private static readonly object _eventQueueGate = new();
|
||||
private static readonly HashSet<ulong> _eventQueues = new();
|
||||
private static readonly Dictionary<ulong, Queue<KernelQueuedEvent>> _pendingEvents = new();
|
||||
private static long _nextEventQueueHandle = 1;
|
||||
|
||||
public readonly record struct KernelQueuedEvent(
|
||||
ulong Ident,
|
||||
short Filter,
|
||||
ushort Flags,
|
||||
uint Fflags,
|
||||
ulong Data,
|
||||
ulong UserData);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "D0OdFMjp46I",
|
||||
ExportName = "sceKernelCreateEqueue",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCreateEqueue(CpuContext ctx)
|
||||
{
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
if (outAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var handle = unchecked((ulong)Interlocked.Increment(ref _nextEventQueueHandle));
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
_eventQueues.Add(handle);
|
||||
_pendingEvents[handle] = new Queue<KernelQueuedEvent>();
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(outAddress, handle))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceEventQueue(ctx, "create", handle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "jpFjmgAC5AE",
|
||||
ExportName = "sceKernelDeleteEqueue",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteEqueue(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
_eventQueues.Remove(handle);
|
||||
_pendingEvents.Remove(handle);
|
||||
}
|
||||
|
||||
TraceEventQueue(ctx, "delete", handle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "WDszmSbWuDk",
|
||||
ExportName = "sceKernelAddUserEventEdge",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddUserEventEdge(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_user_edge", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4R6-OvI2cEA",
|
||||
ExportName = "sceKernelAddUserEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddUserEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_user", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "LJDwdSNTnDg",
|
||||
ExportName = "sceKernelDeleteUserEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteUserEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "delete_user", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "F6e0kwo4cnk",
|
||||
ExportName = "sceKernelTriggerUserEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelTriggerUserEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "trigger_user", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bBfz7kMF2Ho",
|
||||
ExportName = "sceKernelAddAmprEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddAmprEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_ampr", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vuae5JPNt9A",
|
||||
ExportName = "sceKernelAddAmprSystemEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddAmprSystemEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_ampr_system", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bMmid3pfyjo",
|
||||
ExportName = "sceKernelDeleteAmprEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteAmprEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "delete_ampr", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Ij+ryuEClXQ",
|
||||
ExportName = "sceKernelDeleteAmprSystemEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteAmprSystemEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "delete_ampr_system", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "QyrxcdBrb0M",
|
||||
ExportName = "sceKernelGetKqueueFromEqueue",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelGetKqueueFromEqueue(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = ctx[CpuRegister.Rdi];
|
||||
TraceEventQueue(ctx, "get_kqueue", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "fzyMKs9kim0",
|
||||
ExportName = "sceKernelWaitEqueue",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelWaitEqueue(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var eventsAddress = ctx[CpuRegister.Rsi];
|
||||
var eventCapacity = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
|
||||
var outCountAddress = ctx[CpuRegister.Rcx];
|
||||
var timeoutAddress = ctx[CpuRegister.R8];
|
||||
|
||||
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
||||
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (deliveredCount > 0)
|
||||
{
|
||||
TraceEventQueue(ctx, "wait-deliver", handle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (timeoutAddress == 0 && GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEqueue"))
|
||||
{
|
||||
TraceEventQueue(ctx, "wait-block", handle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
TraceEventQueue(ctx, "wait", handle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
public static bool IsValidEqueue(ulong handle)
|
||||
{
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
return _eventQueues.Contains(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EnqueueEvent(ulong handle, KernelQueuedEvent queuedEvent)
|
||||
{
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
if (!_eventQueues.Contains(handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_pendingEvents.TryGetValue(handle, out var queue))
|
||||
{
|
||||
queue = new Queue<KernelQueuedEvent>();
|
||||
_pendingEvents[handle] = queue;
|
||||
}
|
||||
|
||||
queue.Enqueue(queuedEvent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static int DequeueEvents(CpuContext ctx, ulong handle, ulong eventsAddress, int eventCapacity)
|
||||
{
|
||||
if (eventsAddress == 0 || eventCapacity <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
KernelQueuedEvent[] events;
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
if (!_pendingEvents.TryGetValue(handle, out var queue) || queue.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = Math.Min(eventCapacity, queue.Count);
|
||||
events = new KernelQueuedEvent[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
events[i] = queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < events.Length; i++)
|
||||
{
|
||||
if (!WriteKernelEvent(ctx, eventsAddress + ((ulong)i * KernelEventSize), events[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return events.Length;
|
||||
}
|
||||
|
||||
private static bool WriteKernelEvent(CpuContext ctx, ulong address, KernelQueuedEvent queuedEvent)
|
||||
{
|
||||
Span<byte> eventBytes = stackalloc byte[KernelEventSize];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x00..], queuedEvent.Ident);
|
||||
BinaryPrimitives.WriteInt16LittleEndian(eventBytes[0x08..], queuedEvent.Filter);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(eventBytes[0x0A..], queuedEvent.Flags);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(eventBytes[0x0C..], queuedEvent.Fflags);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x10..], queuedEvent.Data);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(eventBytes[0x18..], queuedEvent.UserData);
|
||||
return ctx.Memory.TryWrite(address, eventBytes);
|
||||
}
|
||||
|
||||
private static void TraceEventQueue(CpuContext ctx, string operation, ulong handle)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_EQUEUE"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var returnRip = 0UL;
|
||||
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] equeue.{operation}: handle=0x{handle:X16} rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} ret=0x{returnRip:X16}");
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelExports
|
||||
{
|
||||
private static long _nextThreadId = 1;
|
||||
private static int _nextFileDescriptor = 2;
|
||||
private static readonly object _cxaGate = new();
|
||||
private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
|
||||
private static readonly object _coredumpGate = new();
|
||||
private static ulong _coredumpHandler;
|
||||
private static ulong _coredumpHandlerContext;
|
||||
|
||||
private readonly record struct CxaDestructorEntry(
|
||||
ulong Function,
|
||||
@@ -29,6 +31,23 @@ public static class KernelExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "8zLSfEfW5AU",
|
||||
ExportName = "sceCoredumpRegisterCoredumpHandler",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceCoredump")]
|
||||
public static int CoredumpRegisterHandler(CpuContext ctx)
|
||||
{
|
||||
lock (_coredumpGate)
|
||||
{
|
||||
_coredumpHandler = ctx[CpuRegister.Rdi];
|
||||
_coredumpHandlerContext = ctx[CpuRegister.Rsi];
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uMei1W9uyNo",
|
||||
ExportName = "exit",
|
||||
@@ -36,7 +55,10 @@ public static class KernelExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int Exit(CpuContext ctx)
|
||||
{
|
||||
_ = ctx;
|
||||
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] exit(status={status})");
|
||||
GuestThreadExecution.RequestCurrentEntryExit("exit", status);
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -49,7 +71,8 @@ public static class KernelExports
|
||||
{
|
||||
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
GuestThreadExecution.RequestCurrentEntryExit("catchReturnFromMain", status);
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -175,12 +198,37 @@ public static class KernelExports
|
||||
public static int PthreadCreate(CpuContext ctx)
|
||||
{
|
||||
var threadIdAddress = ctx[CpuRegister.Rdi];
|
||||
var nextThreadId = unchecked((ulong)Interlocked.Increment(ref _nextThreadId));
|
||||
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, nextThreadId))
|
||||
var attrAddress = ctx[CpuRegister.Rsi];
|
||||
var entryAddress = ctx[CpuRegister.Rdx];
|
||||
var argument = ctx[CpuRegister.Rcx];
|
||||
var nameAddress = ctx[CpuRegister.R8];
|
||||
var name = nameAddress == 0 ? string.Empty : ReadCString(ctx, nameAddress, 256);
|
||||
var threadHandle = KernelPthreadState.CreateThreadHandle(name);
|
||||
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (ShouldTracePthread())
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
|
||||
}
|
||||
|
||||
var scheduler = GuestThreadExecution.Scheduler;
|
||||
if (scheduler is not null && entryAddress != 0)
|
||||
{
|
||||
var request = new GuestThreadStartRequest(threadHandle, entryAddress, argument, attrAddress, name);
|
||||
if (!scheduler.TryStartThread(ctx, request, out var error))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] pthread_create: failed to schedule guest thread '{name}' entry=0x{entryAddress:X16}: {error}");
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -194,6 +242,16 @@ public static class KernelExports
|
||||
return PthreadCreate(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Jmi+9w9u0E4",
|
||||
ExportName = "pthread_create_name_np",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadCreateNameNp(CpuContext ctx)
|
||||
{
|
||||
return PthreadCreate(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "onNY9Byn-W8",
|
||||
ExportName = "scePthreadJoin",
|
||||
@@ -201,12 +259,20 @@ public static class KernelExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadJoin(CpuContext ctx)
|
||||
{
|
||||
var threadId = ctx[CpuRegister.Rdi];
|
||||
var returnValueAddress = ctx[CpuRegister.Rsi];
|
||||
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (ShouldTracePthread())
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -225,22 +291,14 @@ public static class KernelExports
|
||||
ExportName = "open",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int Open(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
public static int Open(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "1G3lF1Gg1k8",
|
||||
ExportName = "sceKernelOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelOpen(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hcuQgD53UxM",
|
||||
@@ -251,84 +309,17 @@ public static class KernelExports
|
||||
{
|
||||
ulong fmtPtr = ctx[CpuRegister.Rdi];
|
||||
string fmt = ReadCString(ctx, fmtPtr, 4096);
|
||||
|
||||
ulong[] args =
|
||||
string outStr = KernelMemoryCompatExports.FormatStringFromVarArgs(ctx, fmt, firstGpArgIndex: 1);
|
||||
if (outStr.EndsWith('\n') || outStr.EndsWith('\r'))
|
||||
{
|
||||
ctx[CpuRegister.Rsi],
|
||||
ctx[CpuRegister.Rdx],
|
||||
ctx[CpuRegister.Rcx],
|
||||
ctx[CpuRegister.R8],
|
||||
ctx[CpuRegister.R9],
|
||||
};
|
||||
|
||||
int argIndex = 0;
|
||||
var sb = new System.Text.StringBuilder(fmt.Length + 64);
|
||||
|
||||
for (int i = 0; i < fmt.Length; i++)
|
||||
Console.Write($"[DEBUG][PRINF] {outStr}");
|
||||
}
|
||||
else
|
||||
{
|
||||
char ch = fmt[i];
|
||||
if (ch != '%')
|
||||
{
|
||||
sb.Append(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < fmt.Length && fmt[i + 1] == '%')
|
||||
{
|
||||
sb.Append('%');
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int j = i + 1;
|
||||
while (j < fmt.Length && "-+ #0".IndexOf(fmt[j]) >= 0) j++;
|
||||
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
|
||||
|
||||
if (j < fmt.Length && fmt[j] == '.')
|
||||
{
|
||||
j++;
|
||||
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
|
||||
}
|
||||
|
||||
if (j >= fmt.Length) break;
|
||||
char spec = fmt[j];
|
||||
i = j;
|
||||
|
||||
ulong a = argIndex < args.Length ? args[argIndex++] : 0;
|
||||
|
||||
switch (spec)
|
||||
{
|
||||
case 's':
|
||||
sb.Append(a == 0 ? "(null)" : ReadCString(ctx, a, 4096));
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
sb.Append(unchecked((int)a));
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
sb.Append(unchecked((uint)a));
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
sb.Append(unchecked((uint)a).ToString("x"));
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
sb.Append("0x").Append(a.ToString("x16"));
|
||||
break;
|
||||
|
||||
default:
|
||||
sb.Append('%').Append(spec);
|
||||
break;
|
||||
}
|
||||
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
|
||||
}
|
||||
|
||||
string outStr = sb.ToString();
|
||||
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
|
||||
|
||||
ctx[CpuRegister.Rax] = (ulong)outStr.Length;
|
||||
ctx[CpuRegister.Rax] = (ulong)System.Text.Encoding.UTF8.GetByteCount(outStr);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -370,4 +361,9 @@ public static class KernelExports
|
||||
try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); }
|
||||
catch { return System.Text.Encoding.ASCII.GetString(buf.Slice(0, len)); }
|
||||
}
|
||||
|
||||
private static bool ShouldTracePthread()
|
||||
{
|
||||
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,39 +3,45 @@
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Threading;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelPthreadCompatExports
|
||||
{
|
||||
private const int MutexTypeNormal = 0;
|
||||
private const int MutexTypeRecursive = 1;
|
||||
private const int MutexTypeDefault = 1;
|
||||
private const int MutexTypeErrorCheck = 1;
|
||||
private const int MutexTypeRecursive = 2;
|
||||
private const int MutexTypeNormal = 3;
|
||||
private const int MutexTypeAdaptiveNp = 4;
|
||||
private const ulong StaticAdaptiveMutexInitializer = 1;
|
||||
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
|
||||
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
|
||||
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
|
||||
private const int DefaultSpuriousCondWakeMilliseconds = 1;
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
|
||||
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
|
||||
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
|
||||
private static readonly HashSet<ulong> _condAttrStates = new();
|
||||
private static long _nextSyntheticThreadId = 1;
|
||||
private static long _nextSyntheticMutexHandleId = 1;
|
||||
private static long _nextSyntheticMutexAttrHandleId = 1;
|
||||
[ThreadStatic]
|
||||
private static ulong _currentThreadId;
|
||||
private static long _nextSyntheticCondHandleId = 1;
|
||||
|
||||
private sealed class PthreadMutexState
|
||||
{
|
||||
public SemaphoreSlim Semaphore { get; } = new(1, 1);
|
||||
public ulong OwnerThreadId { get; set; }
|
||||
public int RecursionCount { get; set; }
|
||||
public int Type { get; set; } = MutexTypeNormal;
|
||||
public int Type { get; set; } = MutexTypeDefault;
|
||||
public int Protocol { get; set; }
|
||||
}
|
||||
|
||||
private sealed class PthreadCondState
|
||||
{
|
||||
public int PendingSignals { get; set; }
|
||||
public object SyncRoot { get; } = new();
|
||||
public ulong SignalEpoch { get; set; }
|
||||
public int Waiters { get; set; }
|
||||
}
|
||||
|
||||
@@ -48,9 +54,9 @@ public static class KernelPthreadCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSelf(CpuContext ctx)
|
||||
{
|
||||
var currentThreadId = GetCurrentThreadId();
|
||||
ctx[CpuRegister.Rax] = currentThreadId;
|
||||
TracePthreadSelf(ctx, currentThreadId);
|
||||
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
|
||||
ctx[CpuRegister.Rax] = currentThreadHandle;
|
||||
TracePthreadSelf(ctx, currentThreadHandle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -84,18 +90,14 @@ public static class KernelPthreadCompatExports
|
||||
ExportName = "scePthreadGetthreadid",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadGetthreadid(CpuContext ctx)
|
||||
{
|
||||
var currentThreadId = GetCurrentThreadId();
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
public static int PthreadGetthreadid(CpuContext ctx) => PthreadGetthreadidCore(ctx);
|
||||
|
||||
ctx[CpuRegister.Rax] = currentThreadId;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
[SysAbiExport(
|
||||
Nid = "3eqs37G74-s",
|
||||
ExportName = "pthread_getthreadid_np",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadGetthreadidNp(CpuContext ctx) => PthreadGetthreadidCore(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "cmo1RIYva9o",
|
||||
@@ -167,6 +169,12 @@ public static class KernelPthreadCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadMutexUnlock(CpuContext ctx) => PthreadMutexUnlockCore(ctx, ctx[CpuRegister.Rdi], requireOwner: true);
|
||||
|
||||
private static int PthreadGetthreadidCore(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = KernelPthreadState.GetCurrentThreadUniqueId();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "F8bUHwAG284",
|
||||
ExportName = "scePthreadMutexattrInit",
|
||||
@@ -216,19 +224,33 @@ public static class KernelPthreadCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "5txKfcMUAok",
|
||||
ExportName = "pthread_mutexattr_setprotocol",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadMutexattrSetprotocol(CpuContext ctx) => PthreadMutexattrSetprotocolCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "2Tb92quprl0",
|
||||
ExportName = "scePthreadCondInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx[CpuRegister.Rdi]);
|
||||
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "0TyVk4MSLt0",
|
||||
ExportName = "pthread_cond_init",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "g+PZd2hiacg",
|
||||
ExportName = "scePthreadCondDestroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx[CpuRegister.Rdi]);
|
||||
public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx, ctx[CpuRegister.Rdi]);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "WKAXJ4XBPQ4",
|
||||
@@ -242,21 +264,21 @@ public static class KernelPthreadCompatExports
|
||||
ExportName = "scePthreadCondTimedwait",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true);
|
||||
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: unchecked((uint)ctx[CpuRegister.Rdx]));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "kDh-NfxgMtE",
|
||||
ExportName = "scePthreadCondSignal",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: false);
|
||||
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JGgj7Uvrl+A",
|
||||
ExportName = "scePthreadCondBroadcast",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
|
||||
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Op8TBGY5KHg",
|
||||
@@ -270,7 +292,14 @@ public static class KernelPthreadCompatExports
|
||||
ExportName = "pthread_cond_broadcast",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
|
||||
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "2MOy+rUfuhQ",
|
||||
ExportName = "pthread_cond_signal",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "m5-2bsNfv7s",
|
||||
@@ -363,6 +392,7 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, 0);
|
||||
state.Semaphore.Dispose();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -374,23 +404,13 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
|
||||
PthreadMutexState state;
|
||||
lock (_stateGate)
|
||||
if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
|
||||
{
|
||||
if (!_mutexStates.TryGetValue(resolvedAddress, out state!))
|
||||
{
|
||||
state = new PthreadMutexState();
|
||||
_mutexStates[resolvedAddress] = state;
|
||||
}
|
||||
|
||||
if (resolvedAddress != mutexAddress)
|
||||
{
|
||||
_mutexStates[mutexAddress] = state;
|
||||
}
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var currentThreadId = GetCurrentThreadId();
|
||||
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
|
||||
lock (state)
|
||||
{
|
||||
if (state.OwnerThreadId == currentThreadId)
|
||||
@@ -402,8 +422,28 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
|
||||
if (state.Type is MutexTypeNormal or MutexTypeAdaptiveNp)
|
||||
{
|
||||
if (tryOnly)
|
||||
{
|
||||
TracePthreadMutex(ctx, "trylock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
}
|
||||
|
||||
// Normal/adaptive mutexes do not report EDEADLK on self-lock.
|
||||
// Under the current single-host-thread guest execution model,
|
||||
// treating them as nested ownership keeps init paths moving
|
||||
// without turning a would-block path into a hard error.
|
||||
state.RecursionCount++;
|
||||
TracePthreadMutex(ctx, "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
var ownedResult = tryOnly
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, ownedResult);
|
||||
return ownedResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,8 +458,8 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
if (!acquired)
|
||||
{
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
}
|
||||
|
||||
lock (state)
|
||||
@@ -439,20 +479,13 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
|
||||
PthreadMutexState? state;
|
||||
lock (_stateGate)
|
||||
if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
|
||||
{
|
||||
_mutexStates.TryGetValue(resolvedAddress, out state);
|
||||
}
|
||||
|
||||
if (state is null)
|
||||
{
|
||||
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, GetCurrentThreadId(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var currentThreadId = GetCurrentThreadId();
|
||||
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
|
||||
var shouldRelease = false;
|
||||
lock (state)
|
||||
{
|
||||
@@ -464,8 +497,8 @@ public static class KernelPthreadCompatExports
|
||||
|
||||
if (requireOwner && state.OwnerThreadId != currentThreadId)
|
||||
{
|
||||
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
state.RecursionCount--;
|
||||
@@ -503,8 +536,8 @@ public static class KernelPthreadCompatExports
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeNormal, 0);
|
||||
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeNormal, 0);
|
||||
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
|
||||
@@ -543,10 +576,10 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
{
|
||||
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
|
||||
_mutexAttrStates[resolvedAddress] = state with { Type = type };
|
||||
_mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
|
||||
if (resolvedAddress != attrAddress)
|
||||
{
|
||||
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
|
||||
@@ -568,7 +601,7 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
{
|
||||
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
|
||||
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
|
||||
@@ -610,6 +643,59 @@ public static class KernelPthreadCompatExports
|
||||
return mutexAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
if (mutexAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexStates.TryGetValue(mutexAddress, out state))
|
||||
{
|
||||
resolvedAddress = mutexAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.TryReadUInt64(mutexAddress, out var pointedHandle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pointedHandle == StaticAdaptiveMutexInitializer)
|
||||
{
|
||||
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeAdaptiveNp, out resolvedAddress, out state);
|
||||
}
|
||||
|
||||
if (pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexStates.TryGetValue(pointedHandle, out state))
|
||||
{
|
||||
_mutexStates[mutexAddress] = state;
|
||||
resolvedAddress = pointedHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
resolvedAddress = pointedHandle;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!createIfZero)
|
||||
{
|
||||
resolvedAddress = mutexAddress;
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeDefault, out resolvedAddress, out state);
|
||||
}
|
||||
|
||||
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
|
||||
{
|
||||
if (attrAddress == 0)
|
||||
@@ -651,145 +737,351 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
|
||||
? state
|
||||
: default;
|
||||
: new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ResolveCondHandle(CpuContext ctx, ulong condAddress)
|
||||
{
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_condStates.ContainsKey(condAddress))
|
||||
{
|
||||
return condAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(condAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_condStates.ContainsKey(pointedHandle))
|
||||
{
|
||||
return pointedHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return condAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadCondState? state)
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
state = null;
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_condStates.TryGetValue(condAddress, out state))
|
||||
{
|
||||
resolvedAddress = condAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx is null || !ctx.TryReadUInt64(condAddress, out var pointedHandle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_condStates.TryGetValue(pointedHandle, out state))
|
||||
{
|
||||
_condStates[condAddress] = state;
|
||||
resolvedAddress = pointedHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
resolvedAddress = pointedHandle;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!createIfZero)
|
||||
{
|
||||
resolvedAddress = condAddress;
|
||||
return false;
|
||||
}
|
||||
|
||||
var createdState = new PthreadCondState();
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates[condAddress] = createdState;
|
||||
_condStates[syntheticHandle] = createdState;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
state = createdState;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
|
||||
{
|
||||
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
|
||||
return baseAddress + (id << 4);
|
||||
}
|
||||
|
||||
private static int PthreadCondInitCore(ulong condAddress)
|
||||
private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress)
|
||||
{
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates[condAddress] = new PthreadCondState();
|
||||
var state = new PthreadCondState();
|
||||
_condStates[condAddress] = state;
|
||||
_condStates[syntheticHandle] = state;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int PthreadCondDestroyCore(ulong condAddress)
|
||||
private static int PthreadCondDestroyCore(CpuContext ctx, ulong condAddress)
|
||||
{
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveCondHandle(ctx, condAddress);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_condStates.Remove(condAddress);
|
||||
_condStates.Remove(resolvedAddress);
|
||||
if (resolvedAddress != condAddress)
|
||||
{
|
||||
_condStates.Remove(condAddress);
|
||||
}
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(condAddress, 0);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed)
|
||||
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed, uint timeoutUsec = 0)
|
||||
{
|
||||
if (condAddress == 0 || mutexAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
|
||||
{
|
||||
if (!_condStates.TryGetValue(condAddress, out var state))
|
||||
{
|
||||
state = new PthreadCondState();
|
||||
_condStates[condAddress] = state;
|
||||
}
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
var spuriousWake = false;
|
||||
lock (state.SyncRoot)
|
||||
{
|
||||
state.Waiters++;
|
||||
if (state.PendingSignals > 0)
|
||||
var observedEpoch = state.SignalEpoch;
|
||||
TracePthreadCond("wait-enter", condAddress, mutexAddress, state, timed, waitResult);
|
||||
|
||||
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
|
||||
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
state.PendingSignals--;
|
||||
state.Waiters--;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
TracePthreadCond("wait-unlock-fail", condAddress, mutexAddress, state, timed, unlockResult);
|
||||
return unlockResult;
|
||||
}
|
||||
}
|
||||
|
||||
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
|
||||
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
return unlockResult;
|
||||
}
|
||||
var scheduler = GuestThreadExecution.Scheduler;
|
||||
if (!timed && GuestThreadExecution.RequestCurrentThreadBlock("pthread_cond_wait"))
|
||||
{
|
||||
TracePthreadCond("wait-block", condAddress, mutexAddress, state, timed, waitResult);
|
||||
return waitResult;
|
||||
}
|
||||
|
||||
if (timed)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Yield();
|
||||
if (scheduler is not null)
|
||||
{
|
||||
Monitor.Exit(state.SyncRoot);
|
||||
try
|
||||
{
|
||||
scheduler.Pump(ctx, "pthread_cond_wait");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Enter(state.SyncRoot);
|
||||
}
|
||||
}
|
||||
|
||||
while (state.SignalEpoch == observedEpoch)
|
||||
{
|
||||
if (!timed)
|
||||
{
|
||||
if (!Monitor.Wait(state.SyncRoot, GetCondSpuriousWakeTimeout()))
|
||||
{
|
||||
spuriousWake = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Monitor.Wait(state.SyncRoot, GetCondWaitTimeout(timeoutUsec)))
|
||||
{
|
||||
waitResult = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
state.Waiters = Math.Max(0, state.Waiters - 1);
|
||||
TracePthreadCond(
|
||||
spuriousWake
|
||||
? "wait-spurious"
|
||||
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout"),
|
||||
condAddress,
|
||||
mutexAddress,
|
||||
state,
|
||||
timed,
|
||||
waitResult);
|
||||
}
|
||||
|
||||
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
|
||||
lock (_stateGate)
|
||||
if (lockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
if (_condStates.TryGetValue(condAddress, out var state))
|
||||
{
|
||||
state.Waiters = Math.Max(0, state.Waiters - 1);
|
||||
}
|
||||
TracePthreadCond("wait-relock-fail", condAddress, mutexAddress, state, timed, lockResult);
|
||||
return lockResult;
|
||||
}
|
||||
|
||||
return lockResult;
|
||||
TracePthreadCond(
|
||||
spuriousWake
|
||||
? "wait-exit-spurious"
|
||||
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout"),
|
||||
condAddress,
|
||||
mutexAddress,
|
||||
state,
|
||||
timed,
|
||||
waitResult);
|
||||
return waitResult;
|
||||
}
|
||||
|
||||
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast)
|
||||
private static int PthreadCondSignalCore(CpuContext ctx, ulong condAddress, bool broadcast)
|
||||
{
|
||||
if (condAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
|
||||
{
|
||||
if (!_condStates.TryGetValue(condAddress, out var state))
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
lock (state.SyncRoot)
|
||||
{
|
||||
if (state.Waiters > 0)
|
||||
{
|
||||
state = new PthreadCondState();
|
||||
_condStates[condAddress] = state;
|
||||
state.SignalEpoch++;
|
||||
if (broadcast)
|
||||
{
|
||||
Monitor.PulseAll(state.SyncRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
Monitor.Pulse(state.SyncRoot);
|
||||
}
|
||||
}
|
||||
|
||||
if (broadcast)
|
||||
{
|
||||
state.PendingSignals += Math.Max(1, state.Waiters);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.PendingSignals++;
|
||||
}
|
||||
TracePthreadCond(broadcast ? "broadcast" : "signal", condAddress, mutexAddress: 0, state, timed: false, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static ulong GetCurrentThreadId()
|
||||
private static TimeSpan GetCondWaitTimeout(uint timeoutUsec)
|
||||
{
|
||||
if (_currentThreadId != 0)
|
||||
if (timeoutUsec == 0)
|
||||
{
|
||||
return _currentThreadId;
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
_currentThreadId = unchecked((ulong)Interlocked.Increment(ref _nextSyntheticThreadId));
|
||||
return _currentThreadId;
|
||||
return TimeSpan.FromTicks((long)timeoutUsec * 10L);
|
||||
}
|
||||
|
||||
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadId)
|
||||
private static TimeSpan GetCondSpuriousWakeTimeout()
|
||||
{
|
||||
if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_PTHREAD_COND_SPURIOUS_WAKE_MS"), out var milliseconds))
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(Math.Max(1, milliseconds));
|
||||
}
|
||||
|
||||
return TimeSpan.FromMilliseconds(DefaultSpuriousCondWakeMilliseconds);
|
||||
}
|
||||
|
||||
private static int NormalizeMutexType(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
0 => MutexTypeDefault,
|
||||
1 => MutexTypeErrorCheck,
|
||||
2 => MutexTypeRecursive,
|
||||
3 => MutexTypeNormal,
|
||||
4 => MutexTypeAdaptiveNp,
|
||||
_ => MutexTypeDefault,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool CreateImplicitMutexState(CpuContext ctx, ulong mutexAddress, int type, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
|
||||
{
|
||||
var createdState = new PthreadMutexState
|
||||
{
|
||||
Type = type,
|
||||
};
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_mutexStates.TryGetValue(mutexAddress, out state))
|
||||
{
|
||||
resolvedAddress = mutexAddress;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_mutexStates.TryGetValue(syntheticHandle, out state))
|
||||
{
|
||||
resolvedAddress = syntheticHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
_mutexStates[mutexAddress] = createdState;
|
||||
_mutexStates[syntheticHandle] = createdState;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
state = createdState;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
|
||||
{
|
||||
if (!ShouldTracePthread())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadId:X16}");
|
||||
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadHandle:X16} tid=0x{currentThreadId:X16}");
|
||||
}
|
||||
|
||||
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
|
||||
@@ -808,6 +1100,18 @@ public static class KernelPthreadCompatExports
|
||||
$"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}");
|
||||
}
|
||||
|
||||
private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result)
|
||||
{
|
||||
if (!ShouldTracePthread())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] pthread_cond_{operation}: cond=0x{condAddress:X16} mutex=0x{mutexAddress:X16} " +
|
||||
$"waiters={(state?.Waiters ?? 0)} epoch=0x{(state?.SignalEpoch ?? 0):X} timed={timed} result=0x{unchecked((uint)result):X8}");
|
||||
}
|
||||
|
||||
private static bool ShouldTracePthread()
|
||||
{
|
||||
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
|
||||
|
||||
@@ -5,6 +5,7 @@ using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
@@ -18,16 +19,19 @@ public static class KernelPthreadExtendedCompatExports
|
||||
private const int DefaultInheritSched = 0;
|
||||
private const int DefaultSchedPolicy = 0;
|
||||
private const int DefaultSchedPriority = 0;
|
||||
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
||||
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
|
||||
private static readonly Dictionary<ulong, PthreadAttrState> _attrStates = new();
|
||||
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
|
||||
private static readonly Dictionary<ulong, PthreadRwlockState> _rwlockStates = new();
|
||||
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
|
||||
private static int _nextTlsKey = 1;
|
||||
private static long _nextSyntheticRwlockHandleId = 1;
|
||||
private static long _nextSyntheticPthreadAttrHandleId = 1;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<int, ulong>? _threadLocalSpecific;
|
||||
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new();
|
||||
|
||||
private sealed class ThreadState
|
||||
{
|
||||
@@ -38,6 +42,47 @@ public static class KernelPthreadExtendedCompatExports
|
||||
public PthreadAttrState Attributes { get; set; } = PthreadAttrState.Default;
|
||||
}
|
||||
|
||||
private sealed class PthreadRwlockState
|
||||
{
|
||||
public object SyncRoot { get; } = new();
|
||||
public Dictionary<ulong, int> ReaderCounts { get; } = new();
|
||||
public int ReaderTotalCount { get; set; }
|
||||
public ulong WriterThreadId { get; set; }
|
||||
public int WaitingWriters { get; set; }
|
||||
|
||||
public int GetReaderCount(ulong threadId)
|
||||
{
|
||||
return ReaderCounts.TryGetValue(threadId, out var count) ? count : 0;
|
||||
}
|
||||
|
||||
public void AddReader(ulong threadId)
|
||||
{
|
||||
ReaderCounts.TryGetValue(threadId, out var currentCount);
|
||||
ReaderCounts[threadId] = currentCount + 1;
|
||||
ReaderTotalCount++;
|
||||
}
|
||||
|
||||
public bool RemoveReader(ulong threadId)
|
||||
{
|
||||
if (!ReaderCounts.TryGetValue(threadId, out var currentCount) || currentCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentCount == 1)
|
||||
{
|
||||
ReaderCounts.Remove(threadId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReaderCounts[threadId] = currentCount - 1;
|
||||
}
|
||||
|
||||
ReaderTotalCount = Math.Max(0, ReaderTotalCount - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct TlsKeyState(ulong Destructor);
|
||||
|
||||
private readonly record struct PthreadAttrState(
|
||||
@@ -192,6 +237,41 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Xs9hdiD7sAA",
|
||||
ExportName = "pthread_setschedparam",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadSetschedparam(CpuContext ctx)
|
||||
{
|
||||
var thread = ctx[CpuRegister.Rdi];
|
||||
var policy = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var schedParamAddress = ctx[CpuRegister.Rdx];
|
||||
if (thread == 0 || schedParamAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryReadInt32(ctx, schedParamAddress, out var schedPriority))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
var state = GetOrCreateThreadStateLocked(thread);
|
||||
state.Priority = schedPriority;
|
||||
state.Attributes = state.Attributes with
|
||||
{
|
||||
SchedPolicy = policy,
|
||||
SchedPriority = schedPriority,
|
||||
};
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "nsYoNRywwNg",
|
||||
ExportName = "scePthreadAttrInit",
|
||||
@@ -205,15 +285,32 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticPthreadAttrHandleBase, ref _nextSyntheticPthreadAttrHandleId);
|
||||
if (!ctx.TryWriteUInt64(attrAddress, syntheticHandle))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_attrStates[attrAddress] = PthreadAttrState.Default;
|
||||
_attrStates[syntheticHandle] = PthreadAttrState.Default;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "wtkt-teR1so",
|
||||
ExportName = "pthread_attr_init",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadAttrInit(CpuContext ctx)
|
||||
{
|
||||
return PthreadAttrInit(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "62KCwEMmzcM",
|
||||
ExportName = "scePthreadAttrDestroy",
|
||||
@@ -227,15 +324,31 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_attrStates.Remove(attrAddress);
|
||||
if (resolvedAddress != attrAddress)
|
||||
{
|
||||
_attrStates.Remove(resolvedAddress);
|
||||
}
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(attrAddress, 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "zHchY8ft5pk",
|
||||
ExportName = "pthread_attr_destroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadAttrDestroy(CpuContext ctx)
|
||||
{
|
||||
return PthreadAttrDestroy(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "x1X76arYMxU",
|
||||
ExportName = "scePthreadAttrGet",
|
||||
@@ -568,16 +681,32 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
||||
lock (_stateGate)
|
||||
{
|
||||
var state = GetOrCreateAttrStateLocked(attrAddress);
|
||||
_attrStates[attrAddress] = state with { StackSize = stackSize };
|
||||
var state = GetOrCreateAttrStateLocked(resolvedAddress);
|
||||
var updated = state with { StackSize = stackSize };
|
||||
_attrStates[resolvedAddress] = updated;
|
||||
if (resolvedAddress != attrAddress)
|
||||
{
|
||||
_attrStates[attrAddress] = updated;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "2Q0z6rnBrTE",
|
||||
ExportName = "pthread_attr_setstacksize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadAttrSetstacksize(CpuContext ctx)
|
||||
{
|
||||
return PthreadAttrSetstacksize(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "6ULAa0fq4jA",
|
||||
ExportName = "scePthreadRwlockInit",
|
||||
@@ -591,20 +720,31 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_rwlockStates.Remove(rwlockAddress, out var existing))
|
||||
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
|
||||
if (_rwlockStates.Remove(resolvedAddress, out var existing))
|
||||
{
|
||||
existing.Dispose();
|
||||
}
|
||||
|
||||
_rwlockStates[rwlockAddress] = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
var rwlock = new PthreadRwlockState();
|
||||
_rwlockStates[rwlockAddress] = rwlock;
|
||||
_rwlockStates[syntheticHandle] = rwlock;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ytQULN-nhL4",
|
||||
ExportName = "pthread_rwlock_init",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadRwlockInit(CpuContext ctx) => PthreadRwlockInit(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "BB+kb08Tl9A",
|
||||
ExportName = "scePthreadRwlockDestroy",
|
||||
@@ -618,10 +758,11 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ReaderWriterLockSlim? state;
|
||||
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
|
||||
PthreadRwlockState? state;
|
||||
lock (_stateGate)
|
||||
{
|
||||
_rwlockStates.Remove(rwlockAddress, out state);
|
||||
_rwlockStates.TryGetValue(resolvedAddress, out state);
|
||||
}
|
||||
|
||||
if (state is null)
|
||||
@@ -629,24 +770,62 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
state.Dispose();
|
||||
lock (state.SyncRoot)
|
||||
{
|
||||
if (state.WriterThreadId != 0 || state.ReaderTotalCount != 0 || state.WaitingWriters != 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_rwlockStates.Remove(resolvedAddress);
|
||||
if (resolvedAddress != rwlockAddress)
|
||||
{
|
||||
_rwlockStates.Remove(rwlockAddress);
|
||||
}
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(rwlockAddress, 0);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "1471ajPzxh0",
|
||||
ExportName = "pthread_rwlock_destroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadRwlockDestroy(CpuContext ctx) => PthreadRwlockDestroy(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Ox9i0c7L5w0",
|
||||
ExportName = "scePthreadRwlockRdlock",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: false);
|
||||
public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "iGjsr1WAtI0",
|
||||
ExportName = "pthread_rwlock_rdlock",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockRdlock(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "mqdNorrB+gI",
|
||||
ExportName = "scePthreadRwlockWrlock",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: true);
|
||||
public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: true);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "sIlRvQqsN2Y",
|
||||
ExportName = "pthread_rwlock_wrlock",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockWrlock(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "+L98PIbGttk",
|
||||
@@ -661,40 +840,50 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ReaderWriterLockSlim? rwlock;
|
||||
lock (_stateGate)
|
||||
{
|
||||
_rwlockStates.TryGetValue(rwlockAddress, out rwlock);
|
||||
}
|
||||
|
||||
if (rwlock is null)
|
||||
if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: false, out _, out var rwlock))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
|
||||
|
||||
try
|
||||
{
|
||||
if (rwlock.IsWriteLockHeld)
|
||||
lock (rwlock.SyncRoot)
|
||||
{
|
||||
rwlock.ExitWriteLock();
|
||||
}
|
||||
else if (rwlock.IsReadLockHeld)
|
||||
{
|
||||
rwlock.ExitReadLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
if (rwlock.WriterThreadId == currentThreadId)
|
||||
{
|
||||
rwlock.WriterThreadId = 0;
|
||||
Monitor.PulseAll(rwlock.SyncRoot);
|
||||
}
|
||||
else if (rwlock.RemoveReader(currentThreadId))
|
||||
{
|
||||
if (rwlock.ReaderTotalCount == 0 || rwlock.WaitingWriters > 0)
|
||||
{
|
||||
Monitor.PulseAll(rwlock.SyncRoot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SynchronizationLockException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "EgmLo6EWgso",
|
||||
ExportName = "pthread_rwlock_unlock",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PosixPthreadRwlockUnlock(CpuContext ctx) => PthreadRwlockUnlock(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "mqULNdimTn0",
|
||||
ExportName = "pthread_key_create",
|
||||
@@ -730,6 +919,13 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "geDaqgH9lTg",
|
||||
ExportName = "scePthreadKeyCreate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int OrbisPthreadKeyCreate(CpuContext ctx) => PosixPthreadKeyCreate(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "6BpEZuDT7YI",
|
||||
ExportName = "pthread_key_delete",
|
||||
@@ -744,13 +940,24 @@ public static class KernelPthreadExtendedCompatExports
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
foreach (var entry in _threadLocalSpecific)
|
||||
{
|
||||
entry.Value.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
_threadLocalSpecific?.Remove(key);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "PrdHuuDekhY",
|
||||
ExportName = "scePthreadKeyDelete",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int OrbisPthreadKeyDelete(CpuContext ctx) => PosixPthreadKeyDelete(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "WrOLvHU0yQM",
|
||||
ExportName = "pthread_setspecific",
|
||||
@@ -760,20 +967,34 @@ public static class KernelPthreadExtendedCompatExports
|
||||
{
|
||||
var key = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var value = ctx[CpuRegister.Rsi];
|
||||
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_tlsKeys.TryGetValue(key, out _))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values))
|
||||
{
|
||||
values = new Dictionary<int, ulong>();
|
||||
_threadLocalSpecific[currentThreadHandle] = values;
|
||||
}
|
||||
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
_threadLocalSpecific ??= new Dictionary<int, ulong>();
|
||||
_threadLocalSpecific[key] = value;
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "+BzXYkqYeLE",
|
||||
ExportName = "scePthreadSetspecific",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int OrbisPthreadSetspecific(CpuContext ctx) => PosixPthreadSetspecific(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "0-KXaS70xy4",
|
||||
ExportName = "pthread_getspecific",
|
||||
@@ -782,6 +1003,8 @@ public static class KernelPthreadExtendedCompatExports
|
||||
public static int PosixPthreadGetspecific(CpuContext ctx)
|
||||
{
|
||||
var key = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
|
||||
ulong value = 0;
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_tlsKeys.TryGetValue(key, out _))
|
||||
@@ -789,51 +1012,176 @@ public static class KernelPthreadExtendedCompatExports
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values) &&
|
||||
values.TryGetValue(key, out var storedValue))
|
||||
{
|
||||
value = storedValue;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] =
|
||||
_threadLocalSpecific is not null && _threadLocalSpecific.TryGetValue(key, out var value)
|
||||
? value
|
||||
: 0UL;
|
||||
ctx[CpuRegister.Rax] = value;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int PthreadRwlockLockCore(ulong rwlockAddress, bool write)
|
||||
[SysAbiExport(
|
||||
Nid = "eoht7mQOCmo",
|
||||
ExportName = "scePthreadGetspecific",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int OrbisPthreadGetspecific(CpuContext ctx) => PosixPthreadGetspecific(ctx);
|
||||
|
||||
private static int PthreadRwlockLockCore(CpuContext ctx, ulong rwlockAddress, bool write)
|
||||
{
|
||||
if (rwlockAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ReaderWriterLockSlim rwlock;
|
||||
lock (_stateGate)
|
||||
if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
|
||||
{
|
||||
if (!_rwlockStates.TryGetValue(rwlockAddress, out rwlock!))
|
||||
{
|
||||
rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
_rwlockStates[rwlockAddress] = rwlock;
|
||||
}
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
try
|
||||
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
|
||||
lock (rwlock.SyncRoot)
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
rwlock.EnterWriteLock();
|
||||
if (rwlock.WriterThreadId == currentThreadId || rwlock.GetReaderCount(currentThreadId) > 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
|
||||
}
|
||||
|
||||
rwlock.WaitingWriters++;
|
||||
try
|
||||
{
|
||||
while (rwlock.WriterThreadId != 0 || rwlock.ReaderTotalCount != 0)
|
||||
{
|
||||
Monitor.Wait(rwlock.SyncRoot);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwlock.WaitingWriters--;
|
||||
}
|
||||
|
||||
rwlock.WriterThreadId = currentThreadId;
|
||||
}
|
||||
else
|
||||
{
|
||||
rwlock.EnterReadLock();
|
||||
if (rwlock.WriterThreadId == currentThreadId)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
|
||||
}
|
||||
|
||||
while (rwlock.WriterThreadId != 0 ||
|
||||
(rwlock.WaitingWriters > 0 && rwlock.GetReaderCount(currentThreadId) == 0))
|
||||
{
|
||||
Monitor.Wait(rwlock.SyncRoot);
|
||||
}
|
||||
|
||||
rwlock.AddReader(currentThreadId);
|
||||
}
|
||||
}
|
||||
catch (LockRecursionException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static ulong ResolveRwlockHandle(CpuContext ctx, ulong rwlockAddress)
|
||||
{
|
||||
if (rwlockAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_rwlockStates.ContainsKey(rwlockAddress))
|
||||
{
|
||||
return rwlockAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(rwlockAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_rwlockStates.ContainsKey(pointedHandle))
|
||||
{
|
||||
return pointedHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rwlockAddress;
|
||||
}
|
||||
|
||||
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadRwlockState? rwlock)
|
||||
{
|
||||
resolvedAddress = 0;
|
||||
rwlock = null;
|
||||
if (rwlockAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_rwlockStates.TryGetValue(rwlockAddress, out rwlock))
|
||||
{
|
||||
resolvedAddress = rwlockAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.TryReadUInt64(rwlockAddress, out var pointedHandle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_rwlockStates.TryGetValue(pointedHandle, out rwlock))
|
||||
{
|
||||
_rwlockStates[rwlockAddress] = rwlock;
|
||||
resolvedAddress = pointedHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
resolvedAddress = pointedHandle;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!createIfZero)
|
||||
{
|
||||
resolvedAddress = rwlockAddress;
|
||||
return false;
|
||||
}
|
||||
|
||||
var createdRwlock = new PthreadRwlockState();
|
||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
|
||||
lock (_stateGate)
|
||||
{
|
||||
_rwlockStates[rwlockAddress] = createdRwlock;
|
||||
_rwlockStates[syntheticHandle] = createdRwlock;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
rwlock = createdRwlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
|
||||
{
|
||||
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
|
||||
return baseAddress + (id << 4);
|
||||
}
|
||||
|
||||
private static ThreadState GetOrCreateThreadStateLocked(ulong thread)
|
||||
{
|
||||
if (_threadStates.TryGetValue(thread, out var state))
|
||||
@@ -841,9 +1189,13 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return state;
|
||||
}
|
||||
|
||||
var name = KernelPthreadState.TryGetThreadIdentity(thread, out var identity)
|
||||
? identity.Name
|
||||
: $"Thread-{thread:X}";
|
||||
|
||||
state = new ThreadState
|
||||
{
|
||||
Name = $"Thread-{thread:X}",
|
||||
Name = name,
|
||||
Priority = DefaultThreadPriority,
|
||||
AffinityMask = DefaultThreadAffinityMask,
|
||||
DetachState = DefaultDetachState,
|
||||
@@ -865,6 +1217,35 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return state;
|
||||
}
|
||||
|
||||
private static ulong ResolvePthreadAttrHandle(CpuContext ctx, ulong attrAddress)
|
||||
{
|
||||
if (attrAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_attrStates.ContainsKey(attrAddress))
|
||||
{
|
||||
return attrAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_attrStates.ContainsKey(pointedHandle))
|
||||
{
|
||||
return pointedHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attrAddress;
|
||||
}
|
||||
|
||||
private static bool TryWriteFixedUtf8CString(CpuContext ctx, ulong address, string value, int maxBytes)
|
||||
{
|
||||
if (maxBytes <= 0)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
internal static class KernelPthreadState
|
||||
{
|
||||
private const int ThreadObjectSize = 0x1000;
|
||||
|
||||
private static readonly object Gate = new();
|
||||
private static readonly Dictionary<ulong, ThreadIdentity> Threads = new();
|
||||
private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize];
|
||||
private static long _nextUniqueThreadId = 1;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _currentThreadHandle;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong _currentThreadUniqueId;
|
||||
|
||||
internal readonly record struct ThreadIdentity(ulong UniqueId, string Name);
|
||||
|
||||
internal static ulong GetCurrentThreadHandle()
|
||||
{
|
||||
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
|
||||
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out _))
|
||||
{
|
||||
return guestThreadHandle;
|
||||
}
|
||||
|
||||
EnsureCurrentThreadRegistered();
|
||||
return _currentThreadHandle;
|
||||
}
|
||||
|
||||
internal static ulong GetCurrentThreadUniqueId()
|
||||
{
|
||||
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
|
||||
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out var identity))
|
||||
{
|
||||
return identity.UniqueId;
|
||||
}
|
||||
|
||||
EnsureCurrentThreadRegistered();
|
||||
return _currentThreadUniqueId;
|
||||
}
|
||||
|
||||
internal static ulong CreateThreadHandle(string name)
|
||||
{
|
||||
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
|
||||
return AllocateThreadHandle(uniqueId, name);
|
||||
}
|
||||
|
||||
internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity)
|
||||
{
|
||||
lock (Gate)
|
||||
{
|
||||
return Threads.TryGetValue(threadHandle, out identity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureCurrentThreadRegistered()
|
||||
{
|
||||
if (_currentThreadHandle != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
|
||||
var name = $"Thread-{uniqueId:X}";
|
||||
_currentThreadHandle = AllocateThreadHandle(uniqueId, name);
|
||||
_currentThreadUniqueId = uniqueId;
|
||||
}
|
||||
|
||||
private static ulong AllocateThreadHandle(ulong uniqueId, string name)
|
||||
{
|
||||
var pointer = Marshal.AllocHGlobal(ThreadObjectSize);
|
||||
Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize);
|
||||
|
||||
var handle = unchecked((ulong)pointer.ToInt64());
|
||||
lock (Gate)
|
||||
{
|
||||
Threads[handle] = new ThreadIdentity(uniqueId, string.IsNullOrWhiteSpace(name) ? $"Thread-{uniqueId:X}" : name);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public static class KernelRuntimeCompatExports
|
||||
private const ulong DefaultKernelTscFrequency = 10_000_000UL;
|
||||
private const ulong PrtAreaStartAddress = 0x0000001000000000UL;
|
||||
private const ulong PrtAreaSize = 0x000000EC00000000UL;
|
||||
private const int AioInitParamSize = 0x3C;
|
||||
private const uint MemCommit = 0x1000;
|
||||
private const uint MemReserve = 0x2000;
|
||||
private const uint PageExecuteReadWrite = 0x40;
|
||||
@@ -39,7 +40,10 @@ public static class KernelRuntimeCompatExports
|
||||
private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader();
|
||||
private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency();
|
||||
private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
|
||||
private static readonly nint _stackChkGuardObjectAddress = AllocateStackChkGuardObject();
|
||||
private static readonly nint _stackChkGuardObjectAddress =
|
||||
HleDataSymbols.TryGetAddress("f7uOxY9mM1U", out var stackChkGuardAddress)
|
||||
? unchecked((nint)stackChkGuardAddress)
|
||||
: AllocateStackChkGuardObject();
|
||||
private static ulong _applicationHeapApiAddress;
|
||||
private static ulong _processProcParamAddress;
|
||||
private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL;
|
||||
@@ -254,6 +258,8 @@ public static class KernelRuntimeCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
TraceProcParam(ctx, address);
|
||||
|
||||
ctx[CpuRegister.Rax] = address;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -266,6 +272,223 @@ public static class KernelRuntimeCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceProcParam(CpuContext ctx, ulong address)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][TRACE] proc_param: address=0");
|
||||
return;
|
||||
}
|
||||
|
||||
const int dumpSize = 0x200;
|
||||
var buffer = GC.AllocateUninitializedArray<byte>(dumpSize);
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} unreadable");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} size=0x{dumpSize:X}");
|
||||
for (var offset = 0; offset < dumpSize; offset += 16)
|
||||
{
|
||||
var slice = buffer.AsSpan(offset, 16);
|
||||
var hex = Convert.ToHexString(slice);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param[{offset:X3}]: {hex}");
|
||||
}
|
||||
|
||||
TraceProcParamPointers(ctx, address, buffer);
|
||||
}
|
||||
|
||||
private static void TraceProcParamPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM_PTRS"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.Length < 0x50)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var offset = 0x20; offset <= 0x48; offset += 8)
|
||||
{
|
||||
var ptr = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(offset, 8));
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr@{offset:X2}: 0x{ptr:X16}");
|
||||
if (ptr == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TraceProcParamPointerTarget(ctx, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceProcParamPointerTarget(CpuContext ctx, ulong address)
|
||||
{
|
||||
const int maxAsciiBytes = 256;
|
||||
const int maxWideChars = 128;
|
||||
|
||||
if (TryReadUtf8CString(ctx, address, maxAsciiBytes, out var asciiValue))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target ascii@0x{address:X16}: \"{asciiValue}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryReadUtf16CString(ctx, address, maxWideChars, out var wideValue))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target wide@0x{address:X16}: \"{wideValue}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
var preview = GC.AllocateUninitializedArray<byte>(64);
|
||||
if (ctx.Memory.TryRead(address, preview))
|
||||
{
|
||||
var hex = Convert.ToHexString(preview);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target hex@0x{address:X16}: {hex}");
|
||||
TraceProcParamEmbeddedPointers(ctx, address, preview);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target unreadable@0x{address:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryReadUtf8CString(CpuContext ctx, ulong address, int maxBytes, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = Array.IndexOf(buffer, (byte)0);
|
||||
if (length < 0)
|
||||
{
|
||||
length = maxBytes;
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var text = Encoding.UTF8.GetString(buffer, 0, length);
|
||||
if (!IsMostlyPrintable(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadUtf16CString(CpuContext ctx, ulong address, int maxChars, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
var maxBytes = maxChars * 2;
|
||||
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lengthBytes = -1;
|
||||
for (var i = 0; i + 1 < buffer.Length; i += 2)
|
||||
{
|
||||
if (buffer[i] == 0 && buffer[i + 1] == 0)
|
||||
{
|
||||
lengthBytes = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lengthBytes <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var text = Encoding.Unicode.GetString(buffer, 0, lengthBytes);
|
||||
if (!IsMostlyPrintable(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsMostlyPrintable(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var printable = 0;
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (ch == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!char.IsControl(ch) || ch == '\r' || ch == '\n' || ch == '\t')
|
||||
{
|
||||
printable++;
|
||||
}
|
||||
}
|
||||
|
||||
return printable >= Math.Max(4, text.Length * 3 / 4);
|
||||
}
|
||||
|
||||
private static void TraceProcParamEmbeddedPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> data)
|
||||
{
|
||||
const int maxCandidates = 12;
|
||||
var found = 0;
|
||||
Span<byte> probe = stackalloc byte[2];
|
||||
|
||||
for (var offset = 0; offset + 8 <= data.Length; offset += 8)
|
||||
{
|
||||
var candidate = BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(offset, 8));
|
||||
if (candidate == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ctx.Memory.TryRead(candidate, probe))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryReadUtf8CString(ctx, candidate, 256, out var ascii))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} ascii \"{ascii}\"");
|
||||
if (++found >= maxCandidates)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryReadUtf16CString(ctx, candidate, 128, out var wide))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} wide \"{wide}\"");
|
||||
if (++found >= maxCandidates)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "9BcDykPmo1I",
|
||||
ExportName = "__error",
|
||||
@@ -777,6 +1000,48 @@ public static class KernelRuntimeCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "nu4a0-arQis",
|
||||
ExportName = "sceKernelAioInitializeParam",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAioInitializeParam(CpuContext ctx)
|
||||
{
|
||||
var paramAddress = ctx[CpuRegister.Rdi];
|
||||
if (paramAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Span<byte> zero = stackalloc byte[AioInitParamSize];
|
||||
zero.Clear();
|
||||
if (!ctx.Memory.TryWrite(paramAddress, zero))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vYU8P9Td2Zo",
|
||||
ExportName = "sceKernelAioInitializeImpl",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAioInitializeImpl(CpuContext ctx)
|
||||
{
|
||||
var paramAddress = ctx[CpuRegister.Rdi];
|
||||
var size = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
if (paramAddress == 0 || size < AioInitParamSize)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "eR2bZFAAU0Q",
|
||||
ExportName = "sceSysmoduleUnloadModule",
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelSemaphoreCompatExports
|
||||
{
|
||||
private const int MaxSemaphoreNameLength = 128;
|
||||
private static readonly ConcurrentDictionary<uint, KernelSemaphoreState> _semaphores = new();
|
||||
private static int _nextSemaphoreHandle = 1;
|
||||
|
||||
private sealed class KernelSemaphoreState
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required int InitialCount { get; init; }
|
||||
public required int MaxCount { get; init; }
|
||||
public int Count { get; set; }
|
||||
public int WaitingThreads { get; set; }
|
||||
public object Gate { get; } = new();
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "188x57JYp0g",
|
||||
ExportName = "sceKernelCreateSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCreateSema(CpuContext ctx)
|
||||
{
|
||||
var semaphoreAddress = ctx[CpuRegister.Rdi];
|
||||
var nameAddress = ctx[CpuRegister.Rsi];
|
||||
var attr = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var initialCount = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
var maxCount = unchecked((int)ctx[CpuRegister.R8]);
|
||||
var optionAddress = ctx[CpuRegister.R9];
|
||||
|
||||
if (semaphoreAddress == 0 ||
|
||||
nameAddress == 0 ||
|
||||
attr > 2 ||
|
||||
initialCount < 0 ||
|
||||
maxCount <= 0 ||
|
||||
initialCount > maxCount ||
|
||||
optionAddress != 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxSemaphoreNameLength, out var name))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
|
||||
if (handle == 0)
|
||||
{
|
||||
handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
|
||||
}
|
||||
|
||||
_semaphores[handle] = new KernelSemaphoreState
|
||||
{
|
||||
Name = name,
|
||||
InitialCount = initialCount,
|
||||
MaxCount = maxCount,
|
||||
Count = initialCount,
|
||||
};
|
||||
|
||||
if (!TryWriteUInt32(ctx, semaphoreAddress, handle))
|
||||
{
|
||||
_semaphores.TryRemove(handle, out _);
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceSemaphore($"create handle=0x{handle:X8} name='{name}' attr=0x{attr:X} init={initialCount} max={maxCount}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Zxa0VhQVTsk",
|
||||
ExportName = "sceKernelWaitSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelWaitSema(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var timeoutAddress = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (needCount < 1 || needCount > semaphore.MaxCount)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
if (semaphore.Count >= needCount)
|
||||
{
|
||||
semaphore.Count -= needCount;
|
||||
TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
if (timeoutAddress != 0)
|
||||
{
|
||||
if (!TryReadUInt32(ctx, timeoutAddress, out _))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||
TraceSemaphore($"wait-timeout handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||
}
|
||||
|
||||
if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitSema"))
|
||||
{
|
||||
TraceSemaphore($"wait-would-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||
}
|
||||
|
||||
semaphore.WaitingThreads++;
|
||||
TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "12wOHk8ywb0",
|
||||
ExportName = "sceKernelPollSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelPollSema(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
|
||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (needCount < 1 || needCount > semaphore.MaxCount)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
if (semaphore.Count < needCount)
|
||||
{
|
||||
TraceSemaphore($"poll-busy handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
|
||||
}
|
||||
|
||||
semaphore.Count -= needCount;
|
||||
TraceSemaphore($"poll handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4czppHBiriw",
|
||||
ExportName = "sceKernelSignalSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelSignalSema(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var signalCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
|
||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (signalCount <= 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
if (semaphore.Count > semaphore.MaxCount - signalCount)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
semaphore.Count += signalCount;
|
||||
TraceSemaphore($"signal handle=0x{handle:X8} name='{semaphore.Name}' signal={signalCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4DM06U2BNEY",
|
||||
ExportName = "sceKernelCancelSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelCancelSema(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var setCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var waitingThreadsAddress = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (setCount > semaphore.MaxCount)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
if (waitingThreadsAddress != 0 && !TryWriteUInt32(ctx, waitingThreadsAddress, unchecked((uint)semaphore.WaitingThreads)))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
|
||||
semaphore.WaitingThreads = 0;
|
||||
TraceSemaphore($"cancel handle=0x{handle:X8} name='{semaphore.Name}' set={setCount} count={semaphore.Count}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "R1Jvn8bSCW8",
|
||||
ExportName = "sceKernelDeleteSema",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteSema(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
if (!_semaphores.TryRemove(handle, out var semaphore))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
TraceSemaphore($"delete handle=0x{handle:X8} name='{semaphore.Name}'");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
|
||||
{
|
||||
var value = (int)result;
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int maxLength, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
if (address == 0 || maxLength <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bytes = new byte[Math.Min(maxLength, 4096)];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
Span<byte> current = stackalloc byte[1];
|
||||
if (!ctx.Memory.TryRead(address + (ulong)i, current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current[0] == 0)
|
||||
{
|
||||
value = Encoding.UTF8.GetString(bytes, 0, i);
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes[i] = current[0];
|
||||
}
|
||||
|
||||
value = Encoding.UTF8.GetString(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TraceSemaphore(string message)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SEMA"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] sema.{message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.LibcInternal;
|
||||
|
||||
public static class LibcInternalExports
|
||||
{
|
||||
private const ulong HeapTraceInfoSize = 32;
|
||||
private const int HeapTraceTableEntryCount = 64;
|
||||
private const int HeapTraceMaskOffset = 0;
|
||||
private const int HeapTraceTableOffset = HeapTraceMaskOffset + sizeof(ulong);
|
||||
private const int HeapTraceStorageSize = HeapTraceTableOffset + (HeapTraceTableEntryCount * sizeof(ulong));
|
||||
|
||||
private static readonly object _heapTraceGate = new();
|
||||
private static nint _heapTraceStorage;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "NWtTN10cJzE",
|
||||
ExportName = "LibcHeapGetTraceInfo",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "LibcInternalExt")]
|
||||
public static int LibcHeapGetTraceInfo(CpuContext ctx)
|
||||
{
|
||||
var infoAddress = ctx[CpuRegister.Rdi];
|
||||
if (infoAddress == 0 || !ctx.TryReadUInt64(infoAddress, out var size) || size != HeapTraceInfoSize)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var storage = EnsureHeapTraceStorage();
|
||||
if (storage == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var maskAddress = unchecked((ulong)(storage + HeapTraceMaskOffset));
|
||||
var tableAddress = unchecked((ulong)(storage + HeapTraceTableOffset));
|
||||
if (!ctx.TryWriteUInt64(infoAddress + 16, maskAddress) ||
|
||||
!ctx.TryWriteUInt64(infoAddress + 24, tableAddress))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static nint EnsureHeapTraceStorage()
|
||||
{
|
||||
lock (_heapTraceGate)
|
||||
{
|
||||
if (_heapTraceStorage != 0)
|
||||
{
|
||||
return _heapTraceStorage;
|
||||
}
|
||||
|
||||
var storage = Marshal.AllocHGlobal(HeapTraceStorageSize);
|
||||
if (storage == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
NativeMemory.Clear((void*)storage, (nuint)HeapTraceStorageSize);
|
||||
}
|
||||
|
||||
_heapTraceStorage = storage;
|
||||
return storage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SharpEmu.Libs.Pad;
|
||||
|
||||
public static class PadExports
|
||||
{
|
||||
private const int OrbisPadErrorInvalidHandle = unchecked((int)0x80920003);
|
||||
private const int OrbisPadErrorNotInitialized = unchecked((int)0x80920005);
|
||||
private const int OrbisPadErrorDeviceNotConnected = unchecked((int)0x80920007);
|
||||
private const int OrbisPadErrorDeviceNoHandle = unchecked((int)0x80920008);
|
||||
private const int PrimaryUserId = 1;
|
||||
private const int StandardPortType = 0;
|
||||
private const int PrimaryPadHandle = 1;
|
||||
private const int ControllerInformationSize = 0x1C;
|
||||
private const int PadDataSize = 0x78;
|
||||
|
||||
private static bool _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hv1luiJrqQM",
|
||||
ExportName = "scePadInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadInit(CpuContext ctx)
|
||||
{
|
||||
_initialized = true;
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "xk0AcarP3V4",
|
||||
ExportName = "scePadOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadOpen(CpuContext ctx)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var type = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var index = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||
var parameterAddress = ctx[CpuRegister.Rcx];
|
||||
if (!_initialized)
|
||||
{
|
||||
return SetReturn(ctx, OrbisPadErrorNotInitialized);
|
||||
}
|
||||
|
||||
if (userId == -1)
|
||||
{
|
||||
return SetReturn(ctx, OrbisPadErrorDeviceNoHandle);
|
||||
}
|
||||
|
||||
if (userId != PrimaryUserId || type != StandardPortType || index != 0 || parameterAddress != 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisPadErrorDeviceNotConnected);
|
||||
}
|
||||
|
||||
return SetReturn(ctx, PrimaryPadHandle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "clVvL4ZDntw",
|
||||
ExportName = "scePadSetMotionSensorState",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadSetMotionSensorState(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
return handle == PrimaryPadHandle
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "gjP9-KQzoUk",
|
||||
ExportName = "scePadGetControllerInformation",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadGetControllerInformation(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var informationAddress = ctx[CpuRegister.Rsi];
|
||||
if (handle != PrimaryPadHandle)
|
||||
{
|
||||
return SetReturn(ctx, OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
if (informationAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
Span<byte> information = stackalloc byte[ControllerInformationSize];
|
||||
BinaryPrimitives.WriteSingleLittleEndian(information[0x00..], 44.86f);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(information[0x04..], 1920);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(information[0x06..], 943);
|
||||
information[0x08] = 30;
|
||||
information[0x09] = 30;
|
||||
information[0x0A] = StandardPortType;
|
||||
information[0x0B] = 1;
|
||||
information[0x0C] = 1;
|
||||
BinaryPrimitives.WriteInt32LittleEndian(information[0x10..], 0);
|
||||
|
||||
return ctx.Memory.TryWrite(informationAddress, information)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YndgXqQVV7c",
|
||||
ExportName = "scePadReadState",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadReadState(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var dataAddress = ctx[CpuRegister.Rsi];
|
||||
if (handle != PrimaryPadHandle)
|
||||
{
|
||||
return SetReturn(ctx, OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
if (dataAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
Span<byte> data = stackalloc byte[PadDataSize];
|
||||
data.Clear();
|
||||
data[0x04] = 128;
|
||||
data[0x05] = 128;
|
||||
data[0x06] = 128;
|
||||
data[0x07] = 128;
|
||||
BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f);
|
||||
data[0x4C] = 1;
|
||||
var timestampTicks = Stopwatch.GetTimestamp();
|
||||
var timestampMicroseconds =
|
||||
((ulong)(timestampTicks / Stopwatch.Frequency) * 1_000_000UL) +
|
||||
((ulong)(timestampTicks % Stopwatch.Frequency) * 1_000_000UL / (ulong)Stopwatch.Frequency);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(
|
||||
data[0x50..],
|
||||
timestampMicroseconds);
|
||||
data[0x68] = 1;
|
||||
|
||||
return ctx.Memory.TryWrite(dataAddress, data)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpEmu.Libs.PlayGo;
|
||||
|
||||
public static class PlayGoExports
|
||||
{
|
||||
private const int OrbisPlayGoErrorInvalidArgument = unchecked((int)0x80B20004);
|
||||
private const int OrbisPlayGoErrorNotInitialized = unchecked((int)0x80B20005);
|
||||
private const int OrbisPlayGoErrorAlreadyInitialized = unchecked((int)0x80B20006);
|
||||
private const int OrbisPlayGoErrorBadHandle = unchecked((int)0x80B20009);
|
||||
private const int OrbisPlayGoErrorBadPointer = unchecked((int)0x80B2000A);
|
||||
private const int OrbisPlayGoErrorBadSize = unchecked((int)0x80B2000B);
|
||||
private const int OrbisPlayGoErrorNotSupportPlayGo = unchecked((int)0x80B2000E);
|
||||
private const ulong PlayGoInitBufAddrOffset = 0;
|
||||
private const ulong PlayGoInitBufSizeOffset = 8;
|
||||
private const uint PlayGoMinimumInitBufferSize = 0x200000;
|
||||
private const uint PlayGoHandle = 1;
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
private static bool _initialized;
|
||||
private static bool _hasPlayGoData;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ts6GlZOKRrE",
|
||||
ExportName = "scePlayGoInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoInitialize(CpuContext ctx)
|
||||
{
|
||||
var initParamsAddress = ctx[CpuRegister.Rdi];
|
||||
if (initParamsAddress == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (!ctx.TryReadUInt64(initParamsAddress + PlayGoInitBufAddrOffset, out var bufferAddress))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
Span<byte> bufferSizeBytes = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(initParamsAddress + PlayGoInitBufSizeOffset, bufferSizeBytes))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var bufferSize = BinaryPrimitives.ReadUInt32LittleEndian(bufferSizeBytes);
|
||||
|
||||
if (bufferAddress == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (bufferSize < PlayGoMinimumInitBufferSize)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return OrbisPlayGoErrorAlreadyInitialized;
|
||||
}
|
||||
|
||||
_hasPlayGoData = HasPlayGoChunkData();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "M1Gma1ocrGE",
|
||||
ExportName = "scePlayGoOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoOpen(CpuContext ctx)
|
||||
{
|
||||
var outHandleAddress = ctx[CpuRegister.Rdi];
|
||||
var paramAddress = ctx[CpuRegister.Rsi];
|
||||
if (outHandleAddress == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (paramAddress != 0)
|
||||
{
|
||||
return OrbisPlayGoErrorInvalidArgument;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return OrbisPlayGoErrorNotInitialized;
|
||||
}
|
||||
|
||||
if (!_hasPlayGoData)
|
||||
{
|
||||
return OrbisPlayGoErrorNotSupportPlayGo;
|
||||
}
|
||||
}
|
||||
|
||||
Span<byte> handleBytes = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(handleBytes, PlayGoHandle);
|
||||
if (!ctx.Memory.TryWrite(outHandleAddress, handleBytes))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "MPe0EeBGM-E",
|
||||
ExportName = "scePlayGoTerminate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoTerminate(CpuContext ctx)
|
||||
{
|
||||
_ = ctx;
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return OrbisPlayGoErrorNotInitialized;
|
||||
}
|
||||
|
||||
_initialized = false;
|
||||
_hasPlayGoData = false;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Uco1I0dlDi8",
|
||||
ExportName = "scePlayGoClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoClose(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return OrbisPlayGoErrorNotInitialized;
|
||||
}
|
||||
|
||||
if (handle != PlayGoHandle)
|
||||
{
|
||||
return OrbisPlayGoErrorBadHandle;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool HasPlayGoChunkData()
|
||||
{
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
if (string.IsNullOrWhiteSpace(app0Root))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostPath = Path.Combine(app0Root, "sce_sys", "playgo-chunk.dat");
|
||||
return File.Exists(hostPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpEmu.Libs.Rtc;
|
||||
|
||||
public static class RtcExports
|
||||
{
|
||||
[SysAbiExport(
|
||||
Nid = "ZPD1YOKI+Kw",
|
||||
ExportName = "sceRtcGetCurrentClockLocalTime",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceRtc")]
|
||||
public static int RtcGetCurrentClockLocalTime(CpuContext ctx)
|
||||
{
|
||||
var timeAddress = ctx[CpuRegister.Rdi];
|
||||
if (timeAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
Span<byte> rtcDateTime = stackalloc byte[16];
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)now.Year));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)now.Month));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)now.Day));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)now.Hour));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)now.Minute));
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)now.Second));
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(
|
||||
rtcDateTime[12..16],
|
||||
checked((uint)((now.Ticks % TimeSpan.TicksPerSecond) / 10)));
|
||||
|
||||
if (!ctx.Memory.TryWrite(timeAddress, rtcDateTime))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "8w-H19ip48I",
|
||||
ExportName = "sceRtcGetTick",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceRtc")]
|
||||
public static int RtcGetTick(CpuContext ctx)
|
||||
{
|
||||
var dateTimeAddress = ctx[CpuRegister.Rdi];
|
||||
var tickAddress = ctx[CpuRegister.Rsi];
|
||||
if (dateTimeAddress == 0 || tickAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryReadRtcDateTime(ctx, dateTimeAddress, out var rtcDateTime))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ulong tickValue;
|
||||
try
|
||||
{
|
||||
var baseDateTime = new DateTime(
|
||||
rtcDateTime.Year,
|
||||
rtcDateTime.Month,
|
||||
rtcDateTime.Day,
|
||||
rtcDateTime.Hour,
|
||||
rtcDateTime.Minute,
|
||||
rtcDateTime.Second,
|
||||
DateTimeKind.Utc);
|
||||
tickValue = checked((ulong)((baseDateTime.Ticks / 10) + rtcDateTime.Microsecond));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(tickAddress, tickValue))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime rtcDateTime)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[16];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
rtcDateTime = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
rtcDateTime = new RtcDateTime(
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[6..8]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[8..10]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(buffer[10..12]),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(buffer[12..16]));
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly record struct RtcDateTime(
|
||||
ushort Year,
|
||||
ushort Month,
|
||||
ushort Day,
|
||||
ushort Hour,
|
||||
ushort Minute,
|
||||
ushort Second,
|
||||
uint Microsecond);
|
||||
}
|
||||
@@ -8,6 +8,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="Silk.NET.Windowing" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpEmu.Libs.SystemService;
|
||||
|
||||
public static class SystemServiceExports
|
||||
{
|
||||
private const int OrbisSystemServiceErrorParameter = unchecked((int)0x80A10003);
|
||||
private const int SystemServiceStatusSize = 0x0C;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "rPo6tV8D9bM",
|
||||
ExportName = "sceSystemServiceGetStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSystemService")]
|
||||
public static int SystemServiceGetStatus(CpuContext ctx)
|
||||
{
|
||||
var statusAddress = ctx[CpuRegister.Rdi];
|
||||
if (statusAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisSystemServiceErrorParameter);
|
||||
}
|
||||
|
||||
Span<byte> status = stackalloc byte[SystemServiceStatusSize];
|
||||
status.Clear();
|
||||
BinaryPrimitives.WriteInt32LittleEndian(status, 0);
|
||||
status[0x06] = 1;
|
||||
|
||||
return ctx.Memory.TryWrite(statusAddress, status)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpEmu.Libs.UserService;
|
||||
|
||||
public static class UserServiceExports
|
||||
{
|
||||
private const int OrbisUserServiceErrorInvalidArgument = unchecked((int)0x80960005);
|
||||
private const int PrimaryUserId = 1;
|
||||
private const int InvalidUserId = -1;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "j3YMu1MVNNo",
|
||||
ExportName = "sceUserServiceInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceUserService")]
|
||||
public static int UserServiceInitialize(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "CdWp0oHWGr0",
|
||||
ExportName = "sceUserServiceGetInitialUser",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceUserService")]
|
||||
public static int UserServiceGetInitialUser(CpuContext ctx)
|
||||
{
|
||||
var userIdAddress = ctx[CpuRegister.Rdi];
|
||||
if (userIdAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
||||
}
|
||||
|
||||
return TryWriteInt32(ctx, userIdAddress, PrimaryUserId)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "fPhymKNvK-A",
|
||||
ExportName = "sceUserServiceGetLoginUserIdList",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceUserService")]
|
||||
public static int UserServiceGetLoginUserIdList(CpuContext ctx)
|
||||
{
|
||||
var userIdListAddress = ctx[CpuRegister.Rdi];
|
||||
if (userIdListAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
||||
}
|
||||
|
||||
Span<byte> userIds = stackalloc byte[sizeof(int) * 4];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x00..], PrimaryUserId);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x04..], InvalidUserId);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x08..], InvalidUserId);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x0C..], InvalidUserId);
|
||||
return ctx.Memory.TryWrite(userIdListAddress, userIds)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
|
||||
return ctx.Memory.TryWrite(address, bytes);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,702 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Core.Native;
|
||||
using Silk.NET.Maths;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using Silk.NET.Windowing;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkSemaphore = Silk.NET.Vulkan.Semaphore;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
private static readonly object _gate = new();
|
||||
private static Thread? _thread;
|
||||
private static byte[]? _latestFrame;
|
||||
private static uint _latestWidth;
|
||||
private static uint _latestHeight;
|
||||
private static long _latestSequence;
|
||||
private static bool _closed;
|
||||
|
||||
public static void Submit(byte[] bgraFrame, uint width, uint height)
|
||||
{
|
||||
if (bgraFrame.Length != checked((int)(width * height * 4)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_latestFrame = bgraFrame;
|
||||
_latestWidth = width;
|
||||
_latestHeight = height;
|
||||
_latestSequence++;
|
||||
if (_thread is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_thread = new Thread(Run)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "SharpEmu Vulkan VideoOut",
|
||||
};
|
||||
_thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Run()
|
||||
{
|
||||
uint width;
|
||||
uint height;
|
||||
lock (_gate)
|
||||
{
|
||||
width = _latestWidth;
|
||||
height = _latestHeight;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var presenter = new Presenter(width, height);
|
||||
presenter.Run();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][ERROR] Vulkan VideoOut presenter failed: {exception.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_closed = true;
|
||||
_thread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryTakeFrame(long presentedSequence, out Frame frame)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_latestFrame is null || _latestSequence == presentedSequence)
|
||||
{
|
||||
frame = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = new Frame(_latestFrame, _latestWidth, _latestHeight, _latestSequence);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct Frame(byte[] Pixels, uint Width, uint Height, long Sequence);
|
||||
|
||||
private sealed class Presenter : IDisposable
|
||||
{
|
||||
private readonly IWindow _window;
|
||||
private Vk _vk = null!;
|
||||
private KhrSurface _surfaceApi = null!;
|
||||
private KhrSwapchain _swapchainApi = null!;
|
||||
private Instance _instance;
|
||||
private SurfaceKHR _surface;
|
||||
private PhysicalDevice _physicalDevice;
|
||||
private Device _device;
|
||||
private Queue _queue;
|
||||
private uint _queueFamilyIndex;
|
||||
private SwapchainKHR _swapchain;
|
||||
private Image[] _swapchainImages = [];
|
||||
private bool[] _imageInitialized = [];
|
||||
private Format _swapchainFormat;
|
||||
private Extent2D _extent;
|
||||
private CommandPool _commandPool;
|
||||
private CommandBuffer _commandBuffer;
|
||||
private VkSemaphore _imageAvailable;
|
||||
private VkSemaphore _renderFinished;
|
||||
private VkBuffer _stagingBuffer;
|
||||
private DeviceMemory _stagingMemory;
|
||||
private ulong _stagingSize;
|
||||
private long _presentedSequence;
|
||||
private bool _vulkanReady;
|
||||
private bool _firstFramePresented;
|
||||
|
||||
public Presenter(uint width, uint height)
|
||||
{
|
||||
var options = WindowOptions.DefaultVulkan;
|
||||
options.Size = new Vector2D<int>((int)width, (int)height);
|
||||
options.Title = "SharpEmu VideoOut";
|
||||
options.WindowBorder = WindowBorder.Fixed;
|
||||
options.VSync = true;
|
||||
_window = Window.Create(options);
|
||||
_window.Load += Initialize;
|
||||
_window.Render += Render;
|
||||
_window.Closing += DisposeVulkan;
|
||||
}
|
||||
|
||||
public void Run() => _window.Run();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeVulkan();
|
||||
_window.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_vk = Vk.GetApi();
|
||||
CreateInstance();
|
||||
CreateSurface();
|
||||
SelectPhysicalDevice();
|
||||
CreateDevice();
|
||||
CreateSwapchain();
|
||||
CreateCommandResources();
|
||||
_vulkanReady = true;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan VideoOut ready: {_extent.Width}x{_extent.Height}, format={_swapchainFormat}");
|
||||
}
|
||||
|
||||
private void CreateInstance()
|
||||
{
|
||||
var applicationName = (byte*)SilkMarshal.StringToPtr("SharpEmu");
|
||||
try
|
||||
{
|
||||
var applicationInfo = new ApplicationInfo
|
||||
{
|
||||
SType = StructureType.ApplicationInfo,
|
||||
PApplicationName = applicationName,
|
||||
ApplicationVersion = Vk.MakeVersion(0, 0, 1),
|
||||
PEngineName = applicationName,
|
||||
EngineVersion = Vk.MakeVersion(0, 0, 1),
|
||||
ApiVersion = Vk.Version12,
|
||||
};
|
||||
|
||||
var extensions = _window.VkSurface!.GetRequiredExtensions(out var extensionCount);
|
||||
var createInfo = new InstanceCreateInfo
|
||||
{
|
||||
SType = StructureType.InstanceCreateInfo,
|
||||
PApplicationInfo = &applicationInfo,
|
||||
EnabledExtensionCount = extensionCount,
|
||||
PpEnabledExtensionNames = extensions,
|
||||
};
|
||||
|
||||
Check(_vk.CreateInstance(&createInfo, null, out _instance), "vkCreateInstance");
|
||||
if (!_vk.TryGetInstanceExtension(_instance, out _surfaceApi))
|
||||
{
|
||||
throw new InvalidOperationException("VK_KHR_surface is unavailable.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SilkMarshal.Free((nint)applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSurface()
|
||||
{
|
||||
var instanceHandle = new VkHandle(_instance.Handle);
|
||||
var surfaceHandle = _window.VkSurface!.Create<AllocationCallbacks>(instanceHandle, null);
|
||||
_surface = new SurfaceKHR(surfaceHandle.Handle);
|
||||
}
|
||||
|
||||
private void SelectPhysicalDevice()
|
||||
{
|
||||
uint deviceCount = 0;
|
||||
Check(_vk.EnumeratePhysicalDevices(_instance, &deviceCount, null), "vkEnumeratePhysicalDevices");
|
||||
if (deviceCount == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No Vulkan physical device was found.");
|
||||
}
|
||||
|
||||
var devices = new PhysicalDevice[deviceCount];
|
||||
fixed (PhysicalDevice* devicePointer = devices)
|
||||
{
|
||||
Check(_vk.EnumeratePhysicalDevices(_instance, &deviceCount, devicePointer), "vkEnumeratePhysicalDevices");
|
||||
}
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
uint queueCount = 0;
|
||||
_vk.GetPhysicalDeviceQueueFamilyProperties(device, &queueCount, null);
|
||||
var queues = new QueueFamilyProperties[queueCount];
|
||||
fixed (QueueFamilyProperties* queuePointer = queues)
|
||||
{
|
||||
_vk.GetPhysicalDeviceQueueFamilyProperties(device, &queueCount, queuePointer);
|
||||
}
|
||||
|
||||
for (uint index = 0; index < queueCount; index++)
|
||||
{
|
||||
var supportsGraphics = (queues[index].QueueFlags & QueueFlags.GraphicsBit) != 0;
|
||||
_surfaceApi.GetPhysicalDeviceSurfaceSupport(device, index, _surface, out var supportsPresent);
|
||||
if (!supportsGraphics || !supportsPresent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_physicalDevice = device;
|
||||
_queueFamilyIndex = index;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No Vulkan graphics/present queue was found.");
|
||||
}
|
||||
|
||||
private void CreateDevice()
|
||||
{
|
||||
var priority = 1.0f;
|
||||
var queueInfo = new DeviceQueueCreateInfo
|
||||
{
|
||||
SType = StructureType.DeviceQueueCreateInfo,
|
||||
QueueFamilyIndex = _queueFamilyIndex,
|
||||
QueueCount = 1,
|
||||
PQueuePriorities = &priority,
|
||||
};
|
||||
|
||||
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
|
||||
try
|
||||
{
|
||||
var createInfo = new DeviceCreateInfo
|
||||
{
|
||||
SType = StructureType.DeviceCreateInfo,
|
||||
QueueCreateInfoCount = 1,
|
||||
PQueueCreateInfos = &queueInfo,
|
||||
EnabledExtensionCount = 1,
|
||||
PpEnabledExtensionNames = &swapchainExtension,
|
||||
};
|
||||
|
||||
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SilkMarshal.Free((nint)swapchainExtension);
|
||||
}
|
||||
|
||||
_vk.GetDeviceQueue(_device, _queueFamilyIndex, 0, out _queue);
|
||||
if (!_vk.TryGetDeviceExtension(_instance, _device, out _swapchainApi))
|
||||
{
|
||||
throw new InvalidOperationException("VK_KHR_swapchain is unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSwapchain()
|
||||
{
|
||||
Check(
|
||||
_surfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities),
|
||||
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
|
||||
|
||||
uint formatCount = 0;
|
||||
Check(
|
||||
_surfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &formatCount, null),
|
||||
"vkGetPhysicalDeviceSurfaceFormatsKHR");
|
||||
var formats = new SurfaceFormatKHR[formatCount];
|
||||
fixed (SurfaceFormatKHR* formatPointer = formats)
|
||||
{
|
||||
Check(
|
||||
_surfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &formatCount, formatPointer),
|
||||
"vkGetPhysicalDeviceSurfaceFormatsKHR");
|
||||
}
|
||||
|
||||
var surfaceFormat = ChooseSurfaceFormat(formats);
|
||||
_swapchainFormat = surfaceFormat.Format;
|
||||
_extent = ChooseExtent(capabilities);
|
||||
var imageCount = capabilities.MinImageCount + 1;
|
||||
if (capabilities.MaxImageCount != 0)
|
||||
{
|
||||
imageCount = Math.Min(imageCount, capabilities.MaxImageCount);
|
||||
}
|
||||
|
||||
var compositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha);
|
||||
var createInfo = new SwapchainCreateInfoKHR
|
||||
{
|
||||
SType = StructureType.SwapchainCreateInfoKhr,
|
||||
Surface = _surface,
|
||||
MinImageCount = imageCount,
|
||||
ImageFormat = surfaceFormat.Format,
|
||||
ImageColorSpace = surfaceFormat.ColorSpace,
|
||||
ImageExtent = _extent,
|
||||
ImageArrayLayers = 1,
|
||||
ImageUsage = ImageUsageFlags.TransferDstBit,
|
||||
ImageSharingMode = SharingMode.Exclusive,
|
||||
PreTransform = capabilities.CurrentTransform,
|
||||
CompositeAlpha = compositeAlpha,
|
||||
PresentMode = PresentModeKHR.FifoKhr,
|
||||
Clipped = true,
|
||||
};
|
||||
|
||||
Check(_swapchainApi.CreateSwapchain(_device, &createInfo, null, out _swapchain), "vkCreateSwapchainKHR");
|
||||
|
||||
uint swapchainImageCount = 0;
|
||||
Check(
|
||||
_swapchainApi.GetSwapchainImages(_device, _swapchain, &swapchainImageCount, null),
|
||||
"vkGetSwapchainImagesKHR");
|
||||
_swapchainImages = new Image[swapchainImageCount];
|
||||
fixed (Image* imagePointer = _swapchainImages)
|
||||
{
|
||||
Check(
|
||||
_swapchainApi.GetSwapchainImages(_device, _swapchain, &swapchainImageCount, imagePointer),
|
||||
"vkGetSwapchainImagesKHR");
|
||||
}
|
||||
|
||||
_imageInitialized = new bool[swapchainImageCount];
|
||||
}
|
||||
|
||||
private void CreateCommandResources()
|
||||
{
|
||||
var poolInfo = new CommandPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
|
||||
QueueFamilyIndex = _queueFamilyIndex,
|
||||
};
|
||||
Check(_vk.CreateCommandPool(_device, &poolInfo, null, out _commandPool), "vkCreateCommandPool");
|
||||
|
||||
var allocateInfo = new CommandBufferAllocateInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferAllocateInfo,
|
||||
CommandPool = _commandPool,
|
||||
Level = CommandBufferLevel.Primary,
|
||||
CommandBufferCount = 1,
|
||||
};
|
||||
Check(_vk.AllocateCommandBuffers(_device, &allocateInfo, out _commandBuffer), "vkAllocateCommandBuffers");
|
||||
|
||||
var semaphoreInfo = new SemaphoreCreateInfo
|
||||
{
|
||||
SType = StructureType.SemaphoreCreateInfo,
|
||||
};
|
||||
Check(_vk.CreateSemaphore(_device, &semaphoreInfo, null, out _imageAvailable), "vkCreateSemaphore");
|
||||
Check(_vk.CreateSemaphore(_device, &semaphoreInfo, null, out _renderFinished), "vkCreateSemaphore");
|
||||
|
||||
CreateStagingBuffer((ulong)_extent.Width * _extent.Height * 4);
|
||||
}
|
||||
|
||||
private void CreateStagingBuffer(ulong size)
|
||||
{
|
||||
var bufferInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = size,
|
||||
Usage = BufferUsageFlags.TransferSrcBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
};
|
||||
Check(_vk.CreateBuffer(_device, &bufferInfo, null, out _stagingBuffer), "vkCreateBuffer");
|
||||
|
||||
_vk.GetBufferMemoryRequirements(_device, _stagingBuffer, out var requirements);
|
||||
var memoryInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = requirements.Size,
|
||||
MemoryTypeIndex = FindMemoryType(
|
||||
requirements.MemoryTypeBits,
|
||||
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit),
|
||||
};
|
||||
Check(_vk.AllocateMemory(_device, &memoryInfo, null, out _stagingMemory), "vkAllocateMemory");
|
||||
Check(_vk.BindBufferMemory(_device, _stagingBuffer, _stagingMemory, 0), "vkBindBufferMemory");
|
||||
_stagingSize = size;
|
||||
}
|
||||
|
||||
private uint FindMemoryType(uint typeBits, MemoryPropertyFlags requiredFlags)
|
||||
{
|
||||
_vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out var properties);
|
||||
var memoryTypes = &properties.MemoryTypes.Element0;
|
||||
for (uint index = 0; index < properties.MemoryTypeCount; index++)
|
||||
{
|
||||
if ((typeBits & (1u << (int)index)) != 0 &&
|
||||
(memoryTypes[index].PropertyFlags & requiredFlags) == requiredFlags)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No compatible Vulkan host-visible memory type was found.");
|
||||
}
|
||||
|
||||
private void Render(double _)
|
||||
{
|
||||
if (!_vulkanReady || !TryTakeFrame(_presentedSequence, out var frame))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pixels = frame.Width == _extent.Width && frame.Height == _extent.Height
|
||||
? frame.Pixels
|
||||
: ScaleBgra(frame.Pixels, frame.Width, frame.Height, _extent.Width, _extent.Height);
|
||||
if ((ulong)pixels.Length > _stagingSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint imageIndex;
|
||||
Check(
|
||||
_swapchainApi.AcquireNextImage(
|
||||
_device,
|
||||
_swapchain,
|
||||
ulong.MaxValue,
|
||||
_imageAvailable,
|
||||
default,
|
||||
&imageIndex),
|
||||
"vkAcquireNextImageKHR");
|
||||
|
||||
void* mapped;
|
||||
Check(
|
||||
_vk.MapMemory(_device, _stagingMemory, 0, (ulong)pixels.Length, 0, &mapped),
|
||||
"vkMapMemory");
|
||||
fixed (byte* source = pixels)
|
||||
{
|
||||
System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length);
|
||||
}
|
||||
_vk.UnmapMemory(_device, _stagingMemory);
|
||||
|
||||
Check(_vk.ResetCommandBuffer(_commandBuffer, 0), "vkResetCommandBuffer");
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
||||
};
|
||||
Check(_vk.BeginCommandBuffer(_commandBuffer, &beginInfo), "vkBeginCommandBuffer");
|
||||
|
||||
var oldLayout = _imageInitialized[imageIndex]
|
||||
? ImageLayout.PresentSrcKhr
|
||||
: ImageLayout.Undefined;
|
||||
var toTransfer = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = _imageInitialized[imageIndex] ? AccessFlags.MemoryReadBit : 0,
|
||||
DstAccessMask = AccessFlags.TransferWriteBit,
|
||||
OldLayout = oldLayout,
|
||||
NewLayout = ImageLayout.TransferDstOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = _swapchainImages[imageIndex],
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
_vk.CmdPipelineBarrier(
|
||||
_commandBuffer,
|
||||
_imageInitialized[imageIndex] ? PipelineStageFlags.BottomOfPipeBit : PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&toTransfer);
|
||||
|
||||
var copyRegion = new BufferImageCopy
|
||||
{
|
||||
ImageSubresource = new ImageSubresourceLayers
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
LayerCount = 1,
|
||||
},
|
||||
ImageExtent = new Extent3D(_extent.Width, _extent.Height, 1),
|
||||
};
|
||||
_vk.CmdCopyBufferToImage(
|
||||
_commandBuffer,
|
||||
_stagingBuffer,
|
||||
_swapchainImages[imageIndex],
|
||||
ImageLayout.TransferDstOptimal,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
var toPresent = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.TransferWriteBit,
|
||||
DstAccessMask = AccessFlags.MemoryReadBit,
|
||||
OldLayout = ImageLayout.TransferDstOptimal,
|
||||
NewLayout = ImageLayout.PresentSrcKhr,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = _swapchainImages[imageIndex],
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
_vk.CmdPipelineBarrier(
|
||||
_commandBuffer,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.BottomOfPipeBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&toPresent);
|
||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer");
|
||||
|
||||
var waitStage = PipelineStageFlags.TransferBit;
|
||||
var imageAvailable = _imageAvailable;
|
||||
var commandBuffer = _commandBuffer;
|
||||
var renderFinished = _renderFinished;
|
||||
var submitInfo = new SubmitInfo
|
||||
{
|
||||
SType = StructureType.SubmitInfo,
|
||||
WaitSemaphoreCount = 1,
|
||||
PWaitSemaphores = &imageAvailable,
|
||||
PWaitDstStageMask = &waitStage,
|
||||
CommandBufferCount = 1,
|
||||
PCommandBuffers = &commandBuffer,
|
||||
SignalSemaphoreCount = 1,
|
||||
PSignalSemaphores = &renderFinished,
|
||||
};
|
||||
Check(_vk.QueueSubmit(_queue, 1, &submitInfo, default), "vkQueueSubmit");
|
||||
|
||||
var swapchain = _swapchain;
|
||||
var presentInfo = new PresentInfoKHR
|
||||
{
|
||||
SType = StructureType.PresentInfoKhr,
|
||||
WaitSemaphoreCount = 1,
|
||||
PWaitSemaphores = &renderFinished,
|
||||
SwapchainCount = 1,
|
||||
PSwapchains = &swapchain,
|
||||
PImageIndices = &imageIndex,
|
||||
};
|
||||
Check(_swapchainApi.QueuePresent(_queue, &presentInfo), "vkQueuePresentKHR");
|
||||
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
|
||||
_imageInitialized[imageIndex] = true;
|
||||
_presentedSequence = frame.Sequence;
|
||||
if (!_firstFramePresented)
|
||||
{
|
||||
_firstFramePresented = true;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan VideoOut presented first guest frame: {frame.Width}x{frame.Height}");
|
||||
}
|
||||
}
|
||||
|
||||
private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities)
|
||||
{
|
||||
if (capabilities.CurrentExtent.Width != uint.MaxValue)
|
||||
{
|
||||
return capabilities.CurrentExtent;
|
||||
}
|
||||
|
||||
var size = _window.FramebufferSize;
|
||||
return new Extent2D(
|
||||
Math.Clamp((uint)Math.Max(size.X, 1), capabilities.MinImageExtent.Width, capabilities.MaxImageExtent.Width),
|
||||
Math.Clamp((uint)Math.Max(size.Y, 1), capabilities.MinImageExtent.Height, capabilities.MaxImageExtent.Height));
|
||||
}
|
||||
|
||||
private static SurfaceFormatKHR ChooseSurfaceFormat(IReadOnlyList<SurfaceFormatKHR> formats)
|
||||
{
|
||||
foreach (var format in formats)
|
||||
{
|
||||
if (format.Format is Format.B8G8R8A8Srgb or Format.B8G8R8A8Unorm &&
|
||||
format.ColorSpace == ColorSpaceKHR.SpaceSrgbNonlinearKhr)
|
||||
{
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
return formats.Count > 0
|
||||
? formats[0]
|
||||
: throw new InvalidOperationException("The Vulkan surface exposes no pixel formats.");
|
||||
}
|
||||
|
||||
private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supported)
|
||||
{
|
||||
foreach (var candidate in new[]
|
||||
{
|
||||
CompositeAlphaFlagsKHR.OpaqueBitKhr,
|
||||
CompositeAlphaFlagsKHR.PreMultipliedBitKhr,
|
||||
CompositeAlphaFlagsKHR.PostMultipliedBitKhr,
|
||||
CompositeAlphaFlagsKHR.InheritBitKhr,
|
||||
})
|
||||
{
|
||||
if ((supported & candidate) != 0)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The Vulkan surface exposes no composite alpha mode.");
|
||||
}
|
||||
|
||||
private static ImageSubresourceRange ColorSubresourceRange() =>
|
||||
new()
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
LevelCount = 1,
|
||||
LayerCount = 1,
|
||||
};
|
||||
|
||||
private static byte[] ScaleBgra(byte[] source, uint sourceWidth, uint sourceHeight, uint width, uint height)
|
||||
{
|
||||
var destination = new byte[checked((int)(width * height * 4))];
|
||||
for (uint y = 0; y < height; y++)
|
||||
{
|
||||
var sourceY = (uint)(((ulong)y * sourceHeight) / height);
|
||||
for (uint x = 0; x < width; x++)
|
||||
{
|
||||
var sourceX = (uint)(((ulong)x * sourceWidth) / width);
|
||||
var sourceOffset = checked((int)(((ulong)sourceY * sourceWidth + sourceX) * 4));
|
||||
var destinationOffset = checked((int)(((ulong)y * width + x) * 4));
|
||||
source.AsSpan(sourceOffset, 4).CopyTo(destination.AsSpan(destinationOffset, 4));
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private void DisposeVulkan()
|
||||
{
|
||||
if (!_vulkanReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_vulkanReady = false;
|
||||
_vk.DeviceWaitIdle(_device);
|
||||
if (_stagingBuffer.Handle != 0)
|
||||
{
|
||||
_vk.DestroyBuffer(_device, _stagingBuffer, null);
|
||||
}
|
||||
if (_stagingMemory.Handle != 0)
|
||||
{
|
||||
_vk.FreeMemory(_device, _stagingMemory, null);
|
||||
}
|
||||
if (_imageAvailable.Handle != 0)
|
||||
{
|
||||
_vk.DestroySemaphore(_device, _imageAvailable, null);
|
||||
}
|
||||
if (_renderFinished.Handle != 0)
|
||||
{
|
||||
_vk.DestroySemaphore(_device, _renderFinished, null);
|
||||
}
|
||||
if (_commandPool.Handle != 0)
|
||||
{
|
||||
_vk.DestroyCommandPool(_device, _commandPool, null);
|
||||
}
|
||||
if (_swapchain.Handle != 0)
|
||||
{
|
||||
_swapchainApi.DestroySwapchain(_device, _swapchain, null);
|
||||
}
|
||||
if (_device.Handle != 0)
|
||||
{
|
||||
_vk.DestroyDevice(_device, null);
|
||||
}
|
||||
if (_surface.Handle != 0)
|
||||
{
|
||||
_surfaceApi.DestroySurface(_instance, _surface, null);
|
||||
}
|
||||
if (_instance.Handle != 0)
|
||||
{
|
||||
_vk.DestroyInstance(_instance, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Check(Result result, string operation)
|
||||
{
|
||||
if (result != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"{operation} failed with {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,91 @@
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Silk.NET.Vulkan": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Vulkan.Extensions.KHR": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Vulkan": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Windowing.Common": "2.23.0",
|
||||
"Silk.NET.Windowing.Glfw": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.6",
|
||||
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.9",
|
||||
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
|
||||
},
|
||||
"Silk.NET.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.9"
|
||||
}
|
||||
},
|
||||
"Silk.NET.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Ultz.Native.GLFW": "3.4.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Maths": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
|
||||
},
|
||||
"Silk.NET.Windowing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
|
||||
"dependencies": {
|
||||
"Silk.NET.Core": "2.23.0",
|
||||
"Silk.NET.Maths": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Silk.NET.Windowing.Glfw": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.23.0",
|
||||
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
|
||||
"dependencies": {
|
||||
"Silk.NET.GLFW": "2.23.0",
|
||||
"Silk.NET.Windowing.Common": "2.23.0"
|
||||
}
|
||||
},
|
||||
"Ultz.Native.GLFW": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
||||
},
|
||||
"sharpemu.hle": {
|
||||
"type": "Project"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user