How SharpEmu Integrates with Its GUI for Game Launching and Logging
SharpEmu separates its user interface from the emulator core by using the EmulatorProcess class to spawn the CLI as a managed child process, capturing stdout and stderr through anonymous pipes, and streaming formatted log output back to the GUI console in real time.
The open-source PlayStation emulator SharpEmu (par274/sharpemu) implements a clean separation between its Avalonia-based GUI and the emulation engine. This article examines how SharpEmu GUI integration works for game launching and logging, detailing the specific classes and system calls used to manage process lifecycles and capture console output across Windows and Unix-like platforms.
Architecture Overview
SharpEmu’s GUI layer resides in the SharpEmu.GUI namespace and relies on two primary components to handle game execution. The MainWindow class defined in SharpEmu.GUI/MainWindow.axaml.cs orchestrates user interactions and console display, while SharpEmu.GUI/EmulatorProcess.cs contains the low-level process management logic. This design allows the GUI to remain responsive while the emulator runs as a separate process, preventing UI freezes during intensive emulation tasks.
Game Launch Sequence
When a user clicks the Play button, the GUI initiates a structured sequence to start the emulator core with the correct parameters.
Initiating the Launch
The process begins in MainWindow.axaml.cs where the launcher logic collects the selected ISO path and optional flags. It constructs an IReadOnlyList<string> of arguments and instantiates the EmulatorProcess class to handle the actual execution. If the emulator binary is missing, the GUI immediately logs an error using AppendConsoleLine() with the ErrorLineBrush styling:
AppendConsoleLine(Localization.Instance.Get("Launch.ExeNotFound"), ErrorLineBrush);
Process Creation
The EmulatorProcess.Start() method accepts the executable path, argument list, and working directory. It then branches into platform-specific implementations to ensure reliable process control and output capture.
Cross-Platform Process Management
SharpEmu handles process creation differently on Windows versus non-Windows platforms to leverage OS-specific features while maintaining functional parity.
Windows Implementation
On Windows, the StartWindows() method performs several critical setup steps:
- Environment Configuration: Sets
SHARPEMU_DISABLE_MITIGATION_RELAUNCH=1to prevent the CLI from attempting to relaunch with security mitigations that would break the pipe connection. - Pipe Creation: Establishes anonymous pipes for stdout and stderr to enable non-blocking reads.
- Job Object Assignment: Creates a Windows job object with the
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEflag, ensuring that if the GUI process terminates unexpectedly, the entire emulator process tree is automatically destroyed. - Process Creation: Uses
CreateProcessWto spawn the child with the prepared pipes and environment.
Non-Windows Fallback
For Linux and macOS, the StartFallback() method utilizes .NET’s ProcessStartInfo with RedirectStandardOutput and RedirectStandardError set to true. It initiates asynchronous read operations on both streams to capture output without blocking the main thread.
Real-Time Log Integration
Once the process starts, the GUI captures, formats, and displays log messages through an event-driven pipeline.
Output Event Handling
Both platform implementations hook into the OutputReceived event. When the EmulatorProcess receives a line from the child process, it invokes this event with the text content and an error flag. The MainWindow subscribes to this event and routes messages to the console panel:
private void LaunchGame(string exePath, string[] args, string workingDir)
{
var proc = new EmulatorProcess();
proc.OutputReceived += (line, isError) =>
AppendConsoleLine(line, isError ? ErrorLineBrush : InfoLineBrush);
proc.Exited += exitCode =>
AppendConsoleLine($"Emulator exited with code {exitCode}", InfoLineBrush);
proc.Start(exePath, args, workingDir);
}
Console Display and Formatting
The AppendConsoleLine() method in MainWindow.axaml.cs creates a TextBlock with the appropriate foreground brush and appends it to the console panel. It then auto-scrolls to the newest entry:
private void AppendConsoleLine(string text, IBrush brush)
{
var line = new TextBlock { Text = text, Foreground = brush };
ConsolePanel.Children.Add(line);
ConsoleScrollViewer.ScrollToEnd();
}
Process Lifecycle and Cleanup
Proper termination handling ensures that the emulator does not outlive the GUI or consume resources after a session ends.
Exit Monitoring
The EmulatorProcess class spawns a dedicated watcher thread via StartExitWatcherThread(). On Windows, this thread blocks on WaitForSingleObject for the child process handle. When the emulator exits, OnExited(exitCode) fires the Exited event, allowing the GUI to display a final status line and re-enable the Play button:
AppendConsoleLine(Localization.Instance.Format("Launch.ProcessExited", exitCode, meaning), brush);
Graceful Shutdown
When the user closes the application window or clicks Stop, the GUI calls EmulatorProcess.Stop(). On Windows, this terminates the job object, killing the entire process tree instantly. On other platforms, it disposes of the Process object and kills the process if necessary. The MainWindow overrides OnClosed to ensure cleanup always occurs:
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_emulatorProcess?.Stop();
}
Summary
- SharpEmu uses a dedicated
EmulatorProcessclass inSharpEmu.GUI/EmulatorProcess.csto isolate the CLI emulator from the GUI, preventing UI blocking during emulation. - Windows-specific features include job objects with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEand theSHARPEMU_DISABLE_MITIGATION_RELAUNCHenvironment variable to maintain pipe connectivity. - Log output flows through anonymous pipes (Windows) or redirected standard streams (non-Windows) to the
OutputReceivedevent, then toAppendConsoleLine()inMainWindow.axaml.csfor colored console display. - A background watcher thread monitors process termination via
WaitForSingleObjector process exit events, ensuring the GUI updates immediately when emulation ends. - Cleanup logic in
OnClosedandStop()guarantees that child processes cannot outlive the GUI application.
Frequently Asked Questions
How does SharpEmu prevent zombie processes when the GUI closes?
SharpEmu prevents zombie processes by assigning the emulator to a Windows job object with the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag. When the GUI process terminates, Windows automatically terminates the entire job object tree. On non-Windows platforms, the Stop() method explicitly kills the process during window closure.
Why does SharpEmu disable mitigation relaunch on Windows?
The GUI sets the environment variable SHARPEMU_DISABLE_MITIGATION_RELAUNCH=1 before spawning the process to prevent the CLI from relaunching itself with CET/CFG mitigations. A relaunch would detach the process from the GUI's pipes, breaking real-time log capture and preventing proper process management via the job object.
How does the GUI distinguish between error and info log lines?
The OutputReceived event includes a boolean flag indicating whether the line originated from stderr. The MainWindow uses this flag to select the appropriate brush (ErrorLineBrush or InfoLineBrush) when calling AppendConsoleLine(), color-coding errors in red while displaying standard output in neutral colors.
Can SharpEmu run on Linux or macOS?
Yes, SharpEmu supports non-Windows platforms through the StartFallback() method, which uses standard .NET Process APIs with RedirectStandardOutput and RedirectStandardError. While this path lacks Windows-specific job object features, it maintains equivalent functionality for log capture and process termination.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →