mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-16 00:40:59 +00:00
[SourceGenerators] Compile-time SysAbi export registry, analyzers, and build-generated aerolib.bin (#204)
* [SourceGenerators] Add the SysAbi export generator and analyzers (phase 0) New SharpEmu.SourceGenerators Roslyn component, complete and tested but consumed by nothing yet — the emulator projects adopt it in the following commits. Ps5Nid ports the PS NID derivation (base64 of the byte-reversed first eight SHA1 bytes of name + fixed suffix) from scripts/generate_aerolib_binary.py to C#, so what has always been a manual, out-of-band computation becomes a compile-time capability. SysAbiExportGenerator emits a per-assembly SysAbiExportRegistry whose CreateExports(Generation) reproduces ModuleManager's reflection scan exactly — same generation inheritance and filtering, same method-name fallback, same libKernel default — with attribute-omitted NIDs derived algorithmically (equivalent to the runtime catalog lookup, which is built from the same computation). Parameterless handlers are adapted to the SysAbiFunction shape; invalid declarations are skipped here because the analyzer rejects them as build errors, so nothing drops silently. SysAbiExportAnalyzer turns the runtime failure modes into diagnostics: SHEM001 duplicate NID (across declared and derived forms), SHEM002 malformed NID, SHEM003 uncallable handler signature, SHEM004 NID contradicting its export name (the class of drift previously fixed by hand), SHEM005 unresolvable export, SHEM006 export name unknown to ps5_names.txt when the catalog is wired as an AdditionalFile, SHEM007 handler not reachable by generated code. The self-contained test suite drives both in-process against the real SharpEmu.HLE metadata: known catalog NID pairs pin the algorithm, the generated registry must itself compile, and each diagnostic has a triggering fixture. Fittingly, the NID pinning test caught a wrong pair in its own first draft — the exact mistake SHEM004 exists to stop. * [SourceGenerators] Adopt the generated export registry in the emulator (phase 1) SharpEmu.Libs consumes the generator and analyzers, with scripts/ps5_names.txt wired as the AdditionalFile catalog. The runtime now registers exports from the compile-time SysAbiExportRegistry instead of the boot-time reflection scan; RegisterFromAssembly is retained solely as the arbiter for a parity test that pins the two tables identical — same NIDs, names, libraries, targets, and handler methods — across Gen4, Gen5, and combined registration. First contact between the analyzer and all 715 existing exports surfaced real drift the old offline checker structurally missed (scripts/check_sysabi_aerolib.py skipped any NID absent from aerolib.bin): three exports whose friendly names collide with real catalog symbols of different NIDs, now suppressed at-site with reasons pending AGC API confirmation, alongside the established synthetic Unknown* labels for uncatalogued NIDs, which prompted a rule refinement — SHEM004 only hard-errors when the export name is a real catalog symbol, since synthetic labels cannot be validated by hashing and the NID is authoritative for them. The two allowlisted mismatches in the python checker no longer trigger anything, and the checker is deleted: the analyzer subsumes it with the semantic model instead of regex, and validates every declared pair rather than only catalog-known NIDs. * [SourceGenerators] Generate aerolib.bin at build time from ps5_names.txt The runtime NID -> name catalog is derived data and no longer lives in the repository: a Framework-only MSBuild task (GenerateAerolibBinaryTask, sharing the same Ps5Nid implementation the analyzers use) builds it into the intermediate directory from scripts/ps5_names.txt — now the single source of truth — and SharpEmu.HLE embeds it from there. The output is byte-identical to the previously committed binary, verified with cmp against git history; a new test pins that the embedded catalog loads and resolves a known symbol both directions. scripts/generate_aerolib_binary.py is deleted (its algorithm lives in Ps5Nid, its invocation in the build); the REUSE annotation for the binary goes with it. MSBuild's Inputs/Outputs check means the ~154k NID hashes only recompute when the names file actually changes. The task implements ITask against Microsoft.Build.Framework directly, keeping the vulnerable-flagged Utilities.Core package out and the analyzer project's file-IO ban suppressed only inside the task itself. * [SourceGenerators] Emit typed-signature register thunks (phase 2) [SysAbiExport] handlers can now be written with real signatures — a CpuContext followed by up to six int/uint/long/ulong parameters — and the generator emits the SysV unmarshalling thunk, mapping parameters positionally to RDI/RSI/RDX/RCX/R8/R9 with the same unchecked-cast idiom hand-written handlers use. SHEM003 accepts the new shape and rejects register overflow and non-register-representable types. Both shapes coexist, so migration is per-handler; sceKernelPollSema, sceKernelSignalSema, and sceKernelCancelSema migrate as the demonstration (the last showing raw ulong guest-address passthrough). The reflection scan cannot represent typed handlers, so it retires here: RegisterFromAssembly, its signature validation, and ResolveExportInfo are deleted, and the parity test that pinned the generated registry to the scan is replaced by content-invariant tests (duplicate-free, full 715-export surface, catalog identity). Deleting the scan surfaced a phase-1 latent regression — the pre-JIT warm sweep enumerated only reflection-scanned assemblies, so the generated registration path warmed nothing and re-exposed the guest-thread fail-fast risk; the warm set is now derived from the registered handler delegates themselves. * [SourceGenerators] Marshal guest strings declaratively with [GuestCString] (phase 3) A string parameter on a typed [SysAbiExport] handler, annotated [GuestCString(maxLength)], now makes the generated thunk read the null-terminated UTF-8 string from the argument register's guest address before the handler runs, returning ORBIS_GEN2_ERROR_MEMORY_FAULT to the guest when the read fails — the exact prologue nearly every string-taking handler writes by hand. The attribute lives in SharpEmu.HLE next to SysAbiExportAttribute; SHEM008 rejects misuse (non-string parameter, non-positive MaxLength) while a bare string parameter stays a SHEM003 signature error. _open, open, and sceKernelOpen migrate as the demonstration; they were chosen because their hand-written prologue faulted on a null pointer the same way the thunk does (handlers that return INVALID_ARGUMENT for null pointers, like sceKernelCreateSema, keep the raw shape so guest- visible semantics stay untouched). * [SourceGenerators] Apply review findings across the branch Behavior: the open/_open/sceKernelOpen [GuestCString] demo migration is reverted — the local compat reader falls back to host memory for paths in loader-mapped regions that ctx.Memory cannot see, so the generated thunk would have turned recoverable reads into MEMORY_FAULT. The marshalling infrastructure stays, proven by generator/analyzer tests; production migration waits for a handler whose semantics the thunk reproduces exactly. A comment on the handler records why. Build robustness: the aerolib target is skipped for design-time builds (the IDE resolves project references without compiling them, so on a fresh clone the task assembly does not exist yet), and the task/names paths are centralized in properties. The generator now emits no registry for export-free assemblies, so referencing the analyzer can never mint a colliding SharpEmu.Generated type. Cleanup and perf: the pragma-suppression sites left mis-indented by the phase-1 relocation are reformatted and the restores moved after the method body; the dead ExportsForTesting hook and its InternalsVisibleTo are deleted; the aerolib task reuses one SHA1 instance across ~150k names; the analyzer caches the parsed catalog per file snapshot instead of re-parsing 150k lines every compilation start, shares the attribute name constant with the generator, and computes the catalog-membership check once. * [CI] Run the test suites in the build workflow The workflow compiled the test projects (they are in SharpEmu.slnx) but never executed them. A solution-level dotnet test now runs between build and publish, so any test failure fails the build — including the AerolibCatalogTests/SysAbiRegistryTests that guard the build-generated aerolib.bin and the generated export registry. Generation failures of aerolib.bin itself already fail the build step: the MSBuild task logs an error event and returns false, and a missing task assembly or missing embedded output are hard MSBuild errors. The NuGet cache key now also tracks the test projects' lock files. * [SourceGenerators] Address review feedback Multi-diagnostic analyzer tests no longer assume a stable diagnostic order (analyzer execution is concurrent), and the aerolib task logs the full exception instead of only its message so build failures keep the type and stack trace. * [SourceGenerators] Address second review round Symbol-name comparisons in the shape rules and analyzer now pin an explicit SymbolDisplayFormat.FullyQualifiedFormat instead of relying on the display-format default, and the aerolib task fails loudly on a symbol name that would overflow the format's ushort length prefix instead of silently truncating it, with null-safe output-directory handling made explicit. * [SourceGenerators] Embed aerolib.bin via a target so design-time builds never reference it The static EmbeddedResource item referenced the generated file even in design-time builds, where the generation target is skipped — on a fresh clone the IDE would try to embed a file that never existed. The item is now created inside an EmbedAerolibBinary target gated on DesignTimeBuild, separate from the generation target so an up-to-date skip of GenerateAerolibBinary cannot drop the item with the rest of its body, and hooked before AssignTargetPaths since dynamic resource items added later miss the resource pipeline. Verified fresh build, incremental rebuild (embedded catalog test both times), and a simulated design-time compile with no artifacts present. * [SourceGenerators] Regenerate test lock file after rebase onto main Rebase fallout: main's package graph shifted under #200, so the SourceGenerators.Tests lock file is re-evaluated to keep --locked-mode restore green at the branch tip. * [Build] Drop NuGet lock files; rely on central package management Central package management was already in effect (ManagePackageVersionsCentrally with all versions in Directory.Packages.props and no inline PackageReference versions), so the per-project packages.lock.json files and the lock-mode workflow only added maintenance overhead. This removes all eleven lock files, drops RestorePackagesWithLockFile so restore no longer regenerates them, and takes --locked-mode off the CI restore steps (re-keying the NuGet cache on the central props files). Package versions remain centrally pinned in Directory.Packages.props.
This commit is contained in:
committed by
GitHub
parent
30fdd8d6ed
commit
320dbcacba
19
.github/workflows/workflow.yml
vendored
19
.github/workflows/workflow.yml
vendored
@@ -94,14 +94,20 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
Directory.Packages.props
|
Directory.Packages.props
|
||||||
src/**/packages.lock.json
|
Directory.Build.props
|
||||||
|
|
||||||
- name: Restore solution
|
- name: Restore solution
|
||||||
run: dotnet restore SharpEmu.slnx --locked-mode
|
run: dotnet restore SharpEmu.slnx
|
||||||
|
|
||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
||||||
|
|
||||||
|
# Runs every test project in the solution; a test failure fails the build, as
|
||||||
|
# does an aerolib.bin generation failure in the build step above (the MSBuild
|
||||||
|
# task logs an error and returns false).
|
||||||
|
- name: Run tests
|
||||||
|
run: dotnet test SharpEmu.slnx -c Release --no-build --verbosity normal
|
||||||
|
|
||||||
- name: Validate synthetic shaders
|
- name: Validate synthetic shaders
|
||||||
run: dotnet run --project tools/SharpEmu.Tools.ShaderDump/SharpEmu.Tools.ShaderDump.csproj -c Release -- artifacts/shader-dump
|
run: dotnet run --project tools/SharpEmu.Tools.ShaderDump/SharpEmu.Tools.ShaderDump.csproj -c Release -- artifacts/shader-dump
|
||||||
|
|
||||||
@@ -157,14 +163,19 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
Directory.Packages.props
|
Directory.Packages.props
|
||||||
src/**/packages.lock.json
|
Directory.Build.props
|
||||||
|
|
||||||
- name: Restore solution
|
- name: Restore solution
|
||||||
run: dotnet restore SharpEmu.slnx --locked-mode
|
run: dotnet restore SharpEmu.slnx
|
||||||
|
|
||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
||||||
|
|
||||||
|
# Also runs on Linux/macOS: the aerolib.bin MSBuild task does platform-sensitive
|
||||||
|
# path handling, so a cross-platform generation regression surfaces here.
|
||||||
|
- name: Run tests
|
||||||
|
run: dotnet test SharpEmu.slnx -c Release --no-build --verbosity normal
|
||||||
|
|
||||||
- name: Publish ${{ matrix.rid }} CLI
|
- name: Publish ${{ matrix.rid }} CLI
|
||||||
run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r ${{ matrix.rid }} --self-contained true --no-restore -p:PublishDir="$PUBLISH_DIR"
|
run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r ${{ matrix.rid }} --self-contained true --no-restore -p:PublishDir="$PUBLISH_DIR"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
|
||||||
<SharpEmuVersion>0.0.1</SharpEmuVersion>
|
<SharpEmuVersion>0.0.1</SharpEmuVersion>
|
||||||
<Version>$(SharpEmuVersion)</Version>
|
<Version>$(SharpEmuVersion)</Version>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.18" />
|
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.18" />
|
||||||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.18" />
|
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.18" />
|
||||||
<PackageVersion Include="Iced" Version="1.21.0" />
|
<PackageVersion Include="Iced" Version="1.21.0" />
|
||||||
|
<PackageVersion Include="Microsoft.Build.Framework" Version="17.14.8" />
|
||||||
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||||
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageVersion Include="Silk.NET.Input" Version="2.23.0" />
|
<PackageVersion Include="Silk.NET.Input" Version="2.23.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ path = [
|
|||||||
"REUSE.toml",
|
"REUSE.toml",
|
||||||
"nuget.config",
|
"nuget.config",
|
||||||
"global.json",
|
"global.json",
|
||||||
"**/packages.lock.json",
|
|
||||||
"scripts/ps5_names.txt",
|
"scripts/ps5_names.txt",
|
||||||
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
|
|
||||||
"src/SharpEmu.GUI/Languages/**",
|
"src/SharpEmu.GUI/Languages/**",
|
||||||
"_logs/**",
|
"_logs/**",
|
||||||
".github/images/**",
|
".github/images/**",
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<Project Path="src/SharpEmu.Logging/SharpEmu.Logging.csproj" />
|
<Project Path="src/SharpEmu.Logging/SharpEmu.Logging.csproj" />
|
||||||
<Project Path="src/SharpEmu.ShaderCompiler/SharpEmu.ShaderCompiler.csproj" />
|
<Project Path="src/SharpEmu.ShaderCompiler/SharpEmu.ShaderCompiler.csproj" />
|
||||||
<Project Path="src/SharpEmu.ShaderCompiler.Vulkan/SharpEmu.ShaderCompiler.Vulkan.csproj" />
|
<Project Path="src/SharpEmu.ShaderCompiler.Vulkan/SharpEmu.ShaderCompiler.Vulkan.csproj" />
|
||||||
|
<Project Path="src/SharpEmu.SourceGenerators/SharpEmu.SourceGenerators.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj" />
|
<Project Path="tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj" />
|
||||||
|
<Project Path="tests/SharpEmu.SourceGenerators.Tests/SharpEmu.SourceGenerators.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""Offline check: SysAbiExport ExportName must hash to its Nid (name2nid).
|
|
||||||
|
|
||||||
NIDs absent from aerolib.bin are skipped (unknown/unresolved symbols).
|
|
||||||
Known historic mislabels may be allowlisted with a one-line reason.
|
|
||||||
|
|
||||||
Run from the repository root:
|
|
||||||
python scripts/check_sysabi_aerolib.py
|
|
||||||
python scripts/check_sysabi_aerolib.py --strict
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import hashlib
|
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
from base64 import b64encode as base64enc
|
|
||||||
from binascii import unhexlify as uhx
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
SRC_ROOT = Path("src")
|
|
||||||
AEROLIB_BIN = Path("src/SharpEmu.HLE/Aerolib/aerolib.bin")
|
|
||||||
SYSABI_EXPORT_RE = re.compile(r"\[SysAbiExport\((.*?)\)\]", re.DOTALL)
|
|
||||||
NID_RE = re.compile(r'Nid\s*=\s*"([^"]+)"')
|
|
||||||
EXPORT_NAME_RE = re.compile(r'ExportName\s*=\s*"([^"]+)"')
|
|
||||||
|
|
||||||
# NID -> reason. Keep minimal; fix ExportName when safe instead of growing this list.
|
|
||||||
ALLOWLISTED_NIDS: dict[str, str] = {
|
|
||||||
"KMcEa+rHsIo": "Historic kernel MapMemory stub bound to sceAvPlayerAddSource NID; API rewrite deferred.",
|
|
||||||
"WV1GwM32NgY": "Historic WebApi2 init alias for PushEventCreateHandle NID; ABI rewrite deferred.",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def name2nid(name: str) -> str:
|
|
||||||
symbol = hashlib.sha1(name.encode() + uhx("518D64A635DED8C1E6B039B1C3E55230")).digest()
|
|
||||||
id_val = struct.unpack("<Q", symbol[:8])[0]
|
|
||||||
nid = base64enc(uhx("%016x" % id_val), b"+-").rstrip(b"=")
|
|
||||||
return nid.decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def find_repo_root() -> Path:
|
|
||||||
cwd = Path.cwd()
|
|
||||||
if (cwd / SRC_ROOT).is_dir() and (cwd / "scripts").is_dir():
|
|
||||||
return cwd
|
|
||||||
script_root = Path(__file__).resolve().parent.parent
|
|
||||||
if (script_root / SRC_ROOT).is_dir():
|
|
||||||
return script_root
|
|
||||||
raise SystemExit("Run from the repository root (src/ and scripts/ expected).")
|
|
||||||
|
|
||||||
|
|
||||||
def load_aerolib_nids(aerolib_path: Path) -> set[str]:
|
|
||||||
data = aerolib_path.read_bytes()
|
|
||||||
if len(data) < 4:
|
|
||||||
raise SystemExit(f"Aerolib binary too small: {aerolib_path}")
|
|
||||||
|
|
||||||
count = struct.unpack_from("<I", data, 0)[0]
|
|
||||||
offset = 4
|
|
||||||
nids: set[str] = set()
|
|
||||||
for _ in range(count):
|
|
||||||
if offset >= len(data):
|
|
||||||
raise SystemExit(f"Truncated aerolib.bin while reading NIDs: {aerolib_path}")
|
|
||||||
nid_len = data[offset]
|
|
||||||
offset += 1
|
|
||||||
nid = data[offset : offset + nid_len].decode("utf-8")
|
|
||||||
offset += nid_len
|
|
||||||
if offset + 2 > len(data):
|
|
||||||
raise SystemExit(f"Truncated aerolib.bin name length: {aerolib_path}")
|
|
||||||
name_len = struct.unpack_from("<H", data, offset)[0]
|
|
||||||
offset += 2 + name_len
|
|
||||||
nids.add(nid)
|
|
||||||
return nids
|
|
||||||
|
|
||||||
|
|
||||||
def iter_sysabi_exports(cs_path: Path, text: str):
|
|
||||||
for match in SYSABI_EXPORT_RE.finditer(text):
|
|
||||||
block = match.group(1)
|
|
||||||
nid_match = NID_RE.search(block)
|
|
||||||
export_match = EXPORT_NAME_RE.search(block)
|
|
||||||
if nid_match is None or export_match is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
nid = nid_match.group(1)
|
|
||||||
export_name = export_match.group(1)
|
|
||||||
nid_attr = f'Nid = "{nid}"'
|
|
||||||
abs_pos = text.find(nid_attr, match.start(), match.end())
|
|
||||||
if abs_pos < 0:
|
|
||||||
abs_pos = match.start()
|
|
||||||
line = text.count("\n", 0, abs_pos) + 1
|
|
||||||
yield cs_path, line, nid, export_name
|
|
||||||
|
|
||||||
|
|
||||||
def scan(src_root: Path, catalog_nids: set[str]):
|
|
||||||
checked = 0
|
|
||||||
mismatches = []
|
|
||||||
skipped_no_catalog = 0
|
|
||||||
allowlisted = 0
|
|
||||||
|
|
||||||
for cs_path in sorted(src_root.rglob("*.cs")):
|
|
||||||
text = cs_path.read_text(encoding="utf-8")
|
|
||||||
for path, line, nid, export_name in iter_sysabi_exports(cs_path, text):
|
|
||||||
checked += 1
|
|
||||||
computed = name2nid(export_name)
|
|
||||||
if computed == nid:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nid not in catalog_nids:
|
|
||||||
skipped_no_catalog += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if nid in ALLOWLISTED_NIDS:
|
|
||||||
allowlisted += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
mismatches.append((path, line, nid, export_name, computed))
|
|
||||||
|
|
||||||
return checked, mismatches, skipped_no_catalog, allowlisted
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Check that SysAbiExport ExportName values hash to their Nid via name2nid."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--strict",
|
|
||||||
action="store_true",
|
|
||||||
help="Exit 1 when any non-skipped/non-allowlisted ExportName does not hash to its Nid.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--quiet",
|
|
||||||
action="store_true",
|
|
||||||
help="Print only the summary line.",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
repo_root = find_repo_root()
|
|
||||||
aerolib_path = repo_root / AEROLIB_BIN
|
|
||||||
if not aerolib_path.is_file():
|
|
||||||
raise SystemExit(f"Missing Aerolib catalog: {aerolib_path.as_posix()}")
|
|
||||||
|
|
||||||
catalog_nids = load_aerolib_nids(aerolib_path)
|
|
||||||
checked, mismatches, skipped_no_catalog, allowlisted = scan(
|
|
||||||
repo_root / SRC_ROOT, catalog_nids
|
|
||||||
)
|
|
||||||
ok = checked - len(mismatches) - skipped_no_catalog - allowlisted
|
|
||||||
|
|
||||||
if not args.quiet:
|
|
||||||
for path, line, nid, export_name, computed in mismatches:
|
|
||||||
rel = path.relative_to(repo_root).as_posix()
|
|
||||||
print(
|
|
||||||
f"{rel}:{line}: NID={nid} ExportName={export_name!r} "
|
|
||||||
f"computed={computed}"
|
|
||||||
)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"checked={checked} ok={ok} fail={len(mismatches)} "
|
|
||||||
f"skipped_no_catalog={skipped_no_catalog} allowlisted={allowlisted} "
|
|
||||||
f"allowlist_size={len(ALLOWLISTED_NIDS)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.strict and mismatches:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Copyright (C) 2026 SharpEmu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import struct
|
|
||||||
from base64 import b64encode as base64enc
|
|
||||||
from binascii import unhexlify as uhx
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
NAMES = 'scripts/ps5_names.txt'
|
|
||||||
OUTPUT = 'src/SharpEmu.HLE/Aerolib/aerolib.bin'
|
|
||||||
|
|
||||||
def name2nid(name):
|
|
||||||
symbol = hashlib.sha1(name.encode() + uhx('518D64A635DED8C1E6B039B1C3E55230')).digest()
|
|
||||||
id_val = struct.unpack('<Q', symbol[:8])[0]
|
|
||||||
nid = base64enc(uhx('%016x' % id_val), b'+-').rstrip(b'=')
|
|
||||||
return nid.decode('utf-8')
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
names_path = Path(NAMES)
|
|
||||||
output_path = Path(OUTPUT)
|
|
||||||
|
|
||||||
entries = []
|
|
||||||
with open(names_path, 'r', encoding='utf-8') as f:
|
|
||||||
for line in f:
|
|
||||||
name = line.strip()
|
|
||||||
if name:
|
|
||||||
nid = name2nid(name)
|
|
||||||
entries.append((nid, name))
|
|
||||||
|
|
||||||
print(f"Found {len(entries)} entries")
|
|
||||||
|
|
||||||
data = bytearray()
|
|
||||||
data.extend(struct.pack('<I', len(entries)))
|
|
||||||
|
|
||||||
for nid, name in entries:
|
|
||||||
nid_bytes = nid.encode('utf-8')
|
|
||||||
name_bytes = name.encode('utf-8')
|
|
||||||
data.append(len(nid_bytes))
|
|
||||||
data.extend(nid_bytes)
|
|
||||||
data.extend(struct.pack('<H', len(name_bytes)))
|
|
||||||
data.extend(name_bytes)
|
|
||||||
|
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(output_path, 'wb') as f:
|
|
||||||
f.write(data)
|
|
||||||
|
|
||||||
print(f"Generated: {output_path} ({len(data):,} bytes)")
|
|
||||||
print(f"Total entries: {len(entries)}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
generate()
|
|
||||||
@@ -1,586 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.3, )",
|
|
||||||
"resolved": "10.0.3",
|
|
||||||
"contentHash": "0B6nZyCHWXnvmlB559oduOspVdNOnpNXPjhpWVMovLPAsDVG7A4jJR9rzECf67JUzxP8/ee/wA8clwIzJcWNFA=="
|
|
||||||
},
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.BuildServices": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.2",
|
|
||||||
"contentHash": "qHDToxto1e3hci5YqbG9n0Ty8mlp3zBUN5wT66wKqaDVzXyQ0do3EnRILd4Ke9jpvsktaPpgE0YjEk7hornryQ=="
|
|
||||||
},
|
|
||||||
"Avalonia.FreeDesktop": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "aUwv8BNruRUOaUfMu4U3uibIUS60/rSHgGOhd8zBkLkpxY3JFJvgRbeq5ZzHIyKXCuKi18PO00YHAgCarp3wdw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Tmds.DBus.Protocol": "0.21.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Remote.Protocol": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "vw+6ZfgTuu72dA9aVWn6u56t2nrBd5MoMU0wo/qI9XJAl/c0oYYphIvwLvJP1JorubQY4UE3d0ac8ULBhrGBiA=="
|
|
||||||
},
|
|
||||||
"Avalonia.Skia": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "/B4aXmNRNjG8I5U/a1xJI+bIi0XO6DDzS3mBrIKlVnJRY2CyZiUeESRQXLnIU77Z9TvqkUROs+D47s085YjFtA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"HarfBuzzSharp": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.WebAssembly": "8.3.1.1",
|
|
||||||
"SkiaSharp": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.Linux": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.WebAssembly": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "eioUHkM2PeLPETd1aEks3rvb9plbba6buIrNdrqCpwE/qgHKUjvRNBd5mUQfAbGgTLiAes524gB8uUMDhrsJVQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.Angle.Windows.Natives": "2.1.25547.20250602"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.X11": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "m4Ki/G5Dovnq+6QzfS0iGbK8V77Q6oTjToMLOB0CxPCCrl3Oxywh6kIjuGJDPaN6kopMmjxlNShyQf+vPYL+JA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.FreeDesktop": "11.3.18",
|
|
||||||
"Avalonia.Skia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "tLZN66oe/uiRPTZfrCU4i8ScVGwqHNh5MHrXj0yVf4l7Mz0FhTGnQ71RGySROTmdognAs0JtluHkL41pIabWuQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": "8.3.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.WebAssembly": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "loJweK2u/mH/3C2zBa0ggJlITIszOkK64HLAZB7FUT670dTg965whLFYHDQo69NmC4+d9UN0icLC9VHidXaVCA=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"MicroCom.Runtime": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "0.11.0",
|
|
||||||
"contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA=="
|
|
||||||
},
|
|
||||||
"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.Input.Common": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "QbJVV7kFBHEByayXCYdJtXXI9Sp4a+QAf0IdGV6uCWkFYcEmqBYW3aaNGvFOdSwTBDbHL5T/OtOCrGh4qYhk7A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Windowing.Common": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Silk.NET.Input.Glfw": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "KGHYqsv/IQRJtD6dloYh2tN4CkaM40vxM2kj0cGKBoCQiBDYHHhJiyDTyMPx0W7Fz5IgnhnG42ELmIAa0DH69A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Windowing.Glfw": "2.23.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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "3MD5VHjXXieSHCleRLuaTXmL2pD0mB7CcOB1x2kA1I4bhptf4e3R27iM93264ZYuAq6mkUyX5XbcxnZvMJYc1Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp.NativeAssets.Win32": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.macOS": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.WebAssembly": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "kt06RccBHSnAs2wDYdBSfsjIDbY3EpsOVqnlDgKdgvyuRA8ZFDaHRdWNx1VHjGgYzmnFCGiTJBnXFl5BqGwGnA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"Ultz.Native.GLFW": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.4.0",
|
|
||||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
|
||||||
},
|
|
||||||
"sharpemu.core": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"Iced": "[1.21.0, )",
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )",
|
|
||||||
"SharpEmu.Libs": "[0.0.1, )",
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.gui": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "[11.3.18, )",
|
|
||||||
"Avalonia.Desktop": "[11.3.18, )",
|
|
||||||
"Avalonia.Fonts.Inter": "[11.3.18, )",
|
|
||||||
"Avalonia.Themes.Fluent": "[11.3.18, )",
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )",
|
|
||||||
"Tmds.DBus.Protocol": "[0.21.3, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.hle": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.libs": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler.Vulkan": "[0.0.1, )",
|
|
||||||
"Silk.NET.Input": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
|
||||||
"Silk.NET.Windowing": "[2.23.0, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler.vulkan": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "2C4UxhWUObWGgYKWic1x5BMMWGJP6SElb91WeOxs+X/iR26rtkqpxFFwwo50FXS9AyYnHfk8QKXDEfe7oT/kZA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia.BuildServices": "11.3.2",
|
|
||||||
"Avalonia.Remote.Protocol": "11.3.18",
|
|
||||||
"MicroCom.Runtime": "0.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Desktop": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "bilMPa5vYiis6fbNovb6esKytBnOCEGojBa1XFegLCRHCP6g6PvZwS0XF/YOAGkENRlHG8dI7lohOpQ9bIkq1g==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.Native": "11.3.18",
|
|
||||||
"Avalonia.Skia": "11.3.18",
|
|
||||||
"Avalonia.Win32": "11.3.18",
|
|
||||||
"Avalonia.X11": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Fonts.Inter": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "27u6hB3Y2Ue586yjfeVakberY73VNQXtuKwe/P927XG1QPlhsfmOyifLHDDpSHG85Zl1x/Xv9IZ3+tk9FnjcZQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Themes.Fluent": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "+Q/TJoynD0zNuu5w2gD+xcTl7GNKJFxlPYAndRLs/mTDrNbbsvv/271WyIysbMPsXSjCyBDp7RCZzQkpD6x5Bg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Iced": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[1.21.0, )",
|
|
||||||
"resolved": "1.21.0",
|
|
||||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
|
||||||
},
|
|
||||||
"Silk.NET.Input": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "Xzl+tVwAp2eEd8blmGQjmJrsZPnp3PWG0KJjiAQHaY2Zr/ELVWeAROKXmZdCAvexzmte2JVGEy/dxnMycbxlpg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Input.Glfw": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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.EXT": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Core": "2.23.0",
|
|
||||||
"Silk.NET.Vulkan": "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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Tmds.DBus.Protocol": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[0.21.3, )",
|
|
||||||
"resolved": "0.21.3",
|
|
||||||
"contentHash": "hDwB8WsQoyALQKqIbwzS68UKdlnafDm4T/DkO/JrA/YIneP/rKv96SxYPVXeh3FP4i/SXfShrYftKLtciJAIlw=="
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"net10.0/linux-x64": {
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"Ultz.Native.GLFW": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.4.0",
|
|
||||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"net10.0/osx-arm64": {
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"Ultz.Native.GLFW": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.4.0",
|
|
||||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"net10.0/osx-x64": {
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"Ultz.Native.GLFW": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.4.0",
|
|
||||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"net10.0/win-x64": {
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"Ultz.Native.GLFW": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.4.0",
|
|
||||||
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -84,7 +84,9 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
|||||||
ImportTraceLimit = Math.Max(0, options.ImportTraceLimit),
|
ImportTraceLimit = Math.Max(0, options.ImportTraceLimit),
|
||||||
};
|
};
|
||||||
var moduleManager = new ModuleManager();
|
var moduleManager = new ModuleManager();
|
||||||
moduleManager.RegisterFromAssembly(typeof(KernelExports).Assembly, Generation.Gen4 | Generation.Gen5, Aerolib.Instance);
|
// The compile-time generated registry (SharpEmu.SourceGenerators) is the sole
|
||||||
|
// registration source; content tests in SharpEmu.Libs.Tests pin its invariants.
|
||||||
|
moduleManager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4 | Generation.Gen5));
|
||||||
moduleManager.Freeze();
|
moduleManager.Freeze();
|
||||||
|
|
||||||
// Resolve the host platform once at the composition root; on unsupported
|
// Resolve the host platform once at the composition root; on unsupported
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"Iced": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[1.21.0, )",
|
|
||||||
"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.Input.Common": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "QbJVV7kFBHEByayXCYdJtXXI9Sp4a+QAf0IdGV6uCWkFYcEmqBYW3aaNGvFOdSwTBDbHL5T/OtOCrGh4qYhk7A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Windowing.Common": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Silk.NET.Input.Glfw": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "KGHYqsv/IQRJtD6dloYh2tN4CkaM40vxM2kj0cGKBoCQiBDYHHhJiyDTyMPx0W7Fz5IgnhnG42ELmIAa0DH69A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Windowing.Glfw": "2.23.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",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.libs": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler.Vulkan": "[0.0.1, )",
|
|
||||||
"Silk.NET.Input": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
|
||||||
"Silk.NET.Windowing": "[2.23.0, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler.vulkan": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Silk.NET.Input": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "Xzl+tVwAp2eEd8blmGQjmJrsZPnp3PWG0KJjiAQHaY2Zr/ELVWeAROKXmZdCAvexzmte2JVGEy/dxnMycbxlpg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Input.Glfw": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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.EXT": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Core": "2.23.0",
|
|
||||||
"Silk.NET.Vulkan": "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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"Avalonia": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "2C4UxhWUObWGgYKWic1x5BMMWGJP6SElb91WeOxs+X/iR26rtkqpxFFwwo50FXS9AyYnHfk8QKXDEfe7oT/kZA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia.BuildServices": "11.3.2",
|
|
||||||
"Avalonia.Remote.Protocol": "11.3.18",
|
|
||||||
"MicroCom.Runtime": "0.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Desktop": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "bilMPa5vYiis6fbNovb6esKytBnOCEGojBa1XFegLCRHCP6g6PvZwS0XF/YOAGkENRlHG8dI7lohOpQ9bIkq1g==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.Native": "11.3.18",
|
|
||||||
"Avalonia.Skia": "11.3.18",
|
|
||||||
"Avalonia.Win32": "11.3.18",
|
|
||||||
"Avalonia.X11": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Fonts.Inter": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "27u6hB3Y2Ue586yjfeVakberY73VNQXtuKwe/P927XG1QPlhsfmOyifLHDDpSHG85Zl1x/Xv9IZ3+tk9FnjcZQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Themes.Fluent": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[11.3.18, )",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "+Q/TJoynD0zNuu5w2gD+xcTl7GNKJFxlPYAndRLs/mTDrNbbsvv/271WyIysbMPsXSjCyBDp7RCZzQkpD6x5Bg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Tmds.DBus.Protocol": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[0.21.3, )",
|
|
||||||
"resolved": "0.21.3",
|
|
||||||
"contentHash": "hDwB8WsQoyALQKqIbwzS68UKdlnafDm4T/DkO/JrA/YIneP/rKv96SxYPVXeh3FP4i/SXfShrYftKLtciJAIlw=="
|
|
||||||
},
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.1.25547.20250602",
|
|
||||||
"contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A=="
|
|
||||||
},
|
|
||||||
"Avalonia.BuildServices": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.2",
|
|
||||||
"contentHash": "qHDToxto1e3hci5YqbG9n0Ty8mlp3zBUN5wT66wKqaDVzXyQ0do3EnRILd4Ke9jpvsktaPpgE0YjEk7hornryQ=="
|
|
||||||
},
|
|
||||||
"Avalonia.FreeDesktop": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "aUwv8BNruRUOaUfMu4U3uibIUS60/rSHgGOhd8zBkLkpxY3JFJvgRbeq5ZzHIyKXCuKi18PO00YHAgCarp3wdw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Tmds.DBus.Protocol": "0.21.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Native": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "8g53DROFW6wVJAnTsE1Iu5bdZO5r0oqxbdbMQODs1QDYCK2IU/y/x/vQVKCYOvqxl4tXkzU9tExv8XqSGPWthQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Remote.Protocol": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "vw+6ZfgTuu72dA9aVWn6u56t2nrBd5MoMU0wo/qI9XJAl/c0oYYphIvwLvJP1JorubQY4UE3d0ac8ULBhrGBiA=="
|
|
||||||
},
|
|
||||||
"Avalonia.Skia": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "/B4aXmNRNjG8I5U/a1xJI+bIi0XO6DDzS3mBrIKlVnJRY2CyZiUeESRQXLnIU77Z9TvqkUROs+D47s085YjFtA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"HarfBuzzSharp": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.WebAssembly": "8.3.1.1",
|
|
||||||
"SkiaSharp": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.Linux": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.WebAssembly": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "eioUHkM2PeLPETd1aEks3rvb9plbba6buIrNdrqCpwE/qgHKUjvRNBd5mUQfAbGgTLiAes524gB8uUMDhrsJVQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.Angle.Windows.Natives": "2.1.25547.20250602"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Avalonia.X11": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "11.3.18",
|
|
||||||
"contentHash": "m4Ki/G5Dovnq+6QzfS0iGbK8V77Q6oTjToMLOB0CxPCCrl3Oxywh6kIjuGJDPaN6kopMmjxlNShyQf+vPYL+JA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Avalonia": "11.3.18",
|
|
||||||
"Avalonia.FreeDesktop": "11.3.18",
|
|
||||||
"Avalonia.Skia": "11.3.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "tLZN66oe/uiRPTZfrCU4i8ScVGwqHNh5MHrXj0yVf4l7Mz0FhTGnQ71RGySROTmdognAs0JtluHkL41pIabWuQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": "8.3.1.1",
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": "8.3.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.WebAssembly": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "loJweK2u/mH/3C2zBa0ggJlITIszOkK64HLAZB7FUT670dTg965whLFYHDQo69NmC4+d9UN0icLC9VHidXaVCA=="
|
|
||||||
},
|
|
||||||
"HarfBuzzSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.3.1.1",
|
|
||||||
"contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA=="
|
|
||||||
},
|
|
||||||
"MicroCom.Runtime": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "0.11.0",
|
|
||||||
"contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "3MD5VHjXXieSHCleRLuaTXmL2pD0mB7CcOB1x2kA1I4bhptf4e3R27iM93264ZYuAq6mkUyX5XbcxnZvMJYc1Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp.NativeAssets.Win32": "2.88.9",
|
|
||||||
"SkiaSharp.NativeAssets.macOS": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Linux": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
|
||||||
"dependencies": {
|
|
||||||
"SkiaSharp": "2.88.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.macOS": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.WebAssembly": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "kt06RccBHSnAs2wDYdBSfsjIDbY3EpsOVqnlDgKdgvyuRA8ZFDaHRdWNx1VHjGgYzmnFCGiTJBnXFl5BqGwGnA=="
|
|
||||||
},
|
|
||||||
"SkiaSharp.NativeAssets.Win32": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.88.9",
|
|
||||||
"contentHash": "wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w=="
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
20
src/SharpEmu.HLE/GuestCStringAttribute.cs
Normal file
20
src/SharpEmu.HLE/GuestCStringAttribute.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
namespace SharpEmu.HLE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a string parameter of a [SysAbiExport] handler as a guest null-terminated
|
||||||
|
/// UTF-8 pointer. The generated thunk reads the string from the argument register's
|
||||||
|
/// address (bounded by <see cref="MaxLength"/>) before invoking the handler, and
|
||||||
|
/// returns ORBIS_GEN2_ERROR_MEMORY_FAULT to the guest when the read fails — including
|
||||||
|
/// a null pointer, matching TryReadNullTerminatedUtf8's contract.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class GuestCStringAttribute : Attribute
|
||||||
|
{
|
||||||
|
public GuestCStringAttribute(int maxLength) => MaxLength = maxLength;
|
||||||
|
|
||||||
|
/// <summary>Upper bound in bytes for the guest read, terminator included.</summary>
|
||||||
|
public int MaxLength { get; }
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace SharpEmu.HLE;
|
namespace SharpEmu.HLE;
|
||||||
|
|
||||||
public interface IModuleManager
|
public interface IModuleManager
|
||||||
{
|
{
|
||||||
int RegisterFromAssembly(Assembly assembly, Generation generation, ISymbolCatalog? symbolCatalog = null);
|
/// <summary>Registers pre-built exports (the compile-time generated registry).</summary>
|
||||||
|
int RegisterExports(IReadOnlyList<ExportedFunction> exports);
|
||||||
|
|
||||||
void Freeze();
|
void Freeze();
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public sealed class ModuleManager : IModuleManager
|
|||||||
private readonly ConcurrentDictionary<string, ExportedFunction> _exportTable = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, ExportedFunction> _exportTable = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, ExportedFunction> _exportNameTable = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, ExportedFunction> _exportNameTable = new(StringComparer.Ordinal);
|
||||||
private readonly object _registrationGate = new();
|
private readonly object _registrationGate = new();
|
||||||
private readonly HashSet<(Assembly Assembly, Generation Generation)> _scannedAssemblies = new();
|
private readonly HashSet<Assembly> _warmupAssemblies = new();
|
||||||
private bool _isFrozen;
|
private bool _isFrozen;
|
||||||
|
|
||||||
public int RegisterFromAssembly(Assembly assembly, Generation generation, ISymbolCatalog? symbolCatalog = null)
|
public int RegisterExports(IReadOnlyList<ExportedFunction> exports)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(assembly);
|
ArgumentNullException.ThrowIfNull(exports);
|
||||||
|
|
||||||
lock (_registrationGate)
|
lock (_registrationGate)
|
||||||
{
|
{
|
||||||
@@ -27,48 +27,21 @@ public sealed class ModuleManager : IModuleManager
|
|||||||
throw new InvalidOperationException("Module registration is frozen.");
|
throw new InvalidOperationException("Module registration is frozen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicated: one assembly is reached through many types.
|
|
||||||
if (!_scannedAssemblies.Add((assembly, generation)))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var registeredCount = 0;
|
var registeredCount = 0;
|
||||||
var instances = new Dictionary<Type, object>();
|
foreach (var export in exports)
|
||||||
|
|
||||||
foreach (var type in assembly.GetTypes())
|
|
||||||
{
|
{
|
||||||
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
|
if (!_dispatchTable.TryAdd(export.Nid, export.Function))
|
||||||
{
|
{
|
||||||
var exportAttribute = method.GetCustomAttribute<SysAbiExportAttribute>(inherit: false);
|
Console.Error.WriteLine($"[HLE] Duplicate NID '{export.Nid}' ({export.Name}) — already registered, skipping.");
|
||||||
if (exportAttribute is null)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var exportInfo = ResolveExportInfo(exportAttribute, method, generation, symbolCatalog);
|
|
||||||
if (exportInfo is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = CreateHandler(type, method, instances);
|
|
||||||
if (!_dispatchTable.TryAdd(exportInfo.Value.Nid, handler))
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"[HLE] Duplicate NID '{exportInfo.Value.Nid}' ({exportInfo.Value.ExportName}) — already registered, skipping.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_exportTable[exportInfo.Value.Nid] = new ExportedFunction(
|
|
||||||
exportInfo.Value.LibraryName,
|
|
||||||
exportInfo.Value.Nid,
|
|
||||||
exportInfo.Value.ExportName,
|
|
||||||
exportInfo.Value.Target,
|
|
||||||
(SysAbiFunction)handler);
|
|
||||||
_exportNameTable.TryAdd(exportInfo.Value.ExportName, _exportTable[exportInfo.Value.Nid]);
|
|
||||||
|
|
||||||
registeredCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_exportTable[export.Nid] = export;
|
||||||
|
_exportNameTable.TryAdd(export.Name, export);
|
||||||
|
// The warm sweep in Freeze() covers every assembly that contributed a
|
||||||
|
// handler (generated thunks resolve to their home assembly too).
|
||||||
|
_warmupAssemblies.Add(export.Function.Method.Module.Assembly);
|
||||||
|
registeredCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return registeredCount;
|
return registeredCount;
|
||||||
@@ -92,7 +65,8 @@ public sealed class ModuleManager : IModuleManager
|
|||||||
Assembly[] assemblies;
|
Assembly[] assemblies;
|
||||||
lock (_registrationGate)
|
lock (_registrationGate)
|
||||||
{
|
{
|
||||||
assemblies = _scannedAssemblies.Select(entry => entry.Assembly).Distinct().ToArray();
|
assemblies = new Assembly[_warmupAssemblies.Count];
|
||||||
|
_warmupAssemblies.CopyTo(assemblies);
|
||||||
}
|
}
|
||||||
|
|
||||||
assemblies = WithGuestReachableDependencies(assemblies);
|
assemblies = WithGuestReachableDependencies(assemblies);
|
||||||
@@ -328,107 +302,4 @@ public sealed class ModuleManager : IModuleManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
|
|
||||||
{
|
|
||||||
ValidateSignature(method);
|
|
||||||
|
|
||||||
object? target = null;
|
|
||||||
if (!method.IsStatic)
|
|
||||||
{
|
|
||||||
if (!instances.TryGetValue(ownerType, out target))
|
|
||||||
{
|
|
||||||
target = Activator.CreateInstance(ownerType)
|
|
||||||
?? throw new InvalidOperationException($"Cannot instantiate module type: {ownerType.FullName}");
|
|
||||||
instances.Add(ownerType, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameterCount = method.GetParameters().Length;
|
|
||||||
if (parameterCount == 0)
|
|
||||||
{
|
|
||||||
var noArg = method.IsStatic
|
|
||||||
? (Func<int>)method.CreateDelegate(typeof(Func<int>))
|
|
||||||
: (Func<int>)method.CreateDelegate(typeof(Func<int>), target!);
|
|
||||||
|
|
||||||
SysAbiFunction adapter = _ => noArg();
|
|
||||||
return adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return method.IsStatic
|
|
||||||
? method.CreateDelegate(typeof(SysAbiFunction))
|
|
||||||
: method.CreateDelegate(typeof(SysAbiFunction), target!);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateSignature(MethodInfo method)
|
|
||||||
{
|
|
||||||
if (method.ReturnType != typeof(int))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Method {method.DeclaringType?.FullName}.{method.Name} must return int.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameters = method.GetParameters();
|
|
||||||
if (parameters.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(CpuContext))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Method {method.DeclaringType?.FullName}.{method.Name} must accept no arguments or one {nameof(CpuContext)} argument.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ExportInfo? ResolveExportInfo(
|
|
||||||
SysAbiExportAttribute exportAttribute,
|
|
||||||
MethodInfo method,
|
|
||||||
Generation generation,
|
|
||||||
ISymbolCatalog? symbolCatalog)
|
|
||||||
{
|
|
||||||
var target = exportAttribute.Target == Generation.None
|
|
||||||
? generation
|
|
||||||
: exportAttribute.Target;
|
|
||||||
if ((target & generation) == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nid = exportAttribute.Nid;
|
|
||||||
var exportName = exportAttribute.ExportName;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName) && symbolCatalog?.TryGetByExportName(exportName, out var byName) == true)
|
|
||||||
{
|
|
||||||
nid = byName.Nid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(nid) && symbolCatalog?.TryGetByNid(nid, out var byNid) == true)
|
|
||||||
{
|
|
||||||
exportName = string.IsNullOrWhiteSpace(exportName) ? byNid.ExportName : exportName;
|
|
||||||
target = exportAttribute.Target == Generation.None ? byNid.Target : target;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(nid))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Method {method.DeclaringType?.FullName}.{method.Name} must define a NID or match one in symbols catalog.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(exportName))
|
|
||||||
{
|
|
||||||
exportName = method.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((target & generation) == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryName = string.IsNullOrWhiteSpace(exportAttribute.LibraryName) ? "libKernel" : exportAttribute.LibraryName;
|
|
||||||
return new ExportInfo(nid, exportName, libraryName, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly record struct ExportInfo(string Nid, string ExportName, string LibraryName, Generation Target);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,51 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Aerolib\aerolib.bin" />
|
<!-- Forces build ordering for the aerolib task below; loaded as a build component,
|
||||||
|
never a runtime dependency. -->
|
||||||
|
<ProjectReference Include="..\SharpEmu.SourceGenerators\SharpEmu.SourceGenerators.csproj"
|
||||||
|
ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- aerolib.bin (the runtime NID -> name catalog) is derived data: generated here
|
||||||
|
from scripts/ps5_names.txt via the same Ps5Nid implementation the analyzers use,
|
||||||
|
embedded from the intermediate directory, and never committed. Replaces
|
||||||
|
scripts/generate_aerolib_binary.py. -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<SharpEmuAerolibNamesFile>$(MSBuildThisFileDirectory)..\..\scripts\ps5_names.txt</SharpEmuAerolibNamesFile>
|
||||||
|
<SharpEmuAerolibTaskAssembly>$(MSBuildThisFileDirectory)..\..\artifacts\bin\$(Configuration)\netstandard2.0\SharpEmu.SourceGenerators.dll</SharpEmuAerolibTaskAssembly>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<UsingTask TaskName="SharpEmu.SourceGenerators.GenerateAerolibBinaryTask"
|
||||||
|
TaskFactory="TaskHostFactory"
|
||||||
|
AssemblyFile="$(SharpEmuAerolibTaskAssembly)" />
|
||||||
|
|
||||||
|
<!-- Runs after ResolveProjectReferences (which builds the task assembly) and before
|
||||||
|
resource processing embeds the output. Skipped for design-time builds: the IDE
|
||||||
|
resolves project references without compiling them, so on a fresh clone the task
|
||||||
|
assembly does not exist yet and loading it would break the design-time build. -->
|
||||||
|
<Target Name="GenerateAerolibBinary"
|
||||||
|
DependsOnTargets="ResolveProjectReferences"
|
||||||
|
Inputs="$(SharpEmuAerolibNamesFile);$(SharpEmuAerolibTaskAssembly)"
|
||||||
|
Outputs="$(IntermediateOutputPath)aerolib.bin">
|
||||||
|
<GenerateAerolibBinaryTask NamesFile="$(SharpEmuAerolibNamesFile)"
|
||||||
|
OutputFile="$(IntermediateOutputPath)aerolib.bin" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- The resource item is created inside a target (not statically) so design-time
|
||||||
|
builds never reference a file that was not generated, and in a separate target
|
||||||
|
from the generation so an up-to-date skip of GenerateAerolibBinary cannot drop
|
||||||
|
the item (an up-to-date target skips its whole body, ItemGroups included).
|
||||||
|
Hooked before AssignTargetPaths: dynamic EmbeddedResource items added later
|
||||||
|
than that miss the resource pipeline entirely. -->
|
||||||
|
<Target Name="EmbedAerolibBinary"
|
||||||
|
BeforeTargets="AssignTargetPaths"
|
||||||
|
DependsOnTargets="GenerateAerolibBinary"
|
||||||
|
Condition="'$(DesignTimeBuild)' != 'true'">
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="$(IntermediateOutputPath)aerolib.bin"
|
||||||
|
LogicalName="SharpEmu.HLE.Aerolib.aerolib.bin"
|
||||||
|
Visible="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -429,6 +429,8 @@ public static class AgcExports
|
|||||||
|
|
||||||
private sealed record RegisterDefaultsAllocation(ulong Primary, ulong Internal);
|
private sealed record RegisterDefaultsAllocation(ulong Primary, ulong Internal);
|
||||||
|
|
||||||
|
// NID captured from shipped titles; 'sceAgcInit' is a working label that collides with a real catalog symbol of a different NID. Rename pending AGC API confirmation.
|
||||||
|
#pragma warning disable SHEM004
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "23LRUSvYu1M",
|
Nid = "23LRUSvYu1M",
|
||||||
ExportName = "sceAgcInit",
|
ExportName = "sceAgcInit",
|
||||||
@@ -446,6 +448,7 @@ public static class AgcExports
|
|||||||
TraceAgc($"agc.init state=0x{stateAddress:X16} version={version}");
|
TraceAgc($"agc.init state=0x{stateAddress:X16} version={version}");
|
||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM004
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "2JtWUUiYBXs",
|
Nid = "2JtWUUiYBXs",
|
||||||
@@ -625,6 +628,8 @@ public static class AgcExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NID captured from shipped titles; the friendly name collides with a real catalog symbol of a different NID. Rename pending AGC API confirmation.
|
||||||
|
#pragma warning disable SHEM004
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "HV4j+E0MBHE",
|
Nid = "HV4j+E0MBHE",
|
||||||
ExportName = "sceAgcCreateInterpolantMapping",
|
ExportName = "sceAgcCreateInterpolantMapping",
|
||||||
@@ -682,7 +687,10 @@ public static class AgcExports
|
|||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM004
|
||||||
|
|
||||||
|
// NID captured from shipped titles; the friendly name collides with a real catalog symbol of a different NID. Rename pending AGC API confirmation.
|
||||||
|
#pragma warning disable SHEM004
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "V++UgBtQhn0",
|
Nid = "V++UgBtQhn0",
|
||||||
ExportName = "sceAgcGetDataPacketPayloadAddress",
|
ExportName = "sceAgcGetDataPacketPayloadAddress",
|
||||||
@@ -726,6 +734,7 @@ public static class AgcExports
|
|||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM004
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "LtTouSCZjHM",
|
Nid = "LtTouSCZjHM",
|
||||||
@@ -2546,6 +2555,8 @@ public static class AgcExports
|
|||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synthetic label for an uncatalogued NID (the Unknown* convention); the NID is authoritative.
|
||||||
|
#pragma warning disable SHEM006
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "-KRzWekV120",
|
Nid = "-KRzWekV120",
|
||||||
ExportName = "sceAgcDriverUnknown_KRzWekV120",
|
ExportName = "sceAgcDriverUnknown_KRzWekV120",
|
||||||
@@ -2560,6 +2571,7 @@ public static class AgcExports
|
|||||||
|
|
||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM006
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "h9z6+0hEydk",
|
Nid = "h9z6+0hEydk",
|
||||||
@@ -2573,6 +2585,8 @@ public static class AgcExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synthetic label for an uncatalogued NID (the Unknown* convention); the NID is authoritative.
|
||||||
|
#pragma warning disable SHEM006
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "qj7QZpgr9Uw",
|
Nid = "qj7QZpgr9Uw",
|
||||||
ExportName = "sceAgcUnknownQj7QZpgr9Uw",
|
ExportName = "sceAgcUnknownQj7QZpgr9Uw",
|
||||||
@@ -2593,6 +2607,7 @@ public static class AgcExports
|
|||||||
$"arg1=0x{ctx[CpuRegister.Rsi]:X16} arg2=0x{ctx[CpuRegister.Rdx]:X16}");
|
$"arg1=0x{ctx[CpuRegister.Rsi]:X16} arg2=0x{ctx[CpuRegister.Rdx]:X16}");
|
||||||
return ReturnPointer(ctx, commandAddress);
|
return ReturnPointer(ctx, commandAddress);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM006
|
||||||
|
|
||||||
// WAIT_REG_MEM packets whose condition is not met suspend their DCB into
|
// WAIT_REG_MEM packets whose condition is not met suspend their DCB into
|
||||||
// GpuWaitRegistry. Each submit re-checks every suspended DCB against current guest
|
// GpuWaitRegistry. Each submit re-checks every suspended DCB against current guest
|
||||||
|
|||||||
@@ -48,19 +48,25 @@ public static class DiscMapExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synthetic label for an uncatalogued NID (the Unknown* convention); the NID is authoritative.
|
||||||
|
#pragma warning disable SHEM006
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "fJgP+wqifno",
|
Nid = "fJgP+wqifno",
|
||||||
ExportName = "sceDiscMapUnknownFJgP",
|
ExportName = "sceDiscMapUnknownFJgP",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libSceDiscMap")]
|
LibraryName = "libSceDiscMap")]
|
||||||
public static int DiscMapUnknownFJgP(CpuContext ctx) => WriteMappingTriple(ctx, "sceDiscMapUnknownFJgP");
|
public static int DiscMapUnknownFJgP(CpuContext ctx) => WriteMappingTriple(ctx, "sceDiscMapUnknownFJgP");
|
||||||
|
#pragma warning restore SHEM006
|
||||||
|
|
||||||
|
// Synthetic label for an uncatalogued NID (the Unknown* convention); the NID is authoritative.
|
||||||
|
#pragma warning disable SHEM006
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "ioKMruft1ek",
|
Nid = "ioKMruft1ek",
|
||||||
ExportName = "sceDiscMapUnknownIoKM",
|
ExportName = "sceDiscMapUnknownIoKM",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libSceDiscMap")]
|
LibraryName = "libSceDiscMap")]
|
||||||
public static int DiscMapUnknownIoKM(CpuContext ctx) => WriteMappingTriple(ctx, "sceDiscMapUnknownIoKM");
|
public static int DiscMapUnknownIoKM(CpuContext ctx) => WriteMappingTriple(ctx, "sceDiscMapUnknownIoKM");
|
||||||
|
#pragma warning restore SHEM006
|
||||||
|
|
||||||
private static int WriteMappingTriple(CpuContext ctx, string exportName)
|
private static int WriteMappingTriple(CpuContext ctx, string exportName)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1816,6 +1816,8 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
var pathAddress = ctx[CpuRegister.Rdi];
|
var pathAddress = ctx[CpuRegister.Rdi];
|
||||||
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
|
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||||
|
// Not migratable to [GuestCString]: the local reader's TryReadCompat host-memory
|
||||||
|
// fallback recovers paths in loader-mapped regions that ctx.Memory cannot see.
|
||||||
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
|
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
|||||||
@@ -259,11 +259,8 @@ public static class KernelSemaphoreCompatExports
|
|||||||
ExportName = "sceKernelPollSema",
|
ExportName = "sceKernelPollSema",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int KernelPollSema(CpuContext ctx)
|
public static int KernelPollSema(CpuContext ctx, uint handle, int needCount)
|
||||||
{
|
{
|
||||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
|
||||||
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
|
||||||
|
|
||||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||||
{
|
{
|
||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||||
@@ -299,11 +296,8 @@ public static class KernelSemaphoreCompatExports
|
|||||||
ExportName = "sceKernelSignalSema",
|
ExportName = "sceKernelSignalSema",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int KernelSignalSema(CpuContext ctx)
|
public static int KernelSignalSema(CpuContext ctx, uint handle, int signalCount)
|
||||||
{
|
{
|
||||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
|
||||||
var signalCount = unchecked((int)ctx[CpuRegister.Rsi]);
|
|
||||||
|
|
||||||
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||||
{
|
{
|
||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||||
@@ -341,12 +335,8 @@ public static class KernelSemaphoreCompatExports
|
|||||||
ExportName = "sceKernelCancelSema",
|
ExportName = "sceKernelCancelSema",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int KernelCancelSema(CpuContext ctx)
|
public static int KernelCancelSema(CpuContext ctx, uint handle, int setCount, ulong waitingThreadsAddress)
|
||||||
{
|
{
|
||||||
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))
|
if (!_semaphores.TryGetValue(handle, out var semaphore))
|
||||||
{
|
{
|
||||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
|
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
|
||||||
<ProjectReference Include="..\SharpEmu.ShaderCompiler\SharpEmu.ShaderCompiler.csproj" />
|
<ProjectReference Include="..\SharpEmu.ShaderCompiler\SharpEmu.ShaderCompiler.csproj" />
|
||||||
<ProjectReference Include="..\SharpEmu.ShaderCompiler.Vulkan\SharpEmu.ShaderCompiler.Vulkan.csproj" />
|
<ProjectReference Include="..\SharpEmu.ShaderCompiler.Vulkan\SharpEmu.ShaderCompiler.Vulkan.csproj" />
|
||||||
|
<!-- SysAbi export generator + analyzers (compile-time registry, NID validation). -->
|
||||||
|
<ProjectReference Include="..\SharpEmu.SourceGenerators\SharpEmu.SourceGenerators.csproj"
|
||||||
|
OutputItemType="Analyzer"
|
||||||
|
ReferenceOutputAssembly="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- The PS5 symbol catalog: lets the analyzer flag export names it doesn't know. -->
|
||||||
|
<AdditionalFiles Include="..\..\scripts\ps5_names.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ public static class UserServiceExports
|
|||||||
return ctx.SetReturn(0);
|
return ctx.SetReturn(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name not yet in ps5_names.txt and the NID was captured from titles; revisit when the symbol is catalogued.
|
||||||
|
#pragma warning disable SHEM006
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "D-CzAxQL0XI",
|
Nid = "D-CzAxQL0XI",
|
||||||
ExportName = "sceUserServiceGetPlatformPrivacySetting",
|
ExportName = "sceUserServiceGetPlatformPrivacySetting",
|
||||||
@@ -184,6 +186,7 @@ public static class UserServiceExports
|
|||||||
? ctx.SetReturn(0)
|
? ctx.SetReturn(0)
|
||||||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SHEM006
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "woNpu+45RLk",
|
Nid = "woNpu+45RLk",
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"Silk.NET.Input": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "Xzl+tVwAp2eEd8blmGQjmJrsZPnp3PWG0KJjiAQHaY2Zr/ELVWeAROKXmZdCAvexzmte2JVGEy/dxnMycbxlpg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Input.Glfw": "2.23.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.EXT": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Core": "2.23.0",
|
|
||||||
"Silk.NET.Vulkan": "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.Input.Common": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "QbJVV7kFBHEByayXCYdJtXXI9Sp4a+QAf0IdGV6uCWkFYcEmqBYW3aaNGvFOdSwTBDbHL5T/OtOCrGh4qYhk7A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Windowing.Common": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Silk.NET.Input.Glfw": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "KGHYqsv/IQRJtD6dloYh2tN4CkaM40vxM2kj0cGKBoCQiBDYHHhJiyDTyMPx0W7Fz5IgnhnG42ELmIAa0DH69A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Windowing.Glfw": "2.23.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",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler.vulkan": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"sharpemu.hle": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"sharpemu.hle": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
99
src/SharpEmu.SourceGenerators/GenerateAerolibBinaryTask.cs
Normal file
99
src/SharpEmu.SourceGenerators/GenerateAerolibBinaryTask.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds aerolib.bin (the runtime NID -> name catalog) from scripts/ps5_names.txt at
|
||||||
|
/// build time, replacing scripts/generate_aerolib_binary.py and the committed binary.
|
||||||
|
/// Format matches the python script exactly: uint32 entry count, then per entry a
|
||||||
|
/// byte-length-prefixed NID and a ushort-length-prefixed name, little-endian UTF-8.
|
||||||
|
///
|
||||||
|
/// Implements ITask directly (Framework-only reference) and is an MSBuild task, not an
|
||||||
|
/// analyzer — the file-IO ban that RS1035 enforces for compiler-loaded code does not
|
||||||
|
/// apply to build tasks, hence the targeted suppressions.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GenerateAerolibBinaryTask : ITask
|
||||||
|
{
|
||||||
|
public IBuildEngine? BuildEngine { get; set; }
|
||||||
|
|
||||||
|
public ITaskHost? HostObject { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string NamesFile { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string OutputFile { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
#pragma warning disable RS1035 // File IO is the entire point of this build task.
|
||||||
|
public bool Execute()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
foreach (var line in File.ReadAllLines(NamesFile))
|
||||||
|
{
|
||||||
|
var name = line.Trim();
|
||||||
|
if (name.Length != 0)
|
||||||
|
{
|
||||||
|
names.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputDirectory = Path.GetDirectoryName(OutputFile);
|
||||||
|
if (!string.IsNullOrEmpty(outputDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outputDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var sha1 = System.Security.Cryptography.SHA1.Create())
|
||||||
|
using (var stream = File.Create(OutputFile))
|
||||||
|
using (var writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
writer.Write((uint)names.Count);
|
||||||
|
foreach (var name in names)
|
||||||
|
{
|
||||||
|
var nidBytes = Encoding.UTF8.GetBytes(Ps5Nid.Compute(name, sha1));
|
||||||
|
var nameBytes = Encoding.UTF8.GetBytes(name);
|
||||||
|
if (nameBytes.Length > ushort.MaxValue)
|
||||||
|
{
|
||||||
|
// A silent (ushort) truncation would corrupt the catalog.
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"Symbol name exceeds the format's ushort length prefix ({nameBytes.Length} bytes): '{name.Substring(0, 64)}...'");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write((byte)nidBytes.Length);
|
||||||
|
writer.Write(nidBytes);
|
||||||
|
writer.Write((ushort)nameBytes.Length);
|
||||||
|
writer.Write(nameBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildEngine?.LogMessageEvent(new BuildMessageEventArgs(
|
||||||
|
$"aerolib: {names.Count} symbols -> {OutputFile}",
|
||||||
|
helpKeyword: null,
|
||||||
|
senderName: nameof(GenerateAerolibBinaryTask),
|
||||||
|
MessageImportance.Normal));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
BuildEngine?.LogErrorEvent(new BuildErrorEventArgs(
|
||||||
|
subcategory: null,
|
||||||
|
code: "SHEMAERO",
|
||||||
|
file: NamesFile,
|
||||||
|
lineNumber: 0,
|
||||||
|
columnNumber: 0,
|
||||||
|
endLineNumber: 0,
|
||||||
|
endColumnNumber: 0,
|
||||||
|
message: exception.ToString(),
|
||||||
|
helpKeyword: null,
|
||||||
|
senderName: nameof(GenerateAerolibBinaryTask)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore RS1035
|
||||||
|
}
|
||||||
75
src/SharpEmu.SourceGenerators/Ps5Nid.cs
Normal file
75
src/SharpEmu.SourceGenerators/Ps5Nid.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The PS NID derivation: base64 (with the '+','-' alphabet, no padding) of the
|
||||||
|
/// byte-reversed first eight bytes of SHA1(symbolName + fixed suffix). The same
|
||||||
|
/// algorithm scripts/generate_aerolib_binary.py uses to build the runtime catalog,
|
||||||
|
/// in C# so the analyzer and generator can validate and derive NIDs at compile time.
|
||||||
|
/// </summary>
|
||||||
|
public static class Ps5Nid
|
||||||
|
{
|
||||||
|
private static readonly byte[] Suffix =
|
||||||
|
[
|
||||||
|
0x51, 0x8D, 0x64, 0xA6, 0x35, 0xDE, 0xD8, 0xC1,
|
||||||
|
0xE6, 0xB0, 0x39, 0xB1, 0xC3, 0xE5, 0x52, 0x30,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static string Compute(string symbolName)
|
||||||
|
{
|
||||||
|
using var sha1 = SHA1.Create();
|
||||||
|
return Compute(symbolName, sha1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bulk-callable overload: the aerolib build task hashes every catalog name
|
||||||
|
/// (~150k), so the caller owns one SHA1 instance instead of churning one per name.
|
||||||
|
/// </summary>
|
||||||
|
public static string Compute(string symbolName, SHA1 sha1)
|
||||||
|
{
|
||||||
|
var nameBytes = Encoding.UTF8.GetBytes(symbolName);
|
||||||
|
var input = new byte[nameBytes.Length + Suffix.Length];
|
||||||
|
nameBytes.CopyTo(input, 0);
|
||||||
|
Suffix.CopyTo(input, nameBytes.Length);
|
||||||
|
|
||||||
|
var hash = sha1.ComputeHash(input);
|
||||||
|
|
||||||
|
// The script reads the first eight bytes as a little-endian integer and
|
||||||
|
// formats it big-endian before encoding — a byte reversal.
|
||||||
|
var reversed = new byte[8];
|
||||||
|
for (var index = 0; index < 8; index++)
|
||||||
|
{
|
||||||
|
reversed[index] = hash[7 - index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToBase64String(reversed)
|
||||||
|
.TrimEnd('=')
|
||||||
|
.Replace('/', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Eleven characters of the '+','-' base64 alphabet.</summary>
|
||||||
|
public static bool IsValidFormat(string nid)
|
||||||
|
{
|
||||||
|
if (nid.Length != 11)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var character in nid)
|
||||||
|
{
|
||||||
|
var valid = character is (>= 'A' and <= 'Z') or (>= 'a' and <= 'z') or
|
||||||
|
(>= '0' and <= '9') or '+' or '-';
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- Roslyn components (source generator + analyzers) for the SysAbi export system.
|
||||||
|
Must target netstandard2.0 to load inside the compiler. Consumed by the emulator
|
||||||
|
projects as an analyzer reference (OutputItemType="Analyzer"), never at runtime. -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
|
<!-- RS2008: analyzer release tracking files are overkill for an in-repo analyzer. -->
|
||||||
|
<NoWarn>$(NoWarn);RS2008</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Build.Framework" PrivateAssets="all" ExcludeAssets="runtime" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
80
src/SharpEmu.SourceGenerators/SysAbiDiagnostics.cs
Normal file
80
src/SharpEmu.SourceGenerators/SysAbiDiagnostics.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compile-time rules for [SysAbiExport] declarations. Everything here used to be a
|
||||||
|
/// runtime discovery (console warning or InvalidOperationException at boot); the
|
||||||
|
/// analyzer turns each one into a build failure.
|
||||||
|
/// </summary>
|
||||||
|
public static class SysAbiDiagnostics
|
||||||
|
{
|
||||||
|
private const string Category = "SharpEmu.SysAbi";
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor DuplicateNid = new(
|
||||||
|
"SHEM001",
|
||||||
|
"Duplicate SysAbi NID",
|
||||||
|
"NID '{0}' is exported by both '{1}' and '{2}'",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor InvalidNidFormat = new(
|
||||||
|
"SHEM002",
|
||||||
|
"Invalid NID format",
|
||||||
|
"NID '{0}' is not eleven characters of the PS base64 alphabet (A-Z a-z 0-9 + -)",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor InvalidHandlerSignature = new(
|
||||||
|
"SHEM003",
|
||||||
|
"Invalid SysAbi handler signature",
|
||||||
|
"Method '{0}' must be a static, non-generic method returning int and taking no parameters, a single CpuContext parameter, or a CpuContext followed by up to six int/uint/long/ulong or [GuestCString] string parameters",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor NidNameMismatch = new(
|
||||||
|
"SHEM004",
|
||||||
|
"NID does not match export name",
|
||||||
|
"NID '{0}' does not match export name '{1}' (computed NID is '{2}') — one of the two is wrong",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor UnresolvableExport = new(
|
||||||
|
"SHEM005",
|
||||||
|
"Export declares neither NID nor export name",
|
||||||
|
"Method '{0}' must declare an ExportName (from which the NID is derived) or an explicit Nid",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor NameNotInCatalog = new(
|
||||||
|
"SHEM006",
|
||||||
|
"Export name not present in the PS5 symbol catalog",
|
||||||
|
"Export name '{0}' is not in ps5_names.txt — likely a typo, or the catalog needs the new symbol",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor HandlerNotAccessible = new(
|
||||||
|
"SHEM007",
|
||||||
|
"SysAbi handler not accessible to generated registration",
|
||||||
|
"Method '{0}' (or its containing type) must be at least internal so the generated registry can reference it",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor InvalidGuestCString = new(
|
||||||
|
"SHEM008",
|
||||||
|
"Invalid [GuestCString] usage",
|
||||||
|
"Method '{0}' misuses [GuestCString]: it applies only to string parameters and requires a positive MaxLength",
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
}
|
||||||
202
src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs
Normal file
202
src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build-time enforcement for [SysAbiExport] declarations: duplicate NIDs, malformed
|
||||||
|
/// NIDs, NIDs that contradict their export name (checked with the PS NID computation),
|
||||||
|
/// handler signatures the dispatcher cannot call, and — when scripts/ps5_names.txt is
|
||||||
|
/// wired up as an AdditionalFile — export names unknown to the symbol catalog.
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class SysAbiExportAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
private const string CatalogFileName = "ps5_names.txt";
|
||||||
|
|
||||||
|
// The catalog is ~150k lines; parse it once per file snapshot instead of on every
|
||||||
|
// compilation start (the IDE creates one per keystroke). A changed file arrives as
|
||||||
|
// a fresh AdditionalText instance, which naturally misses the cache.
|
||||||
|
private static readonly System.Runtime.CompilerServices.ConditionalWeakTable<AdditionalText, CatalogHolder> _catalogCache = new();
|
||||||
|
|
||||||
|
private sealed class CatalogHolder
|
||||||
|
{
|
||||||
|
public CatalogHolder(HashSet<string>? names) => Names = names;
|
||||||
|
|
||||||
|
public HashSet<string>? Names { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
|
||||||
|
[
|
||||||
|
SysAbiDiagnostics.DuplicateNid,
|
||||||
|
SysAbiDiagnostics.InvalidNidFormat,
|
||||||
|
SysAbiDiagnostics.InvalidHandlerSignature,
|
||||||
|
SysAbiDiagnostics.NidNameMismatch,
|
||||||
|
SysAbiDiagnostics.UnresolvableExport,
|
||||||
|
SysAbiDiagnostics.NameNotInCatalog,
|
||||||
|
SysAbiDiagnostics.HandlerNotAccessible,
|
||||||
|
SysAbiDiagnostics.InvalidGuestCString,
|
||||||
|
];
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.RegisterCompilationStartAction(static startContext =>
|
||||||
|
{
|
||||||
|
var catalogNames = LoadCatalog(startContext.Options.AdditionalFiles, startContext.CancellationToken);
|
||||||
|
var exportsByNid = new ConcurrentDictionary<string, IMethodSymbol>();
|
||||||
|
|
||||||
|
startContext.RegisterSymbolAction(
|
||||||
|
symbolContext => AnalyzeMethod(symbolContext, catalogNames, exportsByNid),
|
||||||
|
SymbolKind.Method);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string>? LoadCatalog(
|
||||||
|
ImmutableArray<AdditionalText> additionalFiles,
|
||||||
|
System.Threading.CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
foreach (var file in additionalFiles)
|
||||||
|
{
|
||||||
|
if (!file.Path.EndsWith(CatalogFileName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _catalogCache.GetValue(file, static f => new CatalogHolder(ParseCatalog(f))).Names;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string>? ParseCatalog(AdditionalText file)
|
||||||
|
{
|
||||||
|
var text = file.GetText();
|
||||||
|
if (text is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var line in text.Lines)
|
||||||
|
{
|
||||||
|
var name = line.ToString().Trim();
|
||||||
|
if (name.Length != 0)
|
||||||
|
{
|
||||||
|
names.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeMethod(
|
||||||
|
SymbolAnalysisContext context,
|
||||||
|
HashSet<string>? catalogNames,
|
||||||
|
ConcurrentDictionary<string, IMethodSymbol> exportsByNid)
|
||||||
|
{
|
||||||
|
var method = (IMethodSymbol)context.Symbol;
|
||||||
|
AttributeData? exportAttribute = null;
|
||||||
|
foreach (var attribute in method.GetAttributes())
|
||||||
|
{
|
||||||
|
if (SysAbiExportShape.IsSysAbiExportAttribute(attribute.AttributeClass))
|
||||||
|
{
|
||||||
|
exportAttribute = attribute;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportAttribute is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = method.Locations.Length != 0 ? method.Locations[0] : Location.None;
|
||||||
|
var methodDisplay = $"{method.ContainingType.ToDisplayString()}.{method.Name}";
|
||||||
|
|
||||||
|
if (SysAbiExportShape.Classify(method, out _, out var invalidGuestCString) == SysAbiExportShape.HandlerShape.Invalid)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
invalidGuestCString ? SysAbiDiagnostics.InvalidGuestCString : SysAbiDiagnostics.InvalidHandlerSignature,
|
||||||
|
location,
|
||||||
|
methodDisplay));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SysAbiExportShape.IsAccessibleFromGeneratedCode(method))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.HandlerNotAccessible, location, methodDisplay));
|
||||||
|
}
|
||||||
|
|
||||||
|
var arguments = SysAbiExportShape.ReadArguments(exportAttribute);
|
||||||
|
var hasNid = !string.IsNullOrWhiteSpace(arguments.Nid);
|
||||||
|
var hasName = !string.IsNullOrWhiteSpace(arguments.ExportName);
|
||||||
|
|
||||||
|
if (!hasNid && !hasName)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.UnresolvableExport, location, methodDisplay));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNid && !Ps5Nid.IsValidFormat(arguments.Nid))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.InvalidNidFormat, location, arguments.Nid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveNid = arguments.Nid;
|
||||||
|
if (hasName)
|
||||||
|
{
|
||||||
|
var computed = Ps5Nid.Compute(arguments.ExportName);
|
||||||
|
|
||||||
|
var nameInCatalog = catalogNames is not null && catalogNames.Contains(arguments.ExportName);
|
||||||
|
|
||||||
|
// A declared NID that contradicts its name is only provably wrong when the
|
||||||
|
// name is a real catalog symbol. Names outside the catalog are synthetic
|
||||||
|
// labels for NIDs whose true symbol is unknown (the "sceAgcUnknown..."
|
||||||
|
// convention) — for those the NID is authoritative and only SHEM006 applies.
|
||||||
|
// With no catalog wired, every name is validated (fail closed).
|
||||||
|
var nameIsKnown = catalogNames is null || nameInCatalog;
|
||||||
|
if (hasNid && nameIsKnown && !string.Equals(computed, arguments.Nid, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.NidNameMismatch,
|
||||||
|
location,
|
||||||
|
arguments.Nid,
|
||||||
|
arguments.ExportName,
|
||||||
|
computed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasNid)
|
||||||
|
{
|
||||||
|
effectiveNid = computed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalogNames is not null && !nameInCatalog)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.NameNotInCatalog, location, arguments.ExportName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = exportsByNid.GetOrAdd(effectiveNid, method);
|
||||||
|
if (!SymbolEqualityComparer.Default.Equals(existing, method))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
SysAbiDiagnostics.DuplicateNid,
|
||||||
|
location,
|
||||||
|
effectiveNid,
|
||||||
|
$"{existing.ContainingType.ToDisplayString()}.{existing.Name}",
|
||||||
|
methodDisplay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
273
src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs
Normal file
273
src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits the SysAbi export registry at compile time: one static class per assembly whose
|
||||||
|
/// CreateExports(Generation) lists every [SysAbiExport] handler — generation filtering,
|
||||||
|
/// name fallback, and library default preserved from the retired reflection scan. NIDs
|
||||||
|
/// omitted from attributes are derived from the export name with the PS NID algorithm
|
||||||
|
/// (the same computation the runtime symbol catalog was built from). Handlers written
|
||||||
|
/// with typed signatures get a SysV register-unmarshalling thunk emitted here.
|
||||||
|
///
|
||||||
|
/// Invalid declarations are skipped here and rejected by SysAbiExportAnalyzer as build
|
||||||
|
/// errors, so nothing can be silently dropped.
|
||||||
|
/// </summary>
|
||||||
|
[Generator]
|
||||||
|
public sealed class SysAbiExportGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
private const string AttributeMetadataName = SysAbiExportShape.SysAbiExportAttributeName;
|
||||||
|
|
||||||
|
private sealed class ExportModel : IEquatable<ExportModel>
|
||||||
|
{
|
||||||
|
public ExportModel(string containingType, string methodName, SysAbiExportShape.HandlerShape shape, string typedParameterKinds, string libraryName, string nid, string exportName, int target)
|
||||||
|
{
|
||||||
|
ContainingType = containingType;
|
||||||
|
MethodName = methodName;
|
||||||
|
Shape = shape;
|
||||||
|
TypedParameterKinds = typedParameterKinds;
|
||||||
|
LibraryName = libraryName;
|
||||||
|
Nid = nid;
|
||||||
|
ExportName = exportName;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContainingType { get; }
|
||||||
|
public string MethodName { get; }
|
||||||
|
public SysAbiExportShape.HandlerShape Shape { get; }
|
||||||
|
|
||||||
|
// Deliberately a comma-joined string ("uint,int,cstring:4096") rather than an
|
||||||
|
// array: the model must be equatable for incremental-generator caching, and a
|
||||||
|
// string gets that for free where an array would need a custom comparer.
|
||||||
|
public string TypedParameterKinds { get; }
|
||||||
|
public string LibraryName { get; }
|
||||||
|
public string Nid { get; }
|
||||||
|
public string ExportName { get; }
|
||||||
|
public int Target { get; }
|
||||||
|
|
||||||
|
public bool Equals(ExportModel? other) =>
|
||||||
|
other is not null &&
|
||||||
|
ContainingType == other.ContainingType &&
|
||||||
|
MethodName == other.MethodName &&
|
||||||
|
Shape == other.Shape &&
|
||||||
|
TypedParameterKinds == other.TypedParameterKinds &&
|
||||||
|
LibraryName == other.LibraryName &&
|
||||||
|
Nid == other.Nid &&
|
||||||
|
ExportName == other.ExportName &&
|
||||||
|
Target == other.Target;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => Equals(obj as ExportModel);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
var hash = 17;
|
||||||
|
hash = (hash * 31) + ContainingType.GetHashCode();
|
||||||
|
hash = (hash * 31) + MethodName.GetHashCode();
|
||||||
|
hash = (hash * 31) + Nid.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
var exports = context.SyntaxProvider
|
||||||
|
.ForAttributeWithMetadataName(
|
||||||
|
AttributeMetadataName,
|
||||||
|
static (node, _) => node is MethodDeclarationSyntax,
|
||||||
|
static (attributeContext, _) => CreateModel(attributeContext))
|
||||||
|
.Where(static model => model is not null)
|
||||||
|
.Collect();
|
||||||
|
|
||||||
|
var assemblyName = context.CompilationProvider
|
||||||
|
.Select(static (compilation, _) => compilation.AssemblyName ?? "Assembly");
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(
|
||||||
|
exports.Combine(assemblyName),
|
||||||
|
static (productionContext, source) => Emit(productionContext, source.Left!, source.Right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExportModel? CreateModel(GeneratorAttributeSyntaxContext context)
|
||||||
|
{
|
||||||
|
if (context.TargetSymbol is not IMethodSymbol method ||
|
||||||
|
!SysAbiExportShape.IsAccessibleFromGeneratedCode(method))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shape = SysAbiExportShape.Classify(method, out var typedParameterKinds);
|
||||||
|
if (shape == SysAbiExportShape.HandlerShape.Invalid)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attribute = context.Attributes[0];
|
||||||
|
var arguments = SysAbiExportShape.ReadArguments(attribute);
|
||||||
|
var nid = arguments.Nid;
|
||||||
|
var exportName = arguments.ExportName;
|
||||||
|
|
||||||
|
// Mirror ModuleManager.ResolveExportInfo: a missing NID resolves from the export
|
||||||
|
// name (algorithmically — equivalent to the runtime catalog lookup, which was
|
||||||
|
// built with the same computation); a missing name falls back to the method name.
|
||||||
|
if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName))
|
||||||
|
{
|
||||||
|
nid = Ps5Nid.Compute(exportName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(nid))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(exportName))
|
||||||
|
{
|
||||||
|
exportName = method.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
var libraryName = string.IsNullOrWhiteSpace(arguments.LibraryName) ? "libKernel" : arguments.LibraryName;
|
||||||
|
return new ExportModel(
|
||||||
|
method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
|
method.Name,
|
||||||
|
shape,
|
||||||
|
typedParameterKinds,
|
||||||
|
libraryName,
|
||||||
|
nid!,
|
||||||
|
exportName!,
|
||||||
|
arguments.Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Emit(
|
||||||
|
SourceProductionContext context,
|
||||||
|
ImmutableArray<ExportModel?> exports,
|
||||||
|
string assemblyName)
|
||||||
|
{
|
||||||
|
// No exports, no registry: an assembly that merely references the analyzer
|
||||||
|
// (e.g. SharpEmu.HLE itself) must not mint a colliding
|
||||||
|
// SharpEmu.Generated.SysAbiExportRegistry type.
|
||||||
|
if (exports.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("// <auto-generated by SharpEmu.SourceGenerators/SysAbiExportGenerator />");
|
||||||
|
builder.AppendLine("#nullable enable");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("namespace SharpEmu.Generated;");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("/// <summary>Compile-time SysAbi export registry for " + assemblyName + ".</summary>");
|
||||||
|
builder.AppendLine("public static class SysAbiExportRegistry");
|
||||||
|
builder.AppendLine("{");
|
||||||
|
builder.AppendLine(" /// <summary>");
|
||||||
|
builder.AppendLine(" /// Exports effective for the given registration generation, with the same");
|
||||||
|
builder.AppendLine(" /// semantics as the reflection scan: an attribute Target of None inherits the");
|
||||||
|
builder.AppendLine(" /// registration generation, and exports outside it are skipped.");
|
||||||
|
builder.AppendLine(" /// </summary>");
|
||||||
|
builder.AppendLine(" public static global::System.Collections.Generic.IReadOnlyList<global::SharpEmu.HLE.ExportedFunction> CreateExports(");
|
||||||
|
builder.AppendLine(" global::SharpEmu.HLE.Generation registrationGeneration)");
|
||||||
|
builder.AppendLine(" {");
|
||||||
|
builder.AppendLine($" var exports = new global::System.Collections.Generic.List<global::SharpEmu.HLE.ExportedFunction>({exports.Length});");
|
||||||
|
|
||||||
|
foreach (var export in exports)
|
||||||
|
{
|
||||||
|
if (export is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var function = export.Shape switch
|
||||||
|
{
|
||||||
|
SysAbiExportShape.HandlerShape.ContextOnly => $"{export.ContainingType}.{export.MethodName}",
|
||||||
|
SysAbiExportShape.HandlerShape.Parameterless => $"static _ => {export.ContainingType}.{export.MethodName}()",
|
||||||
|
_ => TypedThunk(export),
|
||||||
|
};
|
||||||
|
builder.AppendLine(
|
||||||
|
$" Add(exports, registrationGeneration, {Literal(export.LibraryName)}, {Literal(export.Nid)}, " +
|
||||||
|
$"{Literal(export.ExportName)}, (global::SharpEmu.HLE.Generation){export.Target}, {function});");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(" return exports;");
|
||||||
|
builder.AppendLine(" }");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine(" private static void Add(");
|
||||||
|
builder.AppendLine(" global::System.Collections.Generic.List<global::SharpEmu.HLE.ExportedFunction> exports,");
|
||||||
|
builder.AppendLine(" global::SharpEmu.HLE.Generation registrationGeneration,");
|
||||||
|
builder.AppendLine(" string libraryName,");
|
||||||
|
builder.AppendLine(" string nid,");
|
||||||
|
builder.AppendLine(" string exportName,");
|
||||||
|
builder.AppendLine(" global::SharpEmu.HLE.Generation attributeTarget,");
|
||||||
|
builder.AppendLine(" global::SharpEmu.HLE.SysAbiFunction function)");
|
||||||
|
builder.AppendLine(" {");
|
||||||
|
builder.AppendLine(" var target = attributeTarget == global::SharpEmu.HLE.Generation.None ? registrationGeneration : attributeTarget;");
|
||||||
|
builder.AppendLine(" if ((target & registrationGeneration) == 0)");
|
||||||
|
builder.AppendLine(" {");
|
||||||
|
builder.AppendLine(" return;");
|
||||||
|
builder.AppendLine(" }");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine(" exports.Add(new global::SharpEmu.HLE.ExportedFunction(libraryName, nid, exportName, target, function));");
|
||||||
|
builder.AppendLine(" }");
|
||||||
|
builder.AppendLine("}");
|
||||||
|
|
||||||
|
context.AddSource("SysAbiExportRegistry.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SysV integer-register unmarshalling: parameter i reads argument register i as a
|
||||||
|
/// raw ulong and reinterprets it with an unchecked cast, exactly the idiom
|
||||||
|
/// hand-written handlers use today. [GuestCString] parameters read the register as
|
||||||
|
/// a guest pointer and marshal the null-terminated UTF-8 string up front, failing
|
||||||
|
/// the call with ORBIS_GEN2_ERROR_MEMORY_FAULT before the handler runs.
|
||||||
|
/// </summary>
|
||||||
|
private static string TypedThunk(ExportModel export)
|
||||||
|
{
|
||||||
|
var kinds = export.TypedParameterKinds.Split(',');
|
||||||
|
var arguments = new string[kinds.Length];
|
||||||
|
var reads = new StringBuilder();
|
||||||
|
for (var index = 0; index < kinds.Length; index++)
|
||||||
|
{
|
||||||
|
var register = "ctx[global::SharpEmu.HLE.CpuRegister." + SysAbiExportShape.ArgumentRegisters[index] + "]";
|
||||||
|
if (kinds[index].StartsWith("cstring:", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var maxLength = kinds[index].Substring("cstring:".Length);
|
||||||
|
var variable = "guestString" + index;
|
||||||
|
reads.AppendLine($" if (!ctx.TryReadNullTerminatedUtf8({register}, {maxLength}, out var {variable}))");
|
||||||
|
reads.AppendLine(" {");
|
||||||
|
reads.AppendLine(" return ctx.SetReturn(global::SharpEmu.HLE.OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);");
|
||||||
|
reads.AppendLine(" }");
|
||||||
|
reads.AppendLine();
|
||||||
|
arguments[index] = variable;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments[index] = kinds[index] == "ulong"
|
||||||
|
? register
|
||||||
|
: "unchecked((" + kinds[index] + ")" + register + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
var invocation = export.ContainingType + "." + export.MethodName + "(ctx, " + string.Join(", ", arguments) + ")";
|
||||||
|
if (reads.Length == 0)
|
||||||
|
{
|
||||||
|
return "static ctx => " + invocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("static ctx =>");
|
||||||
|
builder.AppendLine(" {");
|
||||||
|
builder.Append(reads);
|
||||||
|
builder.AppendLine(" return " + invocation + ";");
|
||||||
|
builder.Append(" }");
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Literal(string value) =>
|
||||||
|
"\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
||||||
|
}
|
||||||
229
src/SharpEmu.SourceGenerators/SysAbiExportShape.cs
Normal file
229
src/SharpEmu.SourceGenerators/SysAbiExportShape.cs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared shape rules for [SysAbiExport] methods, used by both the generator (to decide
|
||||||
|
/// what it can emit) and the analyzer (to reject everything else as build errors), so
|
||||||
|
/// the two can never disagree about what a valid handler is.
|
||||||
|
/// </summary>
|
||||||
|
public static class SysAbiExportShape
|
||||||
|
{
|
||||||
|
/// <summary>Single source of truth so the generator and analyzer can never
|
||||||
|
/// disagree about which attribute marks an export.</summary>
|
||||||
|
public const string SysAbiExportAttributeName = "SharpEmu.HLE.SysAbiExportAttribute";
|
||||||
|
|
||||||
|
public readonly struct Arguments
|
||||||
|
{
|
||||||
|
public Arguments(string libraryName, string nid, string exportName, int target)
|
||||||
|
{
|
||||||
|
LibraryName = libraryName;
|
||||||
|
Nid = nid;
|
||||||
|
ExportName = exportName;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryName { get; }
|
||||||
|
public string Nid { get; }
|
||||||
|
public string ExportName { get; }
|
||||||
|
public int Target { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SysV integer argument registers in call order; typed handler parameters map to
|
||||||
|
/// these positionally.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] ArgumentRegisters = ["Rdi", "Rsi", "Rdx", "Rcx", "R8", "R9"];
|
||||||
|
|
||||||
|
public enum HandlerShape
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
/// <summary>int M(CpuContext) — the classic raw-register shape.</summary>
|
||||||
|
ContextOnly,
|
||||||
|
/// <summary>int M() — no guest state needed.</summary>
|
||||||
|
Parameterless,
|
||||||
|
/// <summary>int M(CpuContext, up to six int/uint/long/ulong args) — the
|
||||||
|
/// generator emits the SysV register unmarshalling thunk.</summary>
|
||||||
|
Typed,
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string GuestCStringAttributeName = "SharpEmu.HLE.GuestCStringAttribute";
|
||||||
|
|
||||||
|
/// <summary>Static, non-generic, returns int, takes one of the supported shapes.</summary>
|
||||||
|
public static HandlerShape Classify(IMethodSymbol method, out string typedParameterKinds) =>
|
||||||
|
Classify(method, out typedParameterKinds, out _);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <paramref name="invalidGuestCString"/> distinguishes a misused [GuestCString]
|
||||||
|
/// (wrong parameter type, non-positive MaxLength) from a plain signature mismatch,
|
||||||
|
/// so the analyzer can point at the marshalling attribute instead of the shape.
|
||||||
|
/// </summary>
|
||||||
|
public static HandlerShape Classify(IMethodSymbol method, out string typedParameterKinds, out bool invalidGuestCString)
|
||||||
|
{
|
||||||
|
typedParameterKinds = string.Empty;
|
||||||
|
invalidGuestCString = false;
|
||||||
|
if (!method.IsStatic ||
|
||||||
|
method.IsGenericMethod ||
|
||||||
|
method.ReturnType.SpecialType != SpecialType.System_Int32)
|
||||||
|
{
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.Parameters.Length == 0)
|
||||||
|
{
|
||||||
|
return HandlerShape.Parameterless;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.Parameters[0].RefKind != RefKind.None || !IsCpuContext(method.Parameters[0].Type))
|
||||||
|
{
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.Parameters.Length == 1)
|
||||||
|
{
|
||||||
|
return HandlerShape.ContextOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.Parameters.Length > 1 + ArgumentRegisters.Length)
|
||||||
|
{
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kinds = new string[method.Parameters.Length - 1];
|
||||||
|
for (var index = 1; index < method.Parameters.Length; index++)
|
||||||
|
{
|
||||||
|
var parameter = method.Parameters[index];
|
||||||
|
if (parameter.RefKind != RefKind.None)
|
||||||
|
{
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasGuestCString = TryGetGuestCStringMaxLength(parameter, out var maxLength);
|
||||||
|
if (parameter.Type.SpecialType == SpecialType.System_String)
|
||||||
|
{
|
||||||
|
if (!hasGuestCString)
|
||||||
|
{
|
||||||
|
// A bare string has no register representation; the guest pointer
|
||||||
|
// must be marshalled explicitly via [GuestCString].
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLength <= 0)
|
||||||
|
{
|
||||||
|
invalidGuestCString = true;
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
kinds[index - 1] = "cstring:" + maxLength.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasGuestCString)
|
||||||
|
{
|
||||||
|
invalidGuestCString = true;
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
kinds[index - 1] = parameter.Type.SpecialType switch
|
||||||
|
{
|
||||||
|
SpecialType.System_Int32 => "int",
|
||||||
|
SpecialType.System_UInt32 => "uint",
|
||||||
|
SpecialType.System_Int64 => "long",
|
||||||
|
SpecialType.System_UInt64 => "ulong",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
if (kinds[index - 1].Length == 0)
|
||||||
|
{
|
||||||
|
return HandlerShape.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedParameterKinds = string.Join(",", kinds);
|
||||||
|
return HandlerShape.Typed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol names are compared with an explicit fully-qualified format so
|
||||||
|
// classification can never depend on a display-format default.
|
||||||
|
private static bool IsCpuContext(ITypeSymbol type) =>
|
||||||
|
type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::SharpEmu.HLE.CpuContext";
|
||||||
|
|
||||||
|
public static bool IsSysAbiExportAttribute(INamedTypeSymbol? attributeClass) =>
|
||||||
|
attributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::" + SysAbiExportAttributeName;
|
||||||
|
|
||||||
|
private static bool TryGetGuestCStringMaxLength(IParameterSymbol parameter, out int maxLength)
|
||||||
|
{
|
||||||
|
maxLength = 0;
|
||||||
|
foreach (var attribute in parameter.GetAttributes())
|
||||||
|
{
|
||||||
|
if (attribute.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) != "global::" + GuestCStringAttributeName)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.ConstructorArguments.Length == 1 &&
|
||||||
|
attribute.ConstructorArguments[0].Value is int value)
|
||||||
|
{
|
||||||
|
maxLength = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidHandler(IMethodSymbol method) =>
|
||||||
|
Classify(method, out _) != HandlerShape.Invalid;
|
||||||
|
|
||||||
|
/// <summary>The generated registry lives in the same assembly: internal suffices.</summary>
|
||||||
|
public static bool IsAccessibleFromGeneratedCode(IMethodSymbol method)
|
||||||
|
{
|
||||||
|
if (method.DeclaredAccessibility is Accessibility.Private or Accessibility.ProtectedOrInternal
|
||||||
|
or Accessibility.Protected or Accessibility.ProtectedAndInternal)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var type = method.ContainingType; type is not null; type = type.ContainingType)
|
||||||
|
{
|
||||||
|
if (type.DeclaredAccessibility is Accessibility.Private or Accessibility.Protected
|
||||||
|
or Accessibility.ProtectedOrInternal or Accessibility.ProtectedAndInternal)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Arguments ReadArguments(AttributeData attribute)
|
||||||
|
{
|
||||||
|
var libraryName = string.Empty;
|
||||||
|
var nid = string.Empty;
|
||||||
|
var exportName = string.Empty;
|
||||||
|
var target = 0;
|
||||||
|
foreach (var argument in attribute.NamedArguments)
|
||||||
|
{
|
||||||
|
switch (argument.Key)
|
||||||
|
{
|
||||||
|
case "LibraryName":
|
||||||
|
libraryName = argument.Value.Value as string ?? string.Empty;
|
||||||
|
break;
|
||||||
|
case "Nid":
|
||||||
|
nid = argument.Value.Value as string ?? string.Empty;
|
||||||
|
break;
|
||||||
|
case "ExportName":
|
||||||
|
exportName = argument.Value.Value as string ?? string.Empty;
|
||||||
|
break;
|
||||||
|
case "Target":
|
||||||
|
target = argument.Value.Value is int value ? value : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Arguments(libraryName, nid, exportName, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
tests/SharpEmu.Libs.Tests/AerolibCatalogTests.cs
Normal file
25
tests/SharpEmu.Libs.Tests/AerolibCatalogTests.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guards the build-time-generated aerolib.bin embedding: the catalog must load from
|
||||||
|
/// the assembly and resolve both directions, or the loader's import naming, the
|
||||||
|
/// not-implemented diagnostics, and runtime dlsym all silently degrade.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AerolibCatalogTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EmbeddedCatalogResolvesKnownSymbolBothWays()
|
||||||
|
{
|
||||||
|
Assert.True(Aerolib.Instance.TryGetByExportName("sceKernelWaitSema", out var byName));
|
||||||
|
Assert.Equal("Zxa0VhQVTsk", byName.Nid);
|
||||||
|
|
||||||
|
Assert.True(Aerolib.Instance.TryGetByNid("Zxa0VhQVTsk", out var byNid));
|
||||||
|
Assert.Equal("sceKernelWaitSema", byNid.ExportName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ public sealed class JsonExportRegistrationTests
|
|||||||
private static ModuleManager CreateRegisteredManager()
|
private static ModuleManager CreateRegisteredManager()
|
||||||
{
|
{
|
||||||
var manager = new ModuleManager();
|
var manager = new ModuleManager();
|
||||||
manager.RegisterFromAssembly(typeof(JsonValueExports).Assembly, Generation.Gen5);
|
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
tests/SharpEmu.Libs.Tests/SysAbiRegistryTests.cs
Normal file
52
tests/SharpEmu.Libs.Tests/SysAbiRegistryTests.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content invariants over the compile-time generated export registry
|
||||||
|
/// (SharpEmu.Generated.SysAbiExportRegistry), which is the runtime's sole registration
|
||||||
|
/// source. Replaces the parity test that pinned the registry to the retired reflection
|
||||||
|
/// scan while both existed; equality with the scan proved the swap, these pin what must
|
||||||
|
/// stay true now that only the registry remains.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SysAbiRegistryTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(Generation.Gen4)]
|
||||||
|
[InlineData(Generation.Gen5)]
|
||||||
|
[InlineData(Generation.Gen4 | Generation.Gen5)]
|
||||||
|
public void RegistryIsDuplicateFree(Generation generation)
|
||||||
|
{
|
||||||
|
var exports = SharpEmu.Generated.SysAbiExportRegistry.CreateExports(generation);
|
||||||
|
var manager = new ModuleManager();
|
||||||
|
|
||||||
|
// RegisterExports skips NIDs it has already seen, so a shortfall here means the
|
||||||
|
// generated table carries a duplicate the SHEM001 analyzer should have caught.
|
||||||
|
Assert.Equal(exports.Count, manager.RegisterExports(exports));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RegistryCoversTheFullExportSurface()
|
||||||
|
{
|
||||||
|
var exports = SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4 | Generation.Gen5);
|
||||||
|
|
||||||
|
// 715 exports existed when the registry replaced the scan; shrinkage means the
|
||||||
|
// generator silently dropped handlers.
|
||||||
|
Assert.True(exports.Count >= 715, $"registry shrank to {exports.Count} exports");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RegistryResolvesKnownExportWithCatalogIdentity()
|
||||||
|
{
|
||||||
|
var manager = new ModuleManager();
|
||||||
|
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4 | Generation.Gen5));
|
||||||
|
|
||||||
|
Assert.True(manager.TryGetExport("Zxa0VhQVTsk", out var export));
|
||||||
|
Assert.Equal("sceKernelWaitSema", export.Name);
|
||||||
|
Assert.Equal("libKernel", export.LibraryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"dependencies": {
|
|
||||||
"net10.0": {
|
|
||||||
"Microsoft.NET.Test.Sdk": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[17.14.1, )",
|
|
||||||
"resolved": "17.14.1",
|
|
||||||
"contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.CodeCoverage": "17.14.1",
|
|
||||||
"Microsoft.TestPlatform.TestHost": "17.14.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xunit": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[2.9.3, )",
|
|
||||||
"resolved": "2.9.3",
|
|
||||||
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"xunit.analyzers": "1.18.0",
|
|
||||||
"xunit.assert": "2.9.3",
|
|
||||||
"xunit.core": "[2.9.3]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xunit.runner.visualstudio": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[3.1.1, )",
|
|
||||||
"resolved": "3.1.1",
|
|
||||||
"contentHash": "gNu2zhnuwjq5vQlU4S7yK/lfaKZDLmtcu+vTjnhfTlMAUYn+Hmgu8IIX0UCwWepYkk+Szx03DHx1bDnc9Fd+9w=="
|
|
||||||
},
|
|
||||||
"Microsoft.CodeCoverage": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "17.14.1",
|
|
||||||
"contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
|
|
||||||
},
|
|
||||||
"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=="
|
|
||||||
},
|
|
||||||
"Microsoft.TestPlatform.ObjectModel": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "17.14.1",
|
|
||||||
"contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ=="
|
|
||||||
},
|
|
||||||
"Microsoft.TestPlatform.TestHost": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "17.14.1",
|
|
||||||
"contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.TestPlatform.ObjectModel": "17.14.1",
|
|
||||||
"Newtonsoft.Json": "13.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Newtonsoft.Json": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "13.0.3",
|
|
||||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
|
||||||
},
|
|
||||||
"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.Input.Common": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "QbJVV7kFBHEByayXCYdJtXXI9Sp4a+QAf0IdGV6uCWkFYcEmqBYW3aaNGvFOdSwTBDbHL5T/OtOCrGh4qYhk7A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Windowing.Common": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Silk.NET.Input.Glfw": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "KGHYqsv/IQRJtD6dloYh2tN4CkaM40vxM2kj0cGKBoCQiBDYHHhJiyDTyMPx0W7Fz5IgnhnG42ELmIAa0DH69A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Windowing.Glfw": "2.23.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=="
|
|
||||||
},
|
|
||||||
"xunit.abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.0.3",
|
|
||||||
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
|
|
||||||
},
|
|
||||||
"xunit.analyzers": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "1.18.0",
|
|
||||||
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
|
|
||||||
},
|
|
||||||
"xunit.assert": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.9.3",
|
|
||||||
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
|
|
||||||
},
|
|
||||||
"xunit.core": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.9.3",
|
|
||||||
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"xunit.extensibility.core": "[2.9.3]",
|
|
||||||
"xunit.extensibility.execution": "[2.9.3]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xunit.extensibility.core": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.9.3",
|
|
||||||
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
|
|
||||||
"dependencies": {
|
|
||||||
"xunit.abstractions": "2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xunit.extensibility.execution": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "2.9.3",
|
|
||||||
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"xunit.extensibility.core": "[2.9.3]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.core": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"Iced": "[1.21.0, )",
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )",
|
|
||||||
"SharpEmu.Libs": "[0.0.1, )",
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.hle": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.Logging": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.libs": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )",
|
|
||||||
"SharpEmu.ShaderCompiler.Vulkan": "[0.0.1, )",
|
|
||||||
"Silk.NET.Input": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
|
|
||||||
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
|
|
||||||
"Silk.NET.Windowing": "[2.23.0, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.logging": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.HLE": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sharpemu.shadercompiler.vulkan": {
|
|
||||||
"type": "Project",
|
|
||||||
"dependencies": {
|
|
||||||
"SharpEmu.ShaderCompiler": "[0.0.1, )"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Iced": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[1.21.0, )",
|
|
||||||
"resolved": "1.21.0",
|
|
||||||
"contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg=="
|
|
||||||
},
|
|
||||||
"Silk.NET.Input": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "Xzl+tVwAp2eEd8blmGQjmJrsZPnp3PWG0KJjiAQHaY2Zr/ELVWeAROKXmZdCAvexzmte2JVGEy/dxnMycbxlpg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Input.Common": "2.23.0",
|
|
||||||
"Silk.NET.Input.Glfw": "2.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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.EXT": {
|
|
||||||
"type": "CentralTransitive",
|
|
||||||
"requested": "[2.23.0, )",
|
|
||||||
"resolved": "2.23.0",
|
|
||||||
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Silk.NET.Core": "2.23.0",
|
|
||||||
"Silk.NET.Vulkan": "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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
tests/SharpEmu.SourceGenerators.Tests/Ps5NidTests.cs
Normal file
34
tests/SharpEmu.SourceGenerators.Tests/Ps5NidTests.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.SourceGenerators;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators.Tests;
|
||||||
|
|
||||||
|
public sealed class Ps5NidTests
|
||||||
|
{
|
||||||
|
// Real pairs taken from [SysAbiExport] attributes in the emulator: the computation
|
||||||
|
// must reproduce the catalog's NIDs exactly or every derived registration is wrong.
|
||||||
|
[Theory]
|
||||||
|
[InlineData("sceKernelWaitSema", "Zxa0VhQVTsk")]
|
||||||
|
[InlineData("sceKernelSignalSema", "4czppHBiriw")]
|
||||||
|
[InlineData("sceKernelCreateSema", "188x57JYp0g")]
|
||||||
|
[InlineData("sceAudioOutOpen", "ekNvsT22rsY")]
|
||||||
|
[InlineData("sceAudioOutOutput", "QOQtbeDqsT4")]
|
||||||
|
[InlineData("memcpy", "Q3VBxCXhUHs")]
|
||||||
|
[InlineData("memmove", "+P6FRGH4LfA")]
|
||||||
|
public void ComputeMatchesKnownCatalogPairs(string exportName, string expectedNid) =>
|
||||||
|
Assert.Equal(expectedNid, Ps5Nid.Compute(exportName));
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Zxa0VhQVTsk", true)]
|
||||||
|
[InlineData("+P6FRGH4LfA", true)]
|
||||||
|
[InlineData("4R6-OvI2cEA", true)]
|
||||||
|
[InlineData("Zxa0VhQVTs", false)] // ten characters
|
||||||
|
[InlineData("Zxa0VhQVTskk", false)] // twelve characters
|
||||||
|
[InlineData("Zxa0VhQVTs=", false)] // padding character
|
||||||
|
[InlineData("Zxa0VhQVT/k", false)] // '/' is remapped to '-' in this alphabet
|
||||||
|
public void IsValidFormatChecksLengthAndAlphabet(string nid, bool expected) =>
|
||||||
|
Assert.Equal(expected, Ps5Nid.IsValidFormat(nid));
|
||||||
|
}
|
||||||
107
tests/SharpEmu.SourceGenerators.Tests/RoslynTestHost.cs
Normal file
107
tests/SharpEmu.SourceGenerators.Tests/RoslynTestHost.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds in-memory compilations for driving the generator and analyzer: the test-host
|
||||||
|
/// runtime assemblies plus the real SharpEmu.HLE (for SysAbiExportAttribute,
|
||||||
|
/// ExportedFunction, and friends), so generated output is compiled against the exact
|
||||||
|
/// types it will target in the emulator.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RoslynTestHost
|
||||||
|
{
|
||||||
|
private static readonly Lazy<IReadOnlyList<MetadataReference>> References = new(static () =>
|
||||||
|
{
|
||||||
|
var references = new List<MetadataReference>();
|
||||||
|
var trustedAssemblies = (string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!;
|
||||||
|
foreach (var path in trustedAssemblies.Split(Path.PathSeparator))
|
||||||
|
{
|
||||||
|
if (path.Length != 0)
|
||||||
|
{
|
||||||
|
references.Add(MetadataReference.CreateFromFile(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
references.Add(MetadataReference.CreateFromFile(typeof(SysAbiExportAttribute).Assembly.Location));
|
||||||
|
return references;
|
||||||
|
});
|
||||||
|
|
||||||
|
public static CSharpCompilation Compile(params string[] sources)
|
||||||
|
{
|
||||||
|
var trees = new SyntaxTree[sources.Length];
|
||||||
|
for (var index = 0; index < sources.Length; index++)
|
||||||
|
{
|
||||||
|
trees[index] = CSharpSyntaxTree.ParseText(
|
||||||
|
sources[index],
|
||||||
|
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CSharpCompilation.Create(
|
||||||
|
"SysAbiGeneratorTests",
|
||||||
|
trees,
|
||||||
|
References.Value,
|
||||||
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (Compilation Updated, string GeneratedSource) RunGenerator(CSharpCompilation compilation)
|
||||||
|
{
|
||||||
|
var driver = CSharpGeneratorDriver.Create(new SysAbiExportGenerator());
|
||||||
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var updated, out _);
|
||||||
|
var generated = string.Empty;
|
||||||
|
foreach (var tree in updated.SyntaxTrees)
|
||||||
|
{
|
||||||
|
if (tree.FilePath.EndsWith("SysAbiExportRegistry.g.cs", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
generated = tree.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (updated, generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<Diagnostic> RunAnalyzer(
|
||||||
|
CSharpCompilation compilation,
|
||||||
|
params AdditionalText[] additionalFiles)
|
||||||
|
{
|
||||||
|
var withAnalyzers = compilation.WithAnalyzers(
|
||||||
|
[new SysAbiExportAnalyzer()],
|
||||||
|
new AnalyzerOptions([.. additionalFiles]));
|
||||||
|
var diagnostics = withAnalyzers.GetAnalyzerDiagnosticsAsync().GetAwaiter().GetResult();
|
||||||
|
var result = new List<Diagnostic>(diagnostics.Length);
|
||||||
|
foreach (var diagnostic in diagnostics)
|
||||||
|
{
|
||||||
|
result.Add(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AssertCompiles(Compilation compilation)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
foreach (var diagnostic in compilation.GetDiagnostics())
|
||||||
|
{
|
||||||
|
if (diagnostic.Severity == DiagnosticSeverity.Error)
|
||||||
|
{
|
||||||
|
errors.Add(diagnostic.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Xunit.Assert.True(errors.Count == 0, string.Join(Environment.NewLine, errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class InMemoryAdditionalText(string path, string content) : AdditionalText
|
||||||
|
{
|
||||||
|
public override string Path { get; } = path;
|
||||||
|
|
||||||
|
public override SourceText GetText(CancellationToken cancellationToken = default) =>
|
||||||
|
SourceText.From(content);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Self-contained (the repo's test-project convention): drives the generator and
|
||||||
|
analyzer in-process with a Roslyn compilation built from inline sources, and
|
||||||
|
references SharpEmu.HLE only to supply the real attribute/registry metadata the
|
||||||
|
generated code compiles against. -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\SharpEmu.SourceGenerators\SharpEmu.SourceGenerators.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\SharpEmu.HLE\SharpEmu.HLE.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||||
|
<PackageReference Include="xunit" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators.Tests;
|
||||||
|
|
||||||
|
public sealed class SysAbiExportAnalyzerTests
|
||||||
|
{
|
||||||
|
private static IReadOnlyList<Diagnostic> Analyze(string source, params AdditionalText[] additionalFiles) =>
|
||||||
|
RoslynTestHost.RunAnalyzer(RoslynTestHost.Compile(source), additionalFiles);
|
||||||
|
|
||||||
|
private static void AssertSingle(IReadOnlyList<Diagnostic> diagnostics, string id)
|
||||||
|
{
|
||||||
|
Assert.Single(diagnostics);
|
||||||
|
Assert.Equal(id, diagnostics[0].Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CleanExportProducesNoDiagnostics()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema", Target = Generation.Gen5)]
|
||||||
|
public static int WaitSema(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
Assert.Empty(diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DuplicateNidIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
||||||
|
public static int First(CpuContext ctx) => 0;
|
||||||
|
|
||||||
|
// Same NID via derivation: duplicates must be caught across both forms.
|
||||||
|
[SysAbiExport(ExportName = "sceKernelWaitSema")]
|
||||||
|
public static int Second(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM001");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MalformedNidIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "not_a_nid", ExportName = "sceKernelWaitSema")]
|
||||||
|
public static int WaitSema(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM002");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WrongSignatureIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
||||||
|
public static void WaitSema(CpuContext ctx) { }
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM003");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TypedHandlerShapeIsAccepted()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema")]
|
||||||
|
public static int PollSema(CpuContext ctx, uint handle, int needCount) => 0;
|
||||||
|
|
||||||
|
[SysAbiExport(Nid = "4DM06U2BNEY", ExportName = "sceKernelCancelSema")]
|
||||||
|
public static int CancelSema(CpuContext ctx, uint a, int b, ulong c, long d, uint e, int f) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
Assert.Empty(diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TypedHandlerBeyondRegisterArgsOrWithUnsupportedTypeIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
// Seven args exceed the six SysV integer registers.
|
||||||
|
[SysAbiExport(Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema")]
|
||||||
|
public static int TooMany(CpuContext ctx, uint a, int b, ulong c, long d, uint e, int f, int g) => 0;
|
||||||
|
|
||||||
|
// string is not register-representable (that is phase 3's marshalling).
|
||||||
|
[SysAbiExport(Nid = "4czppHBiriw", ExportName = "sceKernelSignalSema")]
|
||||||
|
public static int WrongKind(CpuContext ctx, string name) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Order-insensitive: analyzers run concurrently and diagnostic order is unstable.
|
||||||
|
Assert.Equal(2, diagnostics.Count);
|
||||||
|
Assert.All(diagnostics, diagnostic => Assert.Equal("SHEM003", diagnostic.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GuestCStringParameterIsAccepted()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
|
||||||
|
public static int KernelOpen(CpuContext ctx, [GuestCString(4096)] string path, int flags) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
Assert.Empty(diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MisusedGuestCStringIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
// On a non-string parameter the attribute is meaningless.
|
||||||
|
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
|
||||||
|
public static int OnInt(CpuContext ctx, [GuestCString(4096)] int flags) => 0;
|
||||||
|
|
||||||
|
// A read bounded at zero bytes can never succeed.
|
||||||
|
[SysAbiExport(Nid = "6c3rCVE-fTU", ExportName = "_open")]
|
||||||
|
public static int ZeroLength(CpuContext ctx, [GuestCString(0)] string path) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Order-insensitive: analyzers run concurrently and diagnostic order is unstable.
|
||||||
|
Assert.Equal(2, diagnostics.Count);
|
||||||
|
Assert.All(diagnostics, diagnostic => Assert.Equal("SHEM008", diagnostic.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NidContradictingExportNameIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
// This NID belongs to sceKernelSignalSema, not sceKernelWaitSema.
|
||||||
|
[SysAbiExport(Nid = "4czppHBiriw", ExportName = "sceKernelWaitSema")]
|
||||||
|
public static int WaitSema(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM004");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExportWithNeitherNidNorNameIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Target = Generation.Gen5)]
|
||||||
|
public static int Mystery(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM005");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NameOutsideTheCatalogWarnsOnlyWhenCatalogIsWired()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(ExportName = "sceKernelWiatSema", Target = Generation.Gen5)]
|
||||||
|
public static int Typo(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var withoutCatalog = Analyze(source);
|
||||||
|
Assert.Empty(withoutCatalog);
|
||||||
|
|
||||||
|
var catalog = new InMemoryAdditionalText(
|
||||||
|
"/repo/scripts/ps5_names.txt",
|
||||||
|
"sceKernelWaitSema\nsceKernelSignalSema\n");
|
||||||
|
var withCatalog = Analyze(source, catalog);
|
||||||
|
AssertSingle(withCatalog, "SHEM006");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrivateHandlerIsReported()
|
||||||
|
{
|
||||||
|
var diagnostics = Analyze("""
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
public static class Exports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
||||||
|
private static int Hidden(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AssertSingle(diagnostics, "SHEM007");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.SourceGenerators.Tests;
|
||||||
|
|
||||||
|
public sealed class SysAbiExportGeneratorTests
|
||||||
|
{
|
||||||
|
private const string HandlerSource = """
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
namespace TestExports;
|
||||||
|
|
||||||
|
public static class SampleExports
|
||||||
|
{
|
||||||
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
|
||||||
|
public static int WaitSema(CpuContext ctx) => 0;
|
||||||
|
|
||||||
|
// NID omitted on purpose: the generator must derive it from the name.
|
||||||
|
[SysAbiExport(ExportName = "sceKernelSignalSema", Target = Generation.Gen5)]
|
||||||
|
public static int SignalSema(CpuContext ctx) => 0;
|
||||||
|
|
||||||
|
// Parameterless handler shape: must be wrapped to the SysAbiFunction contract.
|
||||||
|
[SysAbiExport(Nid = "ekNvsT22rsY", ExportName = "sceAudioOutOpen")]
|
||||||
|
public static int Open() => 0;
|
||||||
|
|
||||||
|
// Typed handler shape: the generator emits the SysV register thunk.
|
||||||
|
[SysAbiExport(Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema")]
|
||||||
|
public static int PollSema(CpuContext ctx, uint handle, int needCount) => 0;
|
||||||
|
|
||||||
|
// All four integer kinds across all six argument registers.
|
||||||
|
[SysAbiExport(Nid = "4DM06U2BNEY", ExportName = "sceKernelCancelSema")]
|
||||||
|
public static int CancelSema(CpuContext ctx, uint a, int b, ulong c, long d, uint e, int f) => 0;
|
||||||
|
|
||||||
|
// Guest string marshalling: the thunk reads the pointer before the handler.
|
||||||
|
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
|
||||||
|
public static int KernelOpen(CpuContext ctx, [GuestCString(4096)] string path, int flags) => 0;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GeneratedRegistryCompilesAgainstRealHleTypes()
|
||||||
|
{
|
||||||
|
var compilation = RoslynTestHost.Compile(HandlerSource);
|
||||||
|
var (updated, generated) = RoslynTestHost.RunGenerator(compilation);
|
||||||
|
|
||||||
|
Assert.NotEqual(string.Empty, generated);
|
||||||
|
RoslynTestHost.AssertCompiles(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RegistryContainsDeclaredDerivedAndWrappedExports()
|
||||||
|
{
|
||||||
|
var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(HandlerSource));
|
||||||
|
|
||||||
|
// Declared NID passes through verbatim.
|
||||||
|
Assert.Contains("\"Zxa0VhQVTsk\"", generated, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("global::TestExports.SampleExports.WaitSema", generated, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
// Omitted NID is derived from the export name at compile time.
|
||||||
|
Assert.Contains("\"4czppHBiriw\"", generated, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
// Parameterless handlers are adapted to the SysAbiFunction shape.
|
||||||
|
Assert.Contains("static _ => global::TestExports.SampleExports.Open()", generated, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TypedHandlersGetSysVRegisterThunks()
|
||||||
|
{
|
||||||
|
var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(HandlerSource));
|
||||||
|
|
||||||
|
// Parameters map positionally to RDI/RSI/... with the same unchecked-cast idiom
|
||||||
|
// hand-written handlers use; ulong reads the register raw.
|
||||||
|
Assert.Contains(
|
||||||
|
"static ctx => global::TestExports.SampleExports.PollSema(ctx, " +
|
||||||
|
"unchecked((uint)ctx[global::SharpEmu.HLE.CpuRegister.Rdi]), " +
|
||||||
|
"unchecked((int)ctx[global::SharpEmu.HLE.CpuRegister.Rsi]))",
|
||||||
|
generated,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"static ctx => global::TestExports.SampleExports.CancelSema(ctx, " +
|
||||||
|
"unchecked((uint)ctx[global::SharpEmu.HLE.CpuRegister.Rdi]), " +
|
||||||
|
"unchecked((int)ctx[global::SharpEmu.HLE.CpuRegister.Rsi]), " +
|
||||||
|
"ctx[global::SharpEmu.HLE.CpuRegister.Rdx], " +
|
||||||
|
"unchecked((long)ctx[global::SharpEmu.HLE.CpuRegister.Rcx]), " +
|
||||||
|
"unchecked((uint)ctx[global::SharpEmu.HLE.CpuRegister.R8]), " +
|
||||||
|
"unchecked((int)ctx[global::SharpEmu.HLE.CpuRegister.R9]))",
|
||||||
|
generated,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GuestCStringParametersAreMarshalledWithAFaultPath()
|
||||||
|
{
|
||||||
|
var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(HandlerSource));
|
||||||
|
|
||||||
|
// The string is read from the pointer in RDI before the handler runs, and a
|
||||||
|
// failed read returns MEMORY_FAULT to the guest without invoking the handler.
|
||||||
|
Assert.Contains(
|
||||||
|
"if (!ctx.TryReadNullTerminatedUtf8(ctx[global::SharpEmu.HLE.CpuRegister.Rdi], 4096, out var guestString0))",
|
||||||
|
generated,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"return ctx.SetReturn(global::SharpEmu.HLE.OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);",
|
||||||
|
generated,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"return global::TestExports.SampleExports.KernelOpen(ctx, guestString0, " +
|
||||||
|
"unchecked((int)ctx[global::SharpEmu.HLE.CpuRegister.Rsi]));",
|
||||||
|
generated,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerationFilteringMatchesTheReflectionScanSemantics()
|
||||||
|
{
|
||||||
|
var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(HandlerSource));
|
||||||
|
|
||||||
|
// The Add helper reproduces ResolveExportInfo: None inherits the registration
|
||||||
|
// generation, and exports outside the registration generation are skipped.
|
||||||
|
Assert.Contains("attributeTarget == global::SharpEmu.HLE.Generation.None ? registrationGeneration : attributeTarget", generated, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("(target & registrationGeneration) == 0", generated, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AssemblyWithoutExportsEmitsNoRegistry()
|
||||||
|
{
|
||||||
|
// Referencing the analyzer must not mint a colliding
|
||||||
|
// SharpEmu.Generated.SysAbiExportRegistry type in export-free assemblies.
|
||||||
|
const string noExports = """
|
||||||
|
public static class PlainCode
|
||||||
|
{
|
||||||
|
public static int Nothing() => 0;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(noExports));
|
||||||
|
|
||||||
|
Assert.Equal(string.Empty, generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidHandlersAreSkippedNotEmitted()
|
||||||
|
{
|
||||||
|
const string invalid = """
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
namespace TestExports;
|
||||||
|
|
||||||
|
public static class BrokenExports
|
||||||
|
{
|
||||||
|
[SysAbiExport(ExportName = "sceKernelUsleep")]
|
||||||
|
public static long WrongReturn(CpuContext ctx) => 0;
|
||||||
|
|
||||||
|
[SysAbiExport(ExportName = "sceKernelGettimeofday")]
|
||||||
|
private static int Inaccessible(CpuContext ctx) => 0;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
var (updated, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(invalid));
|
||||||
|
|
||||||
|
Assert.DoesNotContain("WrongReturn", generated, StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain("Inaccessible", generated, StringComparison.Ordinal);
|
||||||
|
RoslynTestHost.AssertCompiles(updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user