mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-27 05:01:07 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be6a6a5935 | |||
| caf859cc52 |
Binary file not shown.
|
After Width: | Height: | Size: 698 B |
Binary file not shown.
|
After Width: | Height: | Size: 802 B |
@@ -5,6 +5,7 @@ using SharpEmu.Core.Runtime;
|
|||||||
using SharpEmu.Core.Cpu;
|
using SharpEmu.Core.Cpu;
|
||||||
using SharpEmu.GUI;
|
using SharpEmu.GUI;
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.VideoOut;
|
||||||
using SharpEmu.Logging;
|
using SharpEmu.Logging;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -111,8 +112,16 @@ internal static partial class Program
|
|||||||
using var runtime = SharpEmuRuntime.CreateDefault(runtimeOptions);
|
using var runtime = SharpEmuRuntime.CreateDefault(runtimeOptions);
|
||||||
|
|
||||||
OrbisGen2Result result;
|
OrbisGen2Result result;
|
||||||
|
ConsoleCancelEventHandler? cancelHandler = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
cancelHandler = (_, eventArgs) =>
|
||||||
|
{
|
||||||
|
eventArgs.Cancel = true;
|
||||||
|
VideoOutExports.NotifyHostInterrupt();
|
||||||
|
};
|
||||||
|
Console.CancelKeyPress += cancelHandler;
|
||||||
|
|
||||||
Console.Error.WriteLine($"[DEBUG] Running: {ebootPath}");
|
Console.Error.WriteLine($"[DEBUG] Running: {ebootPath}");
|
||||||
result = runtime.Run(ebootPath);
|
result = runtime.Run(ebootPath);
|
||||||
Console.Error.WriteLine($"[DEBUG] Result: {result}");
|
Console.Error.WriteLine($"[DEBUG] Result: {result}");
|
||||||
@@ -123,6 +132,13 @@ internal static partial class Program
|
|||||||
Log.Error("SharpEmu failed to run.", ex);
|
Log.Error("SharpEmu failed to run.", ex);
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (cancelHandler is not null)
|
||||||
|
{
|
||||||
|
Console.CancelKeyPress -= cancelHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info($"SharpEmu execution completed. Result={result} (0x{(int)result:X8})");
|
Log.Info($"SharpEmu execution completed. Result={result} (0x{(int)result:X8})");
|
||||||
if (!string.IsNullOrWhiteSpace(runtime.LastSessionSummary))
|
if (!string.IsNullOrWhiteSpace(runtime.LastSessionSummary))
|
||||||
|
|||||||
@@ -238,6 +238,22 @@ public sealed partial class DirectExecutionBackend
|
|||||||
cpuContext[CpuRegister.Rax] = 0uL;
|
cpuContext[CpuRegister.Rax] = 0uL;
|
||||||
return 0uL;
|
return 0uL;
|
||||||
}
|
}
|
||||||
|
if (_hostShutdownRequested)
|
||||||
|
{
|
||||||
|
if (isGuestWorker &&
|
||||||
|
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, "host shutdown"))
|
||||||
|
{
|
||||||
|
cpuContext[CpuRegister.Rax] = 0uL;
|
||||||
|
return 0uL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGuestWorker &&
|
||||||
|
TryAbortGuestForHostShutdown(argPackPtr, num, num7))
|
||||||
|
{
|
||||||
|
cpuContext[CpuRegister.Rax] = 1uL;
|
||||||
|
return 1uL;
|
||||||
|
}
|
||||||
|
}
|
||||||
bool flag0 = ShouldSuppressStrlenTrace(importStubEntry.Nid);
|
bool flag0 = ShouldSuppressStrlenTrace(importStubEntry.Nid);
|
||||||
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
|
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
|
||||||
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;
|
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;
|
||||||
@@ -975,6 +991,31 @@ public sealed partial class DirectExecutionBackend
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe bool TryAbortGuestForHostShutdown(nint argPackPtr, long dispatchIndex, ulong returnRip)
|
||||||
|
{
|
||||||
|
ulong hostExit = ActiveEntryReturnSentinelRip;
|
||||||
|
if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
*(ulong*)(argPackPtr + 96) = hostExit;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ActiveForcedGuestExit = true;
|
||||||
|
if (string.IsNullOrWhiteSpace(LastError))
|
||||||
|
{
|
||||||
|
LastError = "Host shutdown requested.";
|
||||||
|
}
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Guest unwind for host shutdown at import#{dispatchIndex} ret=0x{returnRip:X16} -> host_exit=0x{hostExit:X16}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, ulong value)
|
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, ulong value)
|
||||||
{
|
{
|
||||||
ulong hostExit = ActiveEntryReturnSentinelRip;
|
ulong hostExit = ActiveEntryReturnSentinelRip;
|
||||||
|
|||||||
@@ -576,6 +576,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
// thread entering a freed stub).
|
// thread entering a freed stub).
|
||||||
private volatile bool _guestTeardownRequested;
|
private volatile bool _guestTeardownRequested;
|
||||||
|
|
||||||
|
private volatile bool _hostShutdownRequested;
|
||||||
|
|
||||||
|
private static volatile DirectExecutionBackend? _activeSessionBackend;
|
||||||
|
|
||||||
private int _readyGuestThreadCount;
|
private int _readyGuestThreadCount;
|
||||||
|
|
||||||
private readonly Dictionary<ulong, GuestThreadState> _guestThreads = new Dictionary<ulong, GuestThreadState>();
|
private readonly Dictionary<ulong, GuestThreadState> _guestThreads = new Dictionary<ulong, GuestThreadState>();
|
||||||
@@ -812,7 +816,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private bool ActiveForcedGuestExit
|
private bool ActiveForcedGuestExit
|
||||||
{
|
{
|
||||||
get => HasActiveExecutionThread ? _activeForcedGuestExit : _forcedGuestExit;
|
get => _hostShutdownRequested ||
|
||||||
|
(HasActiveExecutionThread ? _activeForcedGuestExit : _forcedGuestExit);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (HasActiveExecutionThread)
|
if (HasActiveExecutionThread)
|
||||||
@@ -974,7 +979,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
_importLoopGuardSeconds = GetImportLoopGuardSeconds();
|
_importLoopGuardSeconds = GetImportLoopGuardSeconds();
|
||||||
_entryReturnSentinelRip = 0uL;
|
_entryReturnSentinelRip = 0uL;
|
||||||
_forcedGuestExit = false;
|
_forcedGuestExit = false;
|
||||||
|
_hostShutdownRequested = false;
|
||||||
_guestTeardownRequested = false;
|
_guestTeardownRequested = false;
|
||||||
|
_activeSessionBackend = this;
|
||||||
|
HostSessionControl.SetShutdownHandler(RequestHostShutdown);
|
||||||
_importLoopSignatureCount = 0;
|
_importLoopSignatureCount = 0;
|
||||||
_importLoopSignatureWriteIndex = 0;
|
_importLoopSignatureWriteIndex = 0;
|
||||||
_importLoopPatternHits = 0;
|
_importLoopPatternHits = 0;
|
||||||
@@ -1022,12 +1030,28 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
HostSessionControl.SetShutdownHandler(null);
|
||||||
|
if (ReferenceEquals(_activeSessionBackend, this))
|
||||||
|
{
|
||||||
|
_activeSessionBackend = null;
|
||||||
|
}
|
||||||
DrainDeferredBootstrapTraces();
|
DrainDeferredBootstrapTraces();
|
||||||
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
||||||
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void RequestHostShutdown(string reason)
|
||||||
|
{
|
||||||
|
_hostShutdownRequested = true;
|
||||||
|
_forcedGuestExit = true;
|
||||||
|
_guestTeardownRequested = true;
|
||||||
|
LastError = string.IsNullOrWhiteSpace(reason)
|
||||||
|
? "Host shutdown requested."
|
||||||
|
: $"Host shutdown requested: {reason}";
|
||||||
|
Console.Error.WriteLine($"[LOADER][INFO] {LastError}");
|
||||||
|
}
|
||||||
|
|
||||||
private bool SetupImportStubs(IReadOnlyDictionary<ulong, string> importStubs)
|
private bool SetupImportStubs(IReadOnlyDictionary<ulong, string> importStubs)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][INFO] Setting up {importStubs.Count} import stubs...");
|
Console.Error.WriteLine($"[LOADER][INFO] Setting up {importStubs.Count} import stubs...");
|
||||||
@@ -4425,7 +4449,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
result = OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP;
|
result = OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP;
|
||||||
if (string.IsNullOrEmpty(LastError))
|
if (string.IsNullOrEmpty(LastError))
|
||||||
{
|
{
|
||||||
LastError = "Detected repeating import loop and forced guest unwind to host.";
|
LastError = _hostShutdownRequested
|
||||||
|
? "Host shutdown requested."
|
||||||
|
: "Detected repeating import loop and forced guest unwind to host.";
|
||||||
}
|
}
|
||||||
Console.Error.WriteLine("[LOADER][ERROR] " + LastError);
|
Console.Error.WriteLine("[LOADER][ERROR] " + LastError);
|
||||||
RequestGuestThreadTeardown(3000);
|
RequestGuestThreadTeardown(3000);
|
||||||
|
|||||||
@@ -125,5 +125,13 @@
|
|||||||
"Dialog.PsExecutables": "PS executables",
|
"Dialog.PsExecutables": "PS executables",
|
||||||
"Dialog.SaveLogFile": "Select where to save the Log file",
|
"Dialog.SaveLogFile": "Select where to save the Log file",
|
||||||
"Dialog.PlainTextFiles": "Plain Text Files",
|
"Dialog.PlainTextFiles": "Plain Text Files",
|
||||||
"Dialog.LogFiles": "Log Files"
|
"Dialog.LogFiles": "Log Files",
|
||||||
|
|
||||||
|
"Options.About" : "About",
|
||||||
|
"About.Github.Label": "GitHub",
|
||||||
|
"About.Github.Desc": "Source code, issues and project development.",
|
||||||
|
"About.Discord.Label": "Discord",
|
||||||
|
"About.Discord.Desc": "Join the community, get support and follow development.",
|
||||||
|
"About.GithubButton": "Contribute in GitHub!",
|
||||||
|
"About.DiscordButton": "Join our Discord!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,5 +125,13 @@
|
|||||||
"Dialog.PsExecutables": "Ejecutables de PS",
|
"Dialog.PsExecutables": "Ejecutables de PS",
|
||||||
"Dialog.SaveLogFile": "Selecciona dónde guardar el archivo de Logs",
|
"Dialog.SaveLogFile": "Selecciona dónde guardar el archivo de Logs",
|
||||||
"Dialog.PlainTextFiles": "Archivos en texto plano",
|
"Dialog.PlainTextFiles": "Archivos en texto plano",
|
||||||
"Dialog.LogFiles": "Archivos de Log"
|
"Dialog.LogFiles": "Archivos de Log",
|
||||||
|
|
||||||
|
"Options.About" : "Informacion",
|
||||||
|
"About.Github.Label": "GitHub",
|
||||||
|
"About.Github.Desc": "Código fuente, issues y desarrollo del proyecto.",
|
||||||
|
"About.Discord.Label": "Discord",
|
||||||
|
"About.Discord.Desc": "Únete a la comunidad, recibe soporte y sigue el desarrollo.",
|
||||||
|
"About.GithubButton": "Contribuye en GitHub!",
|
||||||
|
"About.DiscordButton": "Únete a nuestro Discord!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,73 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
<Border Classes="card">
|
||||||
|
<StackPanel Spacing="14">
|
||||||
|
<TextBlock x:Name="AboutSectionTitle"
|
||||||
|
Classes="sectionTitle"
|
||||||
|
Text="ABOUT" />
|
||||||
|
|
||||||
|
<!--Github-->
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Column="0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Image Source="avares://SharpEmu.GUI/Assets/github.png"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<StackPanel VerticalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Name="GithubLabel"
|
||||||
|
Text="GitHub"
|
||||||
|
FontSize="13" />
|
||||||
|
<TextBlock x:Name="GithubDesc"
|
||||||
|
Text="Source code, issues and project development."
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{StaticResource MutedBrush}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
x:Name="GithubButton"
|
||||||
|
Classes="ghost"
|
||||||
|
Content="Open"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!--Discord-->
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Column="0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Image Source="avares://SharpEmu.GUI/Assets/discord.png"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<StackPanel VerticalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Name="DiscordServerLabel"
|
||||||
|
Text="Discord"
|
||||||
|
FontSize="13" />
|
||||||
|
<TextBlock x:Name="DiscordServerDesc"
|
||||||
|
Text="Join the community, get support and follow development."
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{StaticResource MutedBrush}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
x:Name="DiscordButton"
|
||||||
|
Classes="ghost"
|
||||||
|
Content="Join"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -147,6 +147,25 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
_gamepadTimer.Tick += (_, _) => PollGamepad();
|
_gamepadTimer.Tick += (_, _) => PollGamepad();
|
||||||
_gamepadTimer.Start();
|
_gamepadTimer.Start();
|
||||||
|
|
||||||
|
|
||||||
|
GithubButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "https://github.com/par274/sharpemu",
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DiscordButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "https://discord.com/invite/6GejPEDqpc",
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -443,6 +462,14 @@ public partial class MainWindow : Window
|
|||||||
LaunchButton.Content = loc.Get("Launch.Launch");
|
LaunchButton.Content = loc.Get("Launch.Launch");
|
||||||
StopButton.Content = loc.Get("Launch.Stop");
|
StopButton.Content = loc.Get("Launch.Stop");
|
||||||
|
|
||||||
|
AboutSectionTitle.Text = loc.Get("Options.About");
|
||||||
|
GithubLabel.Text = loc.Get("About.Github.Label");
|
||||||
|
GithubDesc.Text = loc.Get("About.Github.Desc");
|
||||||
|
DiscordServerLabel.Text = loc.Get("About.Discord.Label");
|
||||||
|
DiscordServerDesc.Text = loc.Get("About.Discord.Desc");
|
||||||
|
GithubButton.Content = loc.Get("About.GithubButton");
|
||||||
|
DiscordButton.Content = loc.Get("About.DiscordButton");
|
||||||
|
|
||||||
UpdateEmptyStateTexts();
|
UpdateEmptyStateTexts();
|
||||||
UpdateSelectedGameTexts();
|
UpdateSelectedGameTexts();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="..\..\assets\images\SharpEmu.ico" Link="Assets/SharpEmu.ico" />
|
<AvaloniaResource Include="..\..\assets\images\SharpEmu.ico" Link="Assets/SharpEmu.ico" />
|
||||||
|
<AvaloniaResource Include="..\..\assets\images\github.png" Link="Assets/github.png" />
|
||||||
|
<AvaloniaResource Include="..\..\assets\images\discord.png" Link="Assets/discord.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
namespace SharpEmu.HLE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lets host-facing libraries (VideoOut, AudioOut) request cooperative guest
|
||||||
|
/// shutdown without taking a dependency on SharpEmu.Core.
|
||||||
|
/// </summary>
|
||||||
|
public static class HostSessionControl
|
||||||
|
{
|
||||||
|
private static Action<string>? _shutdownHandler;
|
||||||
|
|
||||||
|
public static void SetShutdownHandler(Action<string>? handler)
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _shutdownHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RequestShutdown(string reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Volatile.Read(ref _shutdownHandler)?.Invoke(reason);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][WARN] Host shutdown handler failed: {exception.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -213,6 +213,17 @@ public static class AudioOutExports
|
|||||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ShutdownAllPorts()
|
||||||
|
{
|
||||||
|
foreach (var handle in Ports.Keys)
|
||||||
|
{
|
||||||
|
if (Ports.TryRemove(handle, out var port))
|
||||||
|
{
|
||||||
|
port.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryGetFormat(
|
private static bool TryGetFormat(
|
||||||
int rawFormat,
|
int rawFormat,
|
||||||
out int channels,
|
out int channels,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Audio;
|
||||||
using SharpEmu.Libs.Kernel;
|
using SharpEmu.Libs.Kernel;
|
||||||
using SharpEmu.Logging;
|
using SharpEmu.Logging;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
@@ -52,6 +53,8 @@ public static class VideoOutExports
|
|||||||
private const int VblankWaitTimeoutMilliseconds = 100;
|
private const int VblankWaitTimeoutMilliseconds = 100;
|
||||||
private static Thread? _vblankPumpThread;
|
private static Thread? _vblankPumpThread;
|
||||||
private static int _vblankPumpStarted;
|
private static int _vblankPumpStarted;
|
||||||
|
private static volatile int _vblankPumpStopRequested;
|
||||||
|
private static volatile int _presentationWindowCloseNotified;
|
||||||
|
|
||||||
private static readonly object _vblankEdgeGate = new();
|
private static readonly object _vblankEdgeGate = new();
|
||||||
private static ulong _vblankEdgeSequence;
|
private static ulong _vblankEdgeSequence;
|
||||||
@@ -80,7 +83,7 @@ public static class VideoOutExports
|
|||||||
var intervalTicks = Math.Max(1L, (long)(Stopwatch.Frequency / VblankHz));
|
var intervalTicks = Math.Max(1L, (long)(Stopwatch.Frequency / VblankHz));
|
||||||
var nextEdge = Stopwatch.GetTimestamp() + intervalTicks;
|
var nextEdge = Stopwatch.GetTimestamp() + intervalTicks;
|
||||||
|
|
||||||
while (true)
|
while (_vblankPumpStopRequested == 0)
|
||||||
{
|
{
|
||||||
WaitUntilTimestamp(nextEdge);
|
WaitUntilTimestamp(nextEdge);
|
||||||
PumpVblanks();
|
PumpVblanks();
|
||||||
@@ -97,6 +100,59 @@ public static class VideoOutExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void NotifyPresentationWindowClosed()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _presentationWindowCloseNotified, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error.WriteLine("[LOADER][INFO] VideoOut presentation window closed");
|
||||||
|
RequestHostShutdown("videoout-window-closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NotifyHostInterrupt()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _presentationWindowCloseNotified, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error.WriteLine("[LOADER][INFO] Host interrupt requested");
|
||||||
|
RequestHostShutdown("host-interrupt");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RequestHostShutdown(string reason)
|
||||||
|
{
|
||||||
|
AudioOutExports.ShutdownAllPorts();
|
||||||
|
StopVblankPump();
|
||||||
|
HostSessionControl.RequestShutdown(reason);
|
||||||
|
ScheduleProcessExitIfGuestDoesNotStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ScheduleProcessExitIfGuestDoesNotStop()
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(static _ =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
Environment.Exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopVblankPump()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _vblankPumpStopRequested, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var thread = _vblankPumpThread;
|
||||||
|
if (thread is { IsAlive: true })
|
||||||
|
{
|
||||||
|
thread.Join(TimeSpan.FromSeconds(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void WaitUntilTimestamp(long deadlineTicks)
|
private static void WaitUntilTimestamp(long deadlineTicks)
|
||||||
{
|
{
|
||||||
var spinThresholdTicks = Stopwatch.Frequency * 2L / 1000L;
|
var spinThresholdTicks = Stopwatch.Frequency * 2L / 1000L;
|
||||||
|
|||||||
@@ -1154,7 +1154,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_window = Window.Create(options);
|
_window = Window.Create(options);
|
||||||
_window.Load += Initialize;
|
_window.Load += Initialize;
|
||||||
_window.Render += Render;
|
_window.Render += Render;
|
||||||
_window.Closing += DisposeVulkan;
|
_window.Closing += OnWindowClosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWindowClosing()
|
||||||
|
{
|
||||||
|
VideoOutExports.NotifyPresentationWindowClosed();
|
||||||
|
DisposeVulkan();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run() => _window.Run();
|
public void Run() => _window.Run();
|
||||||
|
|||||||
Reference in New Issue
Block a user