From eb252af7b37aa4f219645577ea0233bfa2156688 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 23 Jul 2026 05:32:49 -0400 Subject: [PATCH] unstub: preserve notice screen skip flag (#559) * fix(system-service): preserve notice screen skip flag * test(bink): avoid frame timing flake on CI --- .../SystemService/SystemServiceExports.cs | 19 +++++++++++---- .../Bink/BinkFramePlaybackTests.cs | 4 +++- .../SystemServiceExportsTests.cs | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs b/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs index 4bdf9fc7..f7c8f29d 100644 --- a/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs +++ b/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs @@ -18,6 +18,7 @@ public static class SystemServiceExports private const int TitleIdFieldSize = 0x10; private static string? _mainAppTitleId; + private static int _noticeScreenSkipFlag; public static void ConfigureApplicationInfo(string? titleId) { @@ -37,9 +38,11 @@ public static class SystemServiceExports return ctx.SetReturn(OrbisSystemServiceErrorParameter); } - // No system notice screen to skip in the emulator; report "do not skip". + // Keep the flag state even though the emulator does not display the + // system notice screen. Titles use this service as a normal preference + // store and expect a later get to observe the value they set. Span flagBytes = stackalloc byte[1]; - flagBytes[0] = 0; + flagBytes[0] = unchecked((byte)Volatile.Read(ref _noticeScreenSkipFlag)); return ctx.Memory.TryWrite(flagAddress, flagBytes) ? ctx.SetReturn(0) : ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); @@ -61,8 +64,13 @@ public static class SystemServiceExports ExportName = "sceSystemServiceSetNoticeScreenSkipFlag", Target = Generation.Gen5, LibraryName = "libSceSystemService")] - public static int SystemServiceSetNoticeScreenSkipFlag(CpuContext ctx) => - ctx.SetReturn(0); + public static int SystemServiceSetNoticeScreenSkipFlag(CpuContext ctx) + { + // The native API takes the flag value in the first argument. Treat any + // non-zero value as true, matching the bool-like PS5 ABI. + Volatile.Write(ref _noticeScreenSkipFlag, ctx[CpuRegister.Rdi] != 0 ? 1 : 0); + return ctx.SetReturn(0); + } [SysAbiExport( Nid = "4veE0XiIugA", @@ -231,4 +239,7 @@ public static class SystemServiceExports Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSystemService")] public static int SystemServiceReportAbnormalTermination(CpuContext ctx) => ctx.SetReturn(0); + + internal static void ResetForTests() => + Volatile.Write(ref _noticeScreenSkipFlag, 0); } diff --git a/tests/SharpEmu.Libs.Tests/Bink/BinkFramePlaybackTests.cs b/tests/SharpEmu.Libs.Tests/Bink/BinkFramePlaybackTests.cs index 271a5c87..20aefbea 100644 --- a/tests/SharpEmu.Libs.Tests/Bink/BinkFramePlaybackTests.cs +++ b/tests/SharpEmu.Libs.Tests/Bink/BinkFramePlaybackTests.cs @@ -83,7 +83,9 @@ public sealed class BinkFramePlaybackTests public uint Height => 1; - public uint FramesPerSecondNumerator => 20; + // Keep frame boundaries far enough apart that a loaded CI runner cannot + // skip an expected frame between polling iterations. + public uint FramesPerSecondNumerator => 2; public uint FramesPerSecondDenominator => 1; diff --git a/tests/SharpEmu.Libs.Tests/SystemService/SystemServiceExportsTests.cs b/tests/SharpEmu.Libs.Tests/SystemService/SystemServiceExportsTests.cs index ba1e1dfa..9cb10d51 100644 --- a/tests/SharpEmu.Libs.Tests/SystemService/SystemServiceExportsTests.cs +++ b/tests/SharpEmu.Libs.Tests/SystemService/SystemServiceExportsTests.cs @@ -11,6 +11,11 @@ public sealed class SystemServiceExportsTests { private const ulong MemoryBase = 0x1_0000_0000; + public SystemServiceExportsTests() + { + SystemServiceExports.ResetForTests(); + } + [Fact] public void GetNoticeScreenSkipFlagWritesOneByteAtMemoryBoundary() { @@ -26,4 +31,23 @@ public sealed class SystemServiceExportsTests Assert.True(memory.TryRead(MemoryBase, flag)); Assert.Equal(0, flag[0]); } + + [Fact] + public void SetNoticeScreenSkipFlagRoundTripsThroughGetter() + { + var memory = new FakeCpuMemory(MemoryBase, 2); + var context = new CpuContext(memory, Generation.Gen5) + { + [CpuRegister.Rdi] = 1, + }; + + Assert.Equal(0, SystemServiceExports.SystemServiceSetNoticeScreenSkipFlag(context)); + + context[CpuRegister.Rdi] = MemoryBase; + Assert.Equal(0, SystemServiceExports.SystemServiceGetNoticeScreenSkipFlag(context)); + + Span flag = stackalloc byte[1]; + Assert.True(memory.TryRead(MemoryBase, flag)); + Assert.Equal(1, flag[0]); + } }