Compare commits

...

8 Commits

Author SHA1 Message Date
ParantezTech cecf55bf2e fix: dereference of a possibly null reference. 2026-03-16 21:19:49 +03:00
ParantezTech 0ede7c70a9 [ci] update actions Node 20 deprecation 2026-03-16 21:18:58 +03:00
ParantezTech 8c39a55501 reuse 2026-03-16 20:49:40 +03:00
ParantezTech 0a4b36ffcb [dotnet/ci] constant version 2026-03-16 20:46:47 +03:00
ParantezTech 1a1d61b21c rework TLS allocations, more HLE's, increase import loop history 2026-03-16 20:22:58 +03:00
ParantezTech 3cf96f38de [github] bug_report fix 2026-03-16 20:14:58 +03:00
ParantezTech d8c44bb64f [github] added bug report issue template 2026-03-16 20:11:56 +03:00
ParantezTech a4b5171586 [github] added issue config & reuse 2026-03-16 20:01:08 +03:00
11 changed files with 179 additions and 27 deletions
+59
View File
@@ -0,0 +1,59 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Bug Report
description: Report a bug in the emulator
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
### Before creating an issue
- Do not create issues involving **software piracy**. Our rules specifically prohibit this.
Issues related to piracy will be closed and the author may be banned from the repository.
- The issue tracker is **not for asking questions**.
Question-type issues will be closed.
- Only open an issue if you are reporting an actual **bug in the emulator**.
- type: input
id: emulator_version
attributes:
label: Emulator Version / Commit
description: The emulator version or git commit where the bug occurred
placeholder: e.g. v0.0.2 / a1b2c3d
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: Describe the problem and how to reproduce it
placeholder: |
Steps to reproduce:
1. Launch emulator
2. Load game
3. Crash occurs
Expected behavior:
...
Actual behavior:
...
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log File
description: |
Drag & drop your log file here, or paste the log contents.
Supported: .log / .txt / .zip
Large logs should be uploaded as a file.
render: shell
+4
View File
@@ -0,0 +1,4 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
blank_issues_enabled: false
@@ -1,3 +1,6 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Game Compatibility Report name: Game Compatibility Report
description: Report compatibility status of a game description: Report compatibility status of a game
labels: ["game-compatibility"] labels: ["game-compatibility"]
+6 -3
View File
@@ -1,3 +1,6 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Build and Release name: Build and Release
on: on:
@@ -79,7 +82,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v5
with: with:
dotnet-version: 10.0.x dotnet-version: 10.0.103
cache: true cache: true
cache-dependency-path: | cache-dependency-path: |
Directory.Packages.props Directory.Packages.props
@@ -106,7 +109,7 @@ jobs:
Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: ${{ needs.init.outputs.artifact-name }} name: ${{ needs.init.outputs.artifact-name }}
path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }} path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }}
@@ -123,7 +126,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Download build artifact - name: Download build artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: ${{ needs.init.outputs.artifact-name }} name: ${{ needs.init.outputs.artifact-name }}
path: release path: release
+1
View File
@@ -3,6 +3,7 @@ version = 1
[[annotations]] [[annotations]]
path = [ path = [
"REUSE.toml", "REUSE.toml",
"global.json",
"**/packages.lock.json", "**/packages.lock.json",
"scripts/ps5_names.txt", "scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin", "src/SharpEmu.HLE/Aerolib/aerolib.bin",
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "10.0.103",
"rollForward": "latestFeature"
}
}
@@ -231,9 +231,6 @@ public sealed partial class DirectExecutionBackend
ProbeReturnRip(num7, num); ProbeReturnRip(num7, num);
Console.Error.Flush(); Console.Error.Flush();
} }
ProbeReturnRip(0x0000000800EA020Eul, 999001);
ProbeReturnRip(0x0000000800EA0213ul, 999002);
ProbeReturnRip(0x0000000800EA040Aul, 999003);
try try
{ {
OrbisGen2Result orbisGen2Result; OrbisGen2Result orbisGen2Result;
@@ -426,7 +423,20 @@ public sealed partial class DirectExecutionBackend
return false; return false;
} }
int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3); int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3);
return num2 <= 2; if (num2 > 2)
{
return false;
}
int num3 = Math.Min(_importLoopSignatureCount, Math.Max(sampleCount * 8, ImportLoopWideDiversityWindow));
if (num3 <= sampleCount)
{
return true;
}
if (CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, num3, 3) > 2)
{
return false;
}
return CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, num3, 3) <= 2;
} }
private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter) private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter)
@@ -13,6 +13,10 @@ namespace SharpEmu.Core.Cpu.Native;
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable
{ {
private const int ImportLoopHistoryLength = 2048;
private const int ImportLoopWideDiversityWindow = 768;
private readonly struct ImportStubEntry private readonly struct ImportStubEntry
{ {
public ulong Address { get; } public ulong Address { get; }
@@ -89,7 +93,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private const uint PAGE_EXECUTE_READ = 32u; private const uint PAGE_EXECUTE_READ = 32u;
private const int TlsHandlerRegionSize = 4096; private const int TlsHandlerRegionSize = 16384;
private const ulong TlsModuleAllocStart = 140726751354880uL; private const ulong TlsModuleAllocStart = 140726751354880uL;
@@ -179,11 +183,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private ulong _entryReturnSentinelRip; private ulong _entryReturnSentinelRip;
private readonly ulong[] _importLoopSignatures = new ulong[192]; private readonly ulong[] _importLoopSignatures = new ulong[ImportLoopHistoryLength];
private readonly ulong[] _importLoopNidHashes = new ulong[192]; private readonly ulong[] _importLoopNidHashes = new ulong[ImportLoopHistoryLength];
private readonly ulong[] _importLoopReturnRips = new ulong[192]; private readonly ulong[] _importLoopReturnRips = new ulong[ImportLoopHistoryLength];
private int _importLoopSignatureCount; private int _importLoopSignatureCount;
@@ -601,14 +605,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{ {
return exportName switch return exportName switch
{ {
"_init_env" or
"atexit" or
"strlen" or
"strnlen" or
"strcmp" or
"strncmp" or
"strcpy" or
"strncpy" or
"memcpy" or "memcpy" or
"memmove" or "memmove" or
"memset" or "memset" or
@@ -40,6 +40,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect); private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll")]
private static extern nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize);
@@ -351,6 +354,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return true; return true;
} }
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{
return false;
}
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages)) if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
{ {
return false; return false;
@@ -389,6 +397,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return true; return true;
} }
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{
return false;
}
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect)) if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
{ {
return false; return false;
@@ -494,6 +507,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY; return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
} }
private static uint GetCommitProtection(MemoryRegion region)
{
return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
}
private static unsafe bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region)
{
if (size == 0 || !region.IsReservedOnly)
{
return true;
}
var startPage = AlignDown(address, PageSize);
var endPage = AlignUp(address + size, PageSize);
var commitProtection = GetCommitProtection(region);
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
{
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
{
return false;
}
if (info.State == MEM_COMMIT)
{
continue;
}
if (info.State != MEM_RESERVE)
{
return false;
}
if (VirtualAlloc((void*)pageAddress, (nuint)PageSize, MEM_COMMIT, commitProtection) == null)
{
return false;
}
}
return true;
}
private bool TryTemporarilyProtectForRead( private bool TryTemporarilyProtectForRead(
ulong address, ulong address,
ulong size, ulong size,
@@ -558,4 +613,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
public bool IsReservedOnly { get; set; } public bool IsReservedOnly { get; set; }
public uint Protection { get; set; } public uint Protection { get; set; }
} }
private struct MemoryBasicInformation64
{
public ulong BaseAddress;
public ulong AllocationBase;
public uint AllocationProtect;
public uint Alignment1;
public ulong RegionSize;
public uint State;
public uint Protect;
public uint Type;
public uint Alignment2;
}
} }
@@ -3,6 +3,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis;
namespace SharpEmu.Libs.Kernel; namespace SharpEmu.Libs.Kernel;
@@ -258,14 +259,14 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadCondSignal", ExportName = "scePthreadCondSignal",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: false); public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
[SysAbiExport( [SysAbiExport(
Nid = "JGgj7Uvrl+A", Nid = "JGgj7Uvrl+A",
ExportName = "scePthreadCondBroadcast", ExportName = "scePthreadCondBroadcast",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true); public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport( [SysAbiExport(
Nid = "Op8TBGY5KHg", Nid = "Op8TBGY5KHg",
@@ -279,7 +280,7 @@ public static class KernelPthreadCompatExports
ExportName = "pthread_cond_broadcast", ExportName = "pthread_cond_broadcast",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true); public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport( [SysAbiExport(
Nid = "m5-2bsNfv7s", Nid = "m5-2bsNfv7s",
@@ -606,7 +607,7 @@ public static class KernelPthreadCompatExports
return mutexAddress; return mutexAddress;
} }
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, out PthreadMutexState? state) private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
{ {
resolvedAddress = 0; resolvedAddress = 0;
state = null; state = null;
@@ -739,7 +740,7 @@ public static class KernelPthreadCompatExports
return condAddress; return condAddress;
} }
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, out PthreadCondState? state) private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadCondState? state)
{ {
resolvedAddress = 0; resolvedAddress = 0;
state = null; state = null;
@@ -901,14 +902,14 @@ public static class KernelPthreadCompatExports
return waitResult; return waitResult;
} }
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast) private static int PthreadCondSignalCore(CpuContext ctx, ulong condAddress, bool broadcast)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
if (!TryResolveCondState(null, condAddress, createIfZero: false, out _, out var state)) if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
@@ -5,6 +5,7 @@ using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis;
namespace SharpEmu.Libs.Kernel; namespace SharpEmu.Libs.Kernel;
@@ -928,7 +929,7 @@ public static class KernelPthreadExtendedCompatExports
return rwlockAddress; return rwlockAddress;
} }
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, out ReaderWriterLockSlim? rwlock) private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out ReaderWriterLockSlim? rwlock)
{ {
resolvedAddress = 0; resolvedAddress = 0;
rwlock = null; rwlock = null;