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=1 to 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_CLOSE flag, ensuring that if the GUI process terminates unexpectedly, the entire emulator process tree is automatically destroyed.
  • Process Creation: Uses CreateProcessW to 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 EmulatorProcess class in SharpEmu.GUI/EmulatorProcess.cs to 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_CLOSE and the SHARPEMU_DISABLE_MITIGATION_RELAUNCH environment variable to maintain pipe connectivity.
  • Log output flows through anonymous pipes (Windows) or redirected standard streams (non-Windows) to the OutputReceived event, then to AppendConsoleLine() in MainWindow.axaml.cs for colored console display.
  • A background watcher thread monitors process termination via WaitForSingleObject or process exit events, ensuring the GUI updates immediately when emulation ends.
  • Cleanup logic in OnClosed and Stop() 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →