From 017969fba3f1d5ea9d6f3a2b640d67113a187812 Mon Sep 17 00:00:00 2001
From: ParantezTech <12572227+par274@users.noreply.github.com>
Date: Wed, 7 Oct 2026 02:43:10 +0300
Subject: [PATCH] [CommonDialog] Add the web browser dialog lifecycle
Co-authored-by: Sekai <140012992+igettingit@users.noreply.github.com>
---
.../CommonDialog/WebBrowserDialogExports.cs | 171 ++++++++++++++++++
src/SharpEmu.Libs/Stubs/GameServiceStubs.cs | 8 -
2 files changed, 171 insertions(+), 8 deletions(-)
create mode 100644 src/SharpEmu.Libs/CommonDialog/WebBrowserDialogExports.cs
diff --git a/src/SharpEmu.Libs/CommonDialog/WebBrowserDialogExports.cs b/src/SharpEmu.Libs/CommonDialog/WebBrowserDialogExports.cs
new file mode 100644
index 0000000..78340d0
--- /dev/null
+++ b/src/SharpEmu.Libs/CommonDialog/WebBrowserDialogExports.cs
@@ -0,0 +1,171 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.Threading;
+using SharpEmu.HLE;
+
+namespace SharpEmu.Libs.CommonDialog;
+
+///
+/// Headless lifecycle for the system web-browser dialog. SharpEmu does not yet
+/// provide a host browser, so an opened dialog completes on its first status
+/// poll instead of leaving games in an infinite RUNNING loop.
+///
+public static class WebBrowserDialogExports
+{
+ internal const int StatusNone = 0;
+ internal const int StatusInitialized = 1;
+ internal const int StatusRunning = 2;
+ internal const int StatusFinished = 3;
+
+ private const int ErrorOk = 0;
+ private const int ErrorNotInitialized = unchecked((int)0x80B80003);
+ private const int ErrorNotFinished = unchecked((int)0x80B80005);
+ private const int ErrorNotRunning = unchecked((int)0x80B8000B);
+ private const int ErrorArgNull = unchecked((int)0x80B8000D);
+
+ private static int _status;
+
+ [SysAbiExport(
+ Nid = "jqb7HntFQFc",
+ ExportName = "sceWebBrowserDialogInitialize",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogInitialize(CpuContext ctx)
+ {
+ Interlocked.CompareExchange(ref _status, StatusInitialized, StatusNone);
+ return ctx.SetReturn(ErrorOk);
+ }
+
+ [SysAbiExport(
+ Nid = "FraP7debcdg",
+ ExportName = "sceWebBrowserDialogOpen",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogOpen(CpuContext ctx) => Open(ctx);
+
+ [SysAbiExport(
+ Nid = "O7dIZQrwVFY",
+ ExportName = "sceWebBrowserDialogOpenForPredeterminedContent",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogOpenForPredeterminedContent(CpuContext ctx) => Open(ctx);
+
+ private static int Open(CpuContext ctx)
+ {
+ if (ctx[CpuRegister.Rdi] == 0)
+ {
+ return ctx.SetReturn(ErrorArgNull);
+ }
+
+ if (Volatile.Read(ref _status) == StatusNone)
+ {
+ return ctx.SetReturn(ErrorNotInitialized);
+ }
+
+ Interlocked.Exchange(ref _status, StatusRunning);
+ return ctx.SetReturn(ErrorOk);
+ }
+
+ [SysAbiExport(
+ Nid = "CFTG6a8TjOU",
+ ExportName = "sceWebBrowserDialogGetStatus",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogGetStatus(CpuContext ctx) => ctx.SetReturn(PollStatus());
+
+ [SysAbiExport(
+ Nid = "h1dR-t5ISgg",
+ ExportName = "sceWebBrowserDialogUpdateStatus",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogUpdateStatus(CpuContext ctx) => ctx.SetReturn(PollStatus());
+
+ private static int PollStatus()
+ {
+ Interlocked.CompareExchange(ref _status, StatusFinished, StatusRunning);
+ return Volatile.Read(ref _status);
+ }
+
+ [SysAbiExport(
+ Nid = "PSK+Eik919Q",
+ ExportName = "sceWebBrowserDialogClose",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogClose(CpuContext ctx)
+ {
+ if (Interlocked.CompareExchange(ref _status, StatusFinished, StatusRunning) != StatusRunning)
+ {
+ return ctx.SetReturn(ErrorNotRunning);
+ }
+
+ return ctx.SetReturn(ErrorOk);
+ }
+
+ [SysAbiExport(
+ Nid = "vCaW0fgVQmc",
+ ExportName = "sceWebBrowserDialogGetResult",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogGetResult(CpuContext ctx)
+ {
+ if (ctx[CpuRegister.Rdi] == 0)
+ {
+ return ctx.SetReturn(ErrorArgNull);
+ }
+
+ if (Volatile.Read(ref _status) != StatusFinished)
+ {
+ return ctx.SetReturn(ErrorNotFinished);
+ }
+
+ Span result = stackalloc byte[sizeof(int)];
+ result.Clear();
+ if (!ctx.Memory.TryWrite(ctx[CpuRegister.Rdi], result))
+ {
+ return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
+ }
+
+ return ctx.SetReturn(ErrorOk);
+ }
+
+ [SysAbiExport(
+ Nid = "Wit4LjeoeX4",
+ ExportName = "sceWebBrowserDialogGetEvent",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogGetEvent(CpuContext ctx) => ctx.SetReturn(ErrorOk);
+
+ [SysAbiExport(
+ Nid = "uYELOMVnmNQ",
+ ExportName = "sceWebBrowserDialogNavigate",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogNavigate(CpuContext ctx) => ctx.SetReturn(ErrorOk);
+
+ [SysAbiExport(
+ Nid = "RLhKBOoNyXY",
+ ExportName = "sceWebBrowserDialogSetZoom",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogSetZoom(CpuContext ctx) => ctx.SetReturn(ErrorOk);
+
+ [SysAbiExport(
+ Nid = "ocHtyBwHfys",
+ ExportName = "sceWebBrowserDialogTerminate",
+ Target = Generation.Gen4 | Generation.Gen5,
+ LibraryName = "libSceWebBrowserDialog")]
+ public static int WebBrowserDialogTerminate(CpuContext ctx)
+ {
+ if (Interlocked.Exchange(ref _status, StatusNone) == StatusNone)
+ {
+ return ctx.SetReturn(ErrorNotInitialized);
+ }
+
+ return ctx.SetReturn(ErrorOk);
+ }
+
+ internal static int StatusForTests => Volatile.Read(ref _status);
+
+ internal static void ResetForTests() => Interlocked.Exchange(ref _status, StatusNone);
+}
diff --git a/src/SharpEmu.Libs/Stubs/GameServiceStubs.cs b/src/SharpEmu.Libs/Stubs/GameServiceStubs.cs
index feae3ac..0d57f65 100644
--- a/src/SharpEmu.Libs/Stubs/GameServiceStubs.cs
+++ b/src/SharpEmu.Libs/Stubs/GameServiceStubs.cs
@@ -64,14 +64,6 @@ public static class GameServiceStubs
Target = Generation.Gen5, LibraryName = "libSceNpGameIntent")]
public static int NpGameIntentTerminate(CpuContext ctx) => Ok(ctx);
- [SysAbiExport(Nid = "jqb7HntFQFc", ExportName = "sceWebBrowserDialogInitialize",
- Target = Generation.Gen5, LibraryName = "libSceWebBrowserDialog")]
- public static int WebBrowserDialogInitialize(CpuContext ctx) => Ok(ctx);
-
- [SysAbiExport(Nid = "ocHtyBwHfys", ExportName = "sceWebBrowserDialogTerminate",
- Target = Generation.Gen5, LibraryName = "libSceWebBrowserDialog")]
- public static int WebBrowserDialogTerminate(CpuContext ctx) => Ok(ctx);
-
[SysAbiExport(Nid = "mlYGfmqE3fQ", ExportName = "sceSigninDialogInitialize",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
public static int SigninDialogInitialize(CpuContext ctx) => Ok(ctx);