Compare commits

..

10 Commits

Author SHA1 Message Date
ParantezTech 0260db3e0e revert lock file 2026-07-18 23:02:51 +03:00
ParantezTech 8fc94bc687 [video] stabilize guest resources 2026-07-18 23:02:30 +03:00
ParantezTech d81d643227 [gpu] reduce queue stalls 2026-07-18 23:02:25 +03:00
ParantezTech da9f132226 [gpu] bound guest data pool 2026-07-18 23:02:18 +03:00
ParantezTech 2064c1eda6 [shader] add scalar memory fallback 2026-07-18 23:02:12 +03:00
ParantezTech ce243b3622 [kernel] streamline host memory access 2026-07-18 23:02:04 +03:00
ParantezTech 9aedb88f3c [bink] keep guest decode path 2026-07-18 23:01:57 +03:00
ParantezTech 7548911413 [ampr] allow concurrent reads 2026-07-18 23:01:51 +03:00
ParantezTech 59059dfd2c [cpu] add string leaf stubs 2026-07-18 23:01:45 +03:00
ParantezTech 24bd38ab5b [runtime] restore default GC mode 2026-07-18 23:01:39 +03:00
18 changed files with 631 additions and 784 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SharpEmuVersion>0.0.2-beta.4</SharpEmuVersion>
<SharpEmuVersion>0.0.2-beta.3</SharpEmuVersion>
<Version>$(SharpEmuVersion)</Version>
<RepoRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)'))</RepoRoot>
-20
View File
@@ -1,20 +0,0 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
# Aerolib Catalog
```bash
# NID to export name
python scripts/aerolib_catalog.py lookup Zxa0VhQVTsk
# Export name to NID
python scripts/aerolib_catalog.py lookup sceKernelWaitSema
# Search export names
python scripts/aerolib_catalog.py search VideoOut --limit 20
# Export all NID/name pairs to artifacts/aerolib.txt
python scripts/aerolib_catalog.py export
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.103",
"rollForward": "latestFeature"
"rollForward": "disable"
}
}
-181
View File
@@ -1,181 +0,0 @@
#!/usr/bin/env python3
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
from __future__ import annotations
import argparse
import base64
import hashlib
import re
import sys
from pathlib import Path
NID_SUFFIX = bytes.fromhex("518d64a635ded8c1e6b039b1c3e55230")
NID_PATTERN = re.compile(r"^[A-Za-z0-9+-]{11}$")
DEFAULT_NAMES_FILE = Path(__file__).resolve().with_name("ps5_names.txt")
DEFAULT_EXPORT_FILE = Path(__file__).resolve().parents[1] / "artifacts" / "aerolib.txt"
def compute_nid(export_name: str) -> str:
digest = hashlib.sha1(export_name.encode("utf-8") + NID_SUFFIX).digest()
encoded = base64.b64encode(digest[:8][::-1]).decode("ascii")
return encoded.rstrip("=").replace("/", "-")
def read_names(path: Path) -> list[str]:
try:
return [
line.strip()
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip()
]
except OSError as error:
raise SystemExit(f"Unable to read catalog '{path}': {error}") from error
def write_pair(nid: str, export_name: str) -> None:
print(f"{nid}\t{export_name}")
def lookup(args: argparse.Namespace) -> int:
value = args.value.strip()
if NID_PATTERN.fullmatch(value):
for export_name in read_names(args.names):
if compute_nid(export_name) == value:
write_pair(value, export_name)
return 0
print(f"NID not found in catalog: {value}", file=sys.stderr)
return 1
names = set(read_names(args.names))
write_pair(compute_nid(value), value)
if value not in names:
print("Warning: export name is not present in the catalog.", file=sys.stderr)
return 0
def search(args: argparse.Namespace) -> int:
names = read_names(args.names)
if args.regex:
try:
pattern = re.compile(args.query, 0 if args.case_sensitive else re.IGNORECASE)
except re.error as error:
print(f"Invalid regular expression: {error}", file=sys.stderr)
return 2
matches = (name for name in names if pattern.search(name))
elif args.case_sensitive:
matches = (name for name in names if args.query in name)
else:
query = args.query.casefold()
matches = (name for name in names if query in name.casefold())
count = 0
for export_name in matches:
write_pair(compute_nid(export_name), export_name)
count += 1
if args.limit and count >= args.limit:
break
if count == 0:
print(f"No catalog names matched: {args.query}", file=sys.stderr)
return 1
return 0
def export_catalog(args: argparse.Namespace) -> int:
pairs = [(compute_nid(name), name) for name in read_names(args.names)]
if args.sort == "nid":
pairs.sort(key=lambda pair: (pair[0], pair[1]))
elif args.sort == "name":
pairs.sort(key=lambda pair: pair[1])
args.output.parent.mkdir(parents=True, exist_ok=True)
try:
with args.output.open("w", encoding="utf-8", newline="\n") as output:
output.write("# NID\tExportName\n")
for nid, export_name in pairs:
output.write(f"{nid}\t{export_name}\n")
except OSError as error:
print(f"Unable to write catalog '{args.output}': {error}", file=sys.stderr)
return 1
print(f"Wrote {len(pairs)} entries to {args.output}")
return 0
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Inspect the SharpEmu PS5 export-name/NID catalog.",
epilog=(
"Examples:\n"
" python scripts/aerolib_catalog.py lookup Zxa0VhQVTsk\n"
" python scripts/aerolib_catalog.py lookup sceKernelWaitSema\n"
" python scripts/aerolib_catalog.py search VideoOut --limit 20\n"
" python scripts/aerolib_catalog.py export"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--names",
type=Path,
default=DEFAULT_NAMES_FILE,
help=f"source name list (default: {DEFAULT_NAMES_FILE})",
)
subparsers = parser.add_subparsers(dest="command", required=True)
lookup_parser = subparsers.add_parser(
"lookup", help="resolve a NID or calculate the NID for an export name"
)
lookup_parser.add_argument("value", help="11-character NID or exact export name")
lookup_parser.set_defaults(handler=lookup)
search_parser = subparsers.add_parser(
"search", help="find export names and print matching NID/name pairs"
)
search_parser.add_argument("query", help="name substring or regular expression")
search_parser.add_argument(
"--limit", type=int, default=50, help="maximum matches; 0 means unlimited"
)
search_parser.add_argument(
"--case-sensitive", action="store_true", help="match case exactly"
)
search_parser.add_argument(
"--regex", action="store_true", help="treat the query as a regular expression"
)
search_parser.set_defaults(handler=search)
export_parser = subparsers.add_parser(
"export", help="write every NID/name pair to a tab-separated text file"
)
export_parser.add_argument(
"output",
type=Path,
nargs="?",
default=DEFAULT_EXPORT_FILE,
help=f"output file (default: {DEFAULT_EXPORT_FILE})",
)
export_parser.add_argument(
"--sort",
choices=("source", "nid", "name"),
default="nid",
help="output ordering (default: nid)",
)
export_parser.set_defaults(handler=export_catalog)
return parser
def main() -> int:
parser = create_parser()
args = parser.parse_args()
return args.handler(args)
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -49,6 +49,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
+603
View File
@@ -0,0 +1,603 @@
{
"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.2-beta.3, )",
"SharpEmu.Libs": "[0.0.2-beta.3, )",
"SharpEmu.Logging": "[0.0.2-beta.3, )"
}
},
"sharpemu.debugger": {
"type": "Project",
"dependencies": {
"SharpEmu.Core": "[0.0.2-beta.3, )",
"SharpEmu.HLE": "[0.0.2-beta.3, )",
"SharpEmu.Logging": "[0.0.2-beta.3, )"
}
},
"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.Core": "[0.0.2-beta.3, )",
"SharpEmu.Libs": "[0.0.2-beta.3, )",
"SharpEmu.Logging": "[0.0.2-beta.3, )",
"Tmds.DBus.Protocol": "[0.21.3, )"
}
},
"sharpemu.hle": {
"type": "Project",
"dependencies": {
"SharpEmu.Logging": "[0.0.2-beta.3, )"
}
},
"sharpemu.libs": {
"type": "Project",
"dependencies": {
"SharpEmu.HLE": "[0.0.2-beta.3, )",
"SharpEmu.ShaderCompiler": "[0.0.2-beta.3, )",
"SharpEmu.ShaderCompiler.Metal": "[0.0.2-beta.3, )",
"SharpEmu.ShaderCompiler.Vulkan": "[0.0.2-beta.3, )",
"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.2-beta.3, )"
}
},
"sharpemu.shadercompiler.metal": {
"type": "Project",
"dependencies": {
"SharpEmu.ShaderCompiler": "[0.0.2-beta.3, )"
}
},
"sharpemu.shadercompiler.vulkan": {
"type": "Project",
"dependencies": {
"SharpEmu.ShaderCompiler": "[0.0.2-beta.3, )"
}
},
"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=="
}
}
}
}
+1 -1
View File
@@ -252,7 +252,7 @@ public static unsafe class JitStubs
var pattern = TlsAccessPattern;
var end = start + length - pattern.Length;
for (var ptr = start; ptr <= end; ptr++)
for (var ptr = start; ptr < end; ptr++)
{
if (MatchesPattern(ptr, pattern))
{
+10 -46
View File
@@ -92,11 +92,6 @@ public partial class MainWindow : Window
// plain window color remains the fallback when the asset fails to load.
private Bitmap? _defaultBackdrop;
// Whether the native loading/closing popup should be showing; it is a
// desktop-topmost popup, so it closes while the launcher is in the
// background or minimized and reopens from this flag on activation.
private bool _sessionLoadingActive;
// Controller navigation state.
private readonly DispatcherTimer _gamepadTimer;
private HostGamepadButtons _previousPadButtons;
@@ -155,18 +150,8 @@ public partial class MainWindow : Window
};
_libraryBlurTimer.Tick += (_, _) => AdvanceLibraryBlur();
// Native popups float above every window on the desktop; they must
// follow the launcher into the background or a minimized state.
Activated += (_, _) =>
{
UpdateSessionBarVisibility();
SessionLoadingPopup.IsOpen = _sessionLoadingActive;
};
Deactivated += (_, _) =>
{
SessionBarPopup.IsOpen = false;
SessionLoadingPopup.IsOpen = false;
};
Activated += (_, _) => UpdateSessionBarVisibility();
Deactivated += (_, _) => SessionBarPopup.IsOpen = false;
TitleBar.PointerPressed += OnTitleBarPointerPressed;
GameList.SelectionChanged += (_, _) => UpdateSelectedGame();
@@ -429,15 +414,6 @@ public partial class MainWindow : Window
return;
}
if (_isRunning || _isStopping)
{
// The game renders inside the launcher window, so the launcher
// stays active while playing. The controller belongs to the game
// then: no navigation, and Circle/B must never stop the session.
_previousPadButtons = pad.Buttons;
return;
}
var shoulderPressed = pad.Buttons & ~_previousPadButtons;
if ((shoulderPressed & HostGamepadButtons.L1) != 0)
{
@@ -487,6 +463,11 @@ public partial class MainWindow : Window
LaunchSelected();
}
if ((pressed & HostGamepadButtons.Circle) != 0)
{
StopEmulator();
}
_previousPadButtons = pad.Buttons;
}
@@ -1645,23 +1626,13 @@ public partial class MainWindow : Window
base.OnPropertyChanged(change);
if (change.Property == WindowStateProperty)
{
// The XAML WindowState="Maximized" assignment raises this change
// during InitializeComponent, before named controls are wired up.
if (WindowState == WindowState.Minimized)
{
_sndPreview.Pause();
if (SessionLoadingPopup is { } popup)
{
popup.IsOpen = false;
}
}
else
{
_sndPreview.Resume();
if (SessionLoadingPopup is { } popup)
{
popup.IsOpen = _sessionLoadingActive;
}
}
}
}
@@ -2038,7 +2009,7 @@ public partial class MainWindow : Window
ContentToolbar.IsVisible = false;
ConsolePanel.IsVisible = false;
LaunchBar.IsVisible = false;
HideSessionLoading();
SessionLoadingPopup.IsOpen = false;
UpdateSessionBarVisibility();
}
});
@@ -2138,7 +2109,7 @@ public partial class MainWindow : Window
GameView.IsVisible = false;
GameView.IsHitTestVisible = true;
SessionBarPopup.IsOpen = false;
HideSessionLoading();
SessionLoadingPopup.IsOpen = false;
AnimateLibraryBlur(0, clearWhenComplete: true);
MainContent.Margin = new Thickness(32, 24, 32, 20);
ContentToolbar.IsVisible = true;
@@ -2222,14 +2193,7 @@ public partial class MainWindow : Window
{
SessionLoadingTitle.Text = title;
SessionLoadingDetail.Text = detail;
_sessionLoadingActive = true;
SessionLoadingPopup.IsOpen = IsActive && WindowState != WindowState.Minimized;
}
private void HideSessionLoading()
{
_sessionLoadingActive = false;
SessionLoadingPopup.IsOpen = false;
SessionLoadingPopup.IsOpen = true;
}
private void ReturnToLibraryWhileStopping()
@@ -1,140 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.Threading;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
// libKernel's address-wait primitives (sceKernelSyncOnAddress*) are the PS5's
// futex-style wait/wake: a thread parks on a guest address until another thread
// wakes that address. Guest runtimes (seen driving Juicy Realm, PPSA19268)
// build their own spinlocks/queues on top of it and call the wait in a hot
// loop; left unimplemented, every wait returns immediately and the runtime
// busy-spins forever (millions of calls, no forward progress).
//
// This implements wait/wake over the existing cooperative-block scheduler,
// keyed on the address. The real primitive takes a compare value so the wait
// only sleeps while the address still holds the expected value; that exact
// value is not recovered here, so each wait is given a bounded deadline and
// treated as a spurious-wakeup-tolerant park: a genuinely missed wake
// self-heals when the deadline expires and the guest re-checks its own
// condition, which futex callers already tolerate. A matching wake releases
// waiters immediately through the same key.
public static class KernelSyncOnAddressCompatExports
{
// Safety-net poll interval. Real releases come from the wake side (generation
// bump + WakeBlockedThreads); this only bounds how long a wait that genuinely
// raced/missed its wake stays parked before the guest re-evaluates. Kept
// large: a short interval turns every parked waiter into a hot re-poll that
// steals scheduler bandwidth from the threads that actually make progress
// (including the ones that would issue the wake), so it must be a rare last
// resort, not a spin substitute.
private static readonly TimeSpan WaitSelfHealTimeout = TimeSpan.FromMilliseconds(100);
// Per-address host gate for the non-cooperative (host main thread) fallback,
// which cannot use the guest-thread scheduler's block mechanism.
private static readonly ConcurrentDictionary<ulong, object> _hostAddressGates = new();
// Per-address wake generation. A wait captures the current generation and
// its wake predicate stays unsatisfied (keeps the thread parked) until a
// wake bumps it. This is what actually holds the thread blocked: a bare
// "always satisfied" predicate is treated as an immediate late-arrival by
// the dispatcher's race guard and never yields, leaving the guest to
// busy-spin. The generation also closes the register-vs-park race for free:
// a wake landing in that window bumps the generation, so the predicate is
// already satisfied and the guest correctly resumes at once.
private static readonly ConcurrentDictionary<ulong, long> _wakeGenerations = new();
private static long CurrentGeneration(ulong address) =>
_wakeGenerations.TryGetValue(address, out var generation) ? generation : 0;
private static string WakeKey(ulong address) => $"sceKernelSyncOnAddress:{address:X16}";
[SysAbiExport(
Nid = "Hc4CaR6JBL0",
ExportName = "sceKernelSyncOnAddressWait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int SyncOnAddressWait(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
if (address == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var observedGeneration = CurrentGeneration(address);
var deadline = GuestThreadExecution.ComputeDeadlineTimestamp(WaitSelfHealTimeout);
// Cooperative path: stay parked until a wake bumps this address's
// generation (or the deadline expires as a self-heal). The guest
// re-evaluates its own condition after resuming.
if (GuestThreadExecution.RequestCurrentThreadBlock(
ctx,
"sceKernelSyncOnAddressWait",
WakeKey(address),
resumeHandler: () => (int)OrbisGen2Result.ORBIS_GEN2_OK,
wakeHandler: () => CurrentGeneration(address) != observedGeneration,
deadline))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
// Non-cooperative caller (host main thread): bounded host wait so a
// missed wake self-heals instead of hanging.
var gate = _hostAddressGates.GetOrAdd(address, static _ => new object());
lock (gate)
{
if (CurrentGeneration(address) == observedGeneration)
{
Monitor.Wait(gate, WaitSelfHealTimeout);
}
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "q2y-wDIVWZA",
ExportName = "sceKernelSyncOnAddressWake",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int SyncOnAddressWake(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
if (address == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
// rsi carries the number of waiters to release (1 = wake-one, a large
// value = wake-all); default to all if it looks unset.
var requested = unchecked((long)ctx[CpuRegister.Rsi]);
var wakeCount = requested is > 0 and < int.MaxValue ? (int)requested : int.MaxValue;
// Bump the generation first so a wait that has registered but not yet
// parked sees the change and resumes instead of missing this wake.
_wakeGenerations.AddOrUpdate(address, 1, static (_, current) => current + 1);
GuestThreadExecution.Scheduler?.WakeBlockedThreads(WakeKey(address), wakeCount);
if (_hostAddressGates.TryGetValue(address, out var gate))
{
lock (gate)
{
Monitor.PulseAll(gate);
}
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
{
var value = (int)result;
ctx[CpuRegister.Rax] = unchecked((ulong)value);
return value;
}
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Security.Cryptography;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Random;
public static class RandomExports
{
private const int RandomErrorInvalid = unchecked((int)0x817C0016);
private const int MaxRandomBytes = 64;
[SysAbiExport(
Nid = "PI7jIZj4pcE",
ExportName = "sceRandomGetRandomNumber",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRandom")]
public static int RandomGetRandomNumber(CpuContext ctx)
{
var destination = ctx[CpuRegister.Rdi];
var size = ctx[CpuRegister.Rsi];
if ((destination == 0 && size != 0) || size > MaxRandomBytes)
{
return ctx.SetReturn(RandomErrorInvalid);
}
if (size == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
Span<byte> bytes = stackalloc byte[(int)size];
RandomNumberGenerator.Fill(bytes);
return ctx.Memory.TryWrite(destination, bytes)
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK)
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
@@ -953,13 +953,22 @@ public static partial class Gen5SpirvTranslator
case "VPkMulF16":
case "VPkMinF16":
case "VPkMaxF16":
case "VPkFmaF16":
if (!TryEmitPackedF16(instruction, out result, out error))
{
return false;
}
break;
case "VPkFmaF16":
// Deliberately loud: a fused f16 FMA rounds the product+add once,
// whereas doing the multiply-add in f32 and rounding to f16 at the
// end double-rounds. Concrete miss: fma(0x4100, 0x7522, 0x04EA) is
// 0x7A6B fused but 0x7A6A via f32. Exact emulation (round-to-odd
// f32 product then RNE pack) is a planned follow-up slice.
error =
$"unsupported vop3p opcode {instruction.Opcode} " +
"(fused f16 FMA requires single-rounding; deferred to a later slice)";
return false;
default:
error = $"unsupported vector opcode {instruction.Opcode}";
return false;
@@ -999,9 +1008,8 @@ public static partial class Gen5SpirvTranslator
// even. For add and mul this is bit-exact to a true f16 op (the f32 result
// rounds losslessly to f16 by the double-rounding theorem; a f16 product even
// fits in f32 exactly). min/max carry no rounding, so they are exact once the
// conversions are. v_pk_fma_f16 cannot be reproduced by a plain f32
// multiply-add plus a pack (that double-rounds), so it goes through the
// round-to-odd sequence in EmitPackedF16FusedMultiplyAdd instead.
// conversions are. v_pk_fma_f16 is intentionally not routed here because a
// fused f16 FMA cannot be reproduced by an f32 multiply-add plus a pack.
private bool TryEmitPackedF16(
Gen5ShaderInstruction instruction,
out uint result,
@@ -1021,8 +1029,7 @@ public static partial class Gen5SpirvTranslator
return false;
}
var sourceCount = instruction.Opcode == "VPkFmaF16" ? 3 : 2;
for (var index = 0; index < sourceCount; index++)
for (var index = 0; index < 2; index++)
{
var source = instruction.Sources[index];
if (source.Kind is not (Gen5OperandKind.VectorRegister or Gen5OperandKind.ScalarRegister))
@@ -1047,12 +1054,6 @@ public static partial class Gen5SpirvTranslator
{
var left = EmitPackedF16Operand(instruction, control, 0, highLane);
var right = EmitPackedF16Operand(instruction, control, 1, highLane);
if (instruction.Opcode == "VPkFmaF16")
{
var addend = EmitPackedF16Operand(instruction, control, 2, highLane);
return EmitFloatToHalf(EmitPackedF16FusedMultiplyAdd(left, right, addend));
}
var value = instruction.Opcode switch
{
"VPkAddF16" => _module.AddInstruction(SpirvOp.FAdd, _floatType, left, right),
@@ -1064,75 +1065,6 @@ public static partial class Gen5SpirvTranslator
return EmitFloatToHalf(Bitcast(_uintType, value));
}
// Fused f16 multiply-add with a single rounding, emulated in f32 without the
// Float16 capability. The f32 product of two widened f16 values is exact
// (11-bit significands, and the exponent stays inside the f32 normal range:
// any non-zero product magnitude is in [2^-48, 2^33]), so only the addition
// rounds. An f32 add then an f16 pack would round twice; instead the add is
// corrected to round-to-odd, which a following round-to-nearest-even pack
// turns into the exactly-once-rounded fused result (innocuous double rounding
// holds because f32 carries 24 significand bits >= 11 + 2).
//
// sum = RN(product + addend); Knuth's 2Sum recovers the exact residual
// (product + addend) - sum from four more RN ops. 2Sum is exact for any two
// finite f32 inputs; no intermediate here can overflow (|product| < 2^33,
// |addend| < 2^16) and none can enter the f32 subnormal range (every finite
// value in play is a multiple of 2^-48 by construction), so implementation
// f32 denorm-flush modes never see a denormal. If the residual says the sum
// was inexact and the sum's significand is even, step one ulp towards the
// true value: consecutive floats have consecutive sign-magnitude encodings,
// so that neighbour is the enclosing float with the odd significand.
//
// Inf/NaN inputs make the residual NaN (e.g. sum - addend = Inf - Inf); the
// ordered compare below is then false and the IEEE sum passes through
// unchanged. A residual of zero also covers the exact-sum case, where the
// parity fix must not fire. Returns the round-to-odd f32 bit pattern.
private uint EmitPackedF16FusedMultiplyAdd(uint left, uint right, uint addend)
{
var product = EmitPreciseFloat(SpirvOp.FMul, left, right);
var sum = EmitPreciseFloat(SpirvOp.FAdd, product, addend);
var productPart = EmitPreciseFloat(SpirvOp.FSub, sum, addend);
var addendPart = EmitPreciseFloat(SpirvOp.FSub, sum, productPart);
var productError = EmitPreciseFloat(SpirvOp.FSub, product, productPart);
var addendError = EmitPreciseFloat(SpirvOp.FSub, addend, addendPart);
var residual = EmitPreciseFloat(SpirvOp.FAdd, productError, addendError);
var sumBits = Bitcast(_uintType, sum);
var residualBits = Bitcast(_uintType, residual);
var inexact = _module.AddInstruction(
SpirvOp.FOrdNotEqual, _boolType, residual, Float(0));
var evenSignificand = Equal(BitwiseAnd(sumBits, UInt(1)), 0);
var adjust = _module.AddInstruction(
SpirvOp.LogicalAnd, _boolType, inexact, evenSignificand);
// Residual sign relative to the sum picks the step direction: same sign
// means the true value lies away from zero (encoding + 1), opposite sign
// means towards zero (encoding - 1). The sum cannot be zero here (any
// inexact sum has magnitude >= 2^-48) and cannot be the largest finite
// value (its significand is odd), so the step never crosses zero or Inf.
var towardZero = IsNotZero(
BitwiseAnd(BitwiseXor(sumBits, residualBits), UInt(0x8000_0000)));
var stepped = SelectU(
towardZero,
ISubU(sumBits, UInt(1)),
IAdd(sumBits, UInt(1)));
return SelectU(adjust, stepped, sumBits);
}
// A float op the driver must evaluate exactly as written. The 2Sum
// residual above is error-free only op by op; without NoContraction
// driver compilers fold the sequence (e.g. contract product+sum into an
// f32 fma and simplify the rebuilt terms), collapsing the residual to
// zero. Observed on AMD RDNA3 Windows: the pinned midpoint case decays
// to the double-rounded result unless every op in the chain is marked.
private uint EmitPreciseFloat(SpirvOp operation, uint left, uint right)
{
var value = _module.AddInstruction(operation, _floatType, left, right);
_module.AddDecoration(value, SpirvDecoration.NoContraction);
return value;
}
// Reads source `index`, selects the half feeding this lane (op_sel / op_sel_hi),
// widens it exactly to f32 and applies the lane's negate modifier (neg_lo / neg_hi).
private uint EmitPackedF16Operand(
@@ -238,7 +238,6 @@ public enum SpirvDecoration : uint
Binding = 33,
DescriptorSet = 34,
Offset = 35,
NoContraction = 42,
}
public enum SpirvBuiltIn : uint
@@ -1,26 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Core.Cpu.Native;
using Xunit;
namespace SharpEmu.Libs.Tests.Cpu;
public sealed unsafe class JitStubsTests
{
[Fact]
public void FindTlsAccessPatterns_IncludesLastValidOffset()
{
var pattern = JitStubs.TlsAccessPattern;
var code = new byte[pattern.Length + 3];
pattern.CopyTo(code.AsSpan(3));
fixed (byte* codePointer = code)
{
var matches = JitStubs.FindTlsAccessPatterns(codePointer, code.Length);
var match = Assert.Single(matches);
Assert.Equal((nint)(codePointer + 3), match);
}
}
}
@@ -1,137 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.Libs.LibcInternal;
using Xunit;
namespace SharpEmu.Libs.Tests.LibcInternal;
public sealed class LibcInternalExportsTests
{
private const ulong Base = 0x3_0000_0000;
private const ulong InfoAddress = Base + 0x100;
private const ulong ExpectedInfoSize = 32;
[Fact]
public void HeapGetTraceInfo_NullPointer_ReturnsInvalidArgument()
{
var memory = new FakeCpuMemory(Base, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
context[CpuRegister.Rdi] = 0;
var result = LibcInternalExports.LibcHeapGetTraceInfo(context);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
result);
Assert.Equal(0UL, context[CpuRegister.Rax]);
}
[Fact]
public void HeapGetTraceInfo_WrongSize_ReturnsInvalidArgument()
{
var memory = new FakeCpuMemory(Base, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
Span<byte> sizeBytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(
sizeBytes,
ExpectedInfoSize - 1);
Assert.True(memory.TryWrite(InfoAddress, sizeBytes));
context[CpuRegister.Rdi] = InfoAddress;
var result = LibcInternalExports.LibcHeapGetTraceInfo(context);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
result);
Assert.Equal(0UL, context[CpuRegister.Rax]);
}
[Fact]
public void HeapGetTraceInfo_ValidBuffer_WritesStablePointers()
{
var memory = new FakeCpuMemory(Base, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
Span<byte> sizeBytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(
sizeBytes,
ExpectedInfoSize);
Assert.True(memory.TryWrite(InfoAddress, sizeBytes));
context[CpuRegister.Rdi] = InfoAddress;
var firstResult =
LibcInternalExports.LibcHeapGetTraceInfo(context);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
firstResult);
Assert.Equal(0UL, context[CpuRegister.Rax]);
Assert.True(
context.TryReadUInt64(
InfoAddress + 16,
out var firstMaskAddress));
Assert.True(
context.TryReadUInt64(
InfoAddress + 24,
out var firstTableAddress));
Assert.NotEqual(0UL, firstMaskAddress);
Assert.Equal(firstMaskAddress + 8UL, firstTableAddress);
var secondResult =
LibcInternalExports.LibcHeapGetTraceInfo(context);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
secondResult);
Assert.True(
context.TryReadUInt64(
InfoAddress + 16,
out var secondMaskAddress));
Assert.True(
context.TryReadUInt64(
InfoAddress + 24,
out var secondTableAddress));
Assert.Equal(firstMaskAddress, secondMaskAddress);
Assert.Equal(firstTableAddress, secondTableAddress);
}
[Fact]
public void HeapGetTraceInfo_TruncatedOutput_ReturnsMemoryFault()
{
var memory = new FakeCpuMemory(Base, 31);
var context = new CpuContext(memory, Generation.Gen5);
Span<byte> sizeBytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(
sizeBytes,
ExpectedInfoSize);
Assert.True(memory.TryWrite(Base, sizeBytes));
context[CpuRegister.Rdi] = Base;
var result = LibcInternalExports.LibcHeapGetTraceInfo(context);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT,
result);
Assert.Equal(0UL, context[CpuRegister.Rax]);
}
}
@@ -1,69 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Random;
using Xunit;
namespace SharpEmu.Libs.Tests.Random;
public sealed class RandomExportsTests
{
private const ulong BaseAddress = 0x1000;
private const int RandomErrorInvalid = unchecked((int)0x817C0016);
[Fact]
public void GetRandomNumberWritesRequestedBytes()
{
var memory = new FakeCpuMemory(BaseAddress, 64);
var ctx = CreateContext(memory, BaseAddress, 64);
Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx));
var bytes = new byte[64];
Assert.True(memory.TryRead(BaseAddress, bytes));
Assert.NotEqual(new byte[64], bytes);
}
[Theory]
[InlineData(0, 1)]
[InlineData(BaseAddress, 65)]
public void GetRandomNumberRejectsInvalidArguments(ulong address, ulong size)
{
var memory = new FakeCpuMemory(BaseAddress, 64);
var ctx = CreateContext(memory, address, size);
Assert.Equal(RandomErrorInvalid, RandomExports.RandomGetRandomNumber(ctx));
}
[Fact]
public void GetRandomNumberAcceptsEmptyRequest()
{
var memory = new FakeCpuMemory(BaseAddress, 64);
var ctx = CreateContext(memory, 0, 0);
Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx));
}
[Fact]
public void GetRandomNumberReportsUnmappedDestination()
{
var memory = new FakeCpuMemory(BaseAddress, 64);
var ctx = CreateContext(memory, BaseAddress + 64, 1);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT,
RandomExports.RandomGetRandomNumber(ctx));
}
private static CpuContext CreateContext(
ICpuMemory memory,
ulong destination,
ulong size)
{
var ctx = new CpuContext(memory, Generation.Gen5);
ctx[CpuRegister.Rdi] = destination;
ctx[CpuRegister.Rsi] = size;
return ctx;
}
}
@@ -12,11 +12,6 @@
// [2] v_mul_lo_i32 -> low 32 bits of the same product
// [3] store attempted with EXEC=0 -> must NOT land (sentinel remains)
// [4] store after EXEC restored -> 1.5f (0x3FC00000)
// [5] v_pk_fma_f16 fma(2.5h, 21024h, 7.496e-5h) -> 0x7A6B packed; the exact
// sum sits just above an f16 midpoint, so a double-rounded f32
// multiply-add would give 0x7A6A instead
// [6] the same fma with the addend negated -> 0x7A6A packed (just below the
// same midpoint), pinning the opposite rounding direction
// Every other word of the buffer must still hold the sentinel afterwards.
//
// Creating the compute pipeline doubles as a driver-acceptance check for the
@@ -40,12 +35,6 @@ var expectedHi = (uint)(product >> 32);
var expectedLo = (uint)product;
var expectedRestored = BitConverter.SingleToUInt32Bits(1.5f);
// v_pk_fma_f16 of (0x4100, 0x7522, 0x04EA) per lane: the exact product
// 2.5 * 21024 = 52560 is an f16 tie (between 0x7A6A and 0x7A6B), so the tiny
// addend decides the rounding direction under a single fused rounding.
const uint ExpectedPkFma = 0x7A6B_7A6B;
const uint ExpectedPkFmaNeg = 0x7A6A_7A6A;
unsafe
{
var spvPath = args.Length > 0
@@ -363,8 +352,6 @@ unsafe
("v_mul_lo_i32 lo(0x7FFFFFFF*0x10003)", words[2], expectedLo),
("exec=0 store suppressed (offset 12 sentinel)", words[3], Sentinel),
("store after exec restore (offset 16)", words[4], expectedRestored),
("v_pk_fma_f16 fused rounds up at midpoint", words[5], ExpectedPkFma),
("v_pk_fma_f16 neg addend rounds down", words[6], ExpectedPkFmaNeg),
};
var failures = 0;
foreach (var (name, actual, expected) in results)
+1 -28
View File
@@ -42,23 +42,6 @@ const ulong ProgramAddress = 0x100000;
0xD56C0005, 0x00020501, // v_mul_hi_i32 v5, v1, v2
0xBF810000, // s_endpgm
]),
// Packed f16 (VOP3P) arithmetic, including the fused multiply-add. The
// constants pin the double-rounding regression from the VOP3P first slice:
// fma(0x4100, 0x7522, 0x04EA) must round once to 0x7A6B (an f32
// multiply-add then pack yields 0x7A6A). The last fma exercises the src2
// neg_lo/neg_hi modifier path.
("pk-f16", true, [
0x7E0002FF, 0x41004100, // v_mov_b32 v0, 0x41004100 (2.5 packed)
0x7E0202FF, 0x75227522, // v_mov_b32 v1, 0x75227522 (21024 packed)
0x7E0402FF, 0x04EA04EA, // v_mov_b32 v2, 0x04EA04EA (~7.496e-5 packed)
0xCC0E4003, 0x1C0A0300, // v_pk_fma_f16 v3, v0, v1, v2
0xCC0F4004, 0x18020500, // v_pk_add_f16 v4, v0, v2
0xCC104005, 0x18020300, // v_pk_mul_f16 v5, v0, v1
0xCC114006, 0x18020300, // v_pk_min_f16 v6, v0, v1
0xCC124007, 0x18020300, // v_pk_max_f16 v7, v0, v1
0xCC0E4408, 0x9C0A0300, // v_pk_fma_f16 v8, v0, v1, neg_lo:[0,0,1] neg_hi:[0,0,1] v2
0xBF810000, // s_endpgm
]),
("mrt", true, [
0x7E0002FF, 0x3F800000, // v_mov_b32 v0, 1.0f
0x7E0202FF, 0x00000000, // v_mov_b32 v1, 0.0f
@@ -132,10 +115,7 @@ const ulong ProgramAddress = 0x100000;
// Executable end-to-end test: compute with real ALU instructions, then
// buffer_store_dword results to guestBuffers[0] at offsets 0/4/8, prove
// that a store with EXEC=0 does not land (offset 12 stays sentinel), and
// that stores work again after EXEC is restored (offset 16). Offsets 20/24
// hold the packed fused f16 FMA and its negated-addend twin, whose exact
// results (0x7A6B7A6B / 0x7A6A7A6A) straddle an f16 midpoint and therefore
// catch any double-rounding regression on real hardware.
// that stores work again after EXEC is restored (offset 16).
("exec", true, [
0xBFA10001, // s_clause 0x1 (hint no-op in an executed program, needs #108)
0x7E0002FF, 0x3FC00000, // v_mov_b32 v0, 1.5f
@@ -153,13 +133,6 @@ const ulong ProgramAddress = 0x100000;
0xE070000C, 0x80020200, // buffer_store_dword v2, off, s[8:11], 0 offset:12 (masked, must not land)
0xBEFE03C1, // s_mov_b32 exec_lo, -1 -> lane active again
0xE0700010, 0x80020000, // buffer_store_dword v0, off, s[8:11], 0 offset:16
0x7E0E02FF, 0x41004100, // v_mov_b32 v7, 0x41004100 (2.5 packed)
0x7E1002FF, 0x75227522, // v_mov_b32 v8, 0x75227522 (21024 packed)
0x7E1202FF, 0x04EA04EA, // v_mov_b32 v9, 0x04EA04EA (~7.496e-5 packed)
0xCC0E400A, 0x1C261107, // v_pk_fma_f16 v10, v7, v8, v9
0xCC0E440B, 0x9C261107, // v_pk_fma_f16 v11, v7, v8, neg_lo:[0,0,1] neg_hi:[0,0,1] v9
0xE0700014, 0x80020A00, // buffer_store_dword v10, off, s[8:11], 0 offset:20
0xE0700018, 0x80020B00, // buffer_store_dword v11, off, s[8:11], 0 offset:24
0xBF810000, // s_endpgm
]),
];