From fbacc494ed4508bb0bbae2c990814759271aae1d Mon Sep 17 00:00:00 2001 From: kuba Date: Tue, 29 Sep 2026 22:57:13 +0200 Subject: [PATCH] [AvPlayer] Start a player on its own when the title registers no event callback Reporting a loaded source as active before Start regressed Quake, which waits for its player to go inactive before starting its queued intro videos. A player left loaded but unstarted stays active forever. Only a title that registers an event callback can see the ready state and call sceAvPlayerStart itself, so a player whose title asks for autoStart or registers no callback now plays by itself once its source is ready, reaches the end of the stream and reports inactive. Titles that start playback themselves keep seeing a loaded source as active. --- src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs | 15 +++++++++++++-- .../AvPlayer/AvPlayerAbiTests.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs index 4e201bd..2c2ccb2 100644 --- a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs +++ b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs @@ -541,7 +541,9 @@ public static class AvPlayerExports { Handle = handle, IsGen5 = IsGen5Target(ctx.TargetGeneration), - AutoStart = TryReadByte(ctx, initDataAddress + autoStartOffset, out var autoStart) && autoStart != 0, + AutoStart = StartsAutomatically( + TryReadByte(ctx, initDataAddress + autoStartOffset, out var autoStart) && autoStart != 0, + TryReadUInt64(ctx, initDataAddress + 88, out var eventCallbackForStart) ? eventCallbackForStart : 0), GuestBuffers = new ulong[ReadOutputVideoFrameBufferCount( ctx, initDataAddress, @@ -615,7 +617,9 @@ public static class AvPlayerExports { Handle = handle, IsGen5 = IsGen5Target(ctx.TargetGeneration), - AutoStart = TryReadByte(ctx, initDataAddress + autoStartOffset, out var autoStart) && autoStart != 0, + AutoStart = StartsAutomatically( + TryReadByte(ctx, initDataAddress + autoStartOffset, out var autoStart) && autoStart != 0, + TryReadUInt64(ctx, initDataAddress + 96, out var eventCallbackForStart) ? eventCallbackForStart : 0), GuestBuffers = new ulong[ReadOutputVideoFrameBufferCount( ctx, initDataAddress, @@ -2620,6 +2624,13 @@ public static class AvPlayerExports internal static bool IsGen5Target(Generation generation) => (generation & Generation.Gen5) != 0; + // A player plays on its own once its source is ready when the title asks for autoStart or + // registers no event callback: without one nothing could observe the ready state and start + // it. Such a title never calls sceAvPlayerStart and waits for the player to go inactive at the + // end of the stream, so leaving it unstarted kept it active forever. + internal static bool StartsAutomatically(bool autoStartRequested, ulong eventCallback) => + autoStartRequested || eventCallback == 0; + internal static ulong GetAutoStartOffset(Generation generation, bool extended) => IsGen5Target(generation) ? extended ? 116UL : 108UL diff --git a/tests/SharpEmu.Libs.Tests/AvPlayer/AvPlayerAbiTests.cs b/tests/SharpEmu.Libs.Tests/AvPlayer/AvPlayerAbiTests.cs index f6dc735..0643e54 100644 --- a/tests/SharpEmu.Libs.Tests/AvPlayer/AvPlayerAbiTests.cs +++ b/tests/SharpEmu.Libs.Tests/AvPlayer/AvPlayerAbiTests.cs @@ -11,6 +11,18 @@ namespace SharpEmu.Libs.Tests.AvPlayer; public sealed class AvPlayerAbiTests { + [Theory] + [InlineData(true, 0x1000UL, true)] + [InlineData(false, 0UL, true)] + [InlineData(true, 0UL, true)] + [InlineData(false, 0x1000UL, false)] + public void APlayerWithoutAnEventCallbackStartsOnItsOwn(bool autoStartRequested, ulong eventCallback, bool expected) + { + // Only a title that registers an event callback can see the ready state and call Start; + // without one the player plays by itself, ends, and reports inactive. + Assert.Equal(expected, AvPlayerExports.StartsAutomatically(autoStartRequested, eventCallback)); + } + [Theory] [InlineData(Generation.Gen4, false, 108UL)] [InlineData(Generation.Gen5, false, 108UL)]