[Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module (#824)

* [Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module

sceVideodec2 was a capability-only stub: the game's video pipeline
worked end-to-end but never produced a picture, so intro/cinematic
videos stayed black even though playback "completed" without errors
(confirmed on Ghost of Yotei's intro cinematic).

- Videodec2Decoder: owns an FFmpeg H.264 session per decoder handle,
  running decode and presentation pacing on their own threads (never
  the guest thread) so a whole clip isn't decoded faster than it can
  be displayed. Converts to BGRA and submits straight to
  VulkanVideoPresenter, bypassing guest memory the same way the
  existing Bink2 path does.
- Videodec2Exports: wires the real decoder into
  sceVideodec2CreateDecoder/Decode/Flush/Reset/DeleteDecoder, falling
  back to the original no-picture stub whenever FFmpeg is unavailable
  or a given decoder failed to open.
- VulkanVideoPresenter: decoded frames were being dropped under a
  single "latest wins" slot the render loop didn't always poll in
  time before the next frame overwrote it. Queues pending video
  presentations the same way guest-image flips already are.
- DirectExecutionBackend: the TLS load patcher's one-shot scan missed
  the main game module when the entry point resolves to a separate
  bootstrap allocation, and never re-scanned lazily-committed pages
  patched in afterward -- both left FS:[0] TLS loads unpatched,
  causing an early mutex-spin boot stall (reproduced on Demon's
  Souls: stuck at import #256). Now scans both the entry point's own
  allocation and the standard PS5/PS4 image base, and re-scans each
  newly committed executable range as it's touched.

* [GUI] Add a toggle for SHARPEMU_FORCE_SUBMIT_ORPHAN_PREAMBLES

This flag already existed as an AGC workaround (forces queued GPU
command-buffer preambles through when their target queue never picks
them up, unblocking titles stuck on a WAIT_REG_MEM that never
signals) but was only reachable by setting the environment variable
by hand. Expose it as a checkbox next to the other env toggles, in
both the global Options panel and the per-game settings panel, so it
can be turned on for a specific title without touching a shell.

* [GUI] Add remaining language translations for the new env toggle

The previous commit only added the new key to en.json/fr.json,
relying on Localization's runtime fallback to English -- but
LocalizationTests.EmbeddedLanguages_ContainEveryEnglishOptionsKey
requires every Options.* key to exist in every embedded language
file, which broke CI on all three build jobs. Fills in the
remaining 13 languages.
This commit is contained in:
Foued Attar
2026-08-17 23:00:01 +02:00
committed by GitHub
parent 1660111189
commit 7521295ee1
23 changed files with 921 additions and 4 deletions
@@ -1453,6 +1453,7 @@ public sealed partial class DirectExecutionBackend
}
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
RescanTlsPatternsIfExecutable(committedBase, committedSize + 4096uL, commitProtect);
if (traceLazyCommit)
{
Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
@@ -1512,6 +1513,7 @@ public sealed partial class DirectExecutionBackend
}
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
RescanTlsPatternsIfExecutable(committedBase, committedSize + 4096uL, commitProtect);
if (traceLazyCommit)
{
Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
@@ -1613,6 +1615,18 @@ public sealed partial class DirectExecutionBackend
}
}
// Re-scans a just-committed window for FS:[0] TLS loads; skips non-executable commits.
private unsafe void RescanTlsPatternsIfExecutable(ulong committedBase, ulong committedSize, uint commitProtect)
{
const uint executableProtectionMask = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
if ((commitProtect & executableProtectionMask) == 0 || committedSize == 0)
{
return;
}
PatchTlsPatternsInRange(committedBase, committedBase + committedSize, announce: false);
}
private static bool ShouldTraceLazyCommit(int traceIndex)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_LAZY_COMMIT"), "1", StringComparison.Ordinal))
@@ -3145,8 +3145,33 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
// Large Gen5 executables can keep valid code well past the first 32 MiB.
// Astro Bot, for example, has an FS:[0] TLS load near +0x70A0000.
const ulong MaxScanBytes = 134217728uL;
ulong num = _entryPoint;
ulong num2 = num + MaxScanBytes;
// _entryPoint can be a separate bootstrap allocation, not the main module —
// always also scan the standard PS5/PS4 image base.
const ulong Ps5MainImageBase = 0x0000000800000000UL;
const ulong Ps4MainImageBase = 0x0000000000400000UL;
ulong scanStart = _entryPoint;
if (VirtualQuery((void*)_entryPoint, out var entryRegion, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 &&
entryRegion.AllocationBase != 0 &&
entryRegion.AllocationBase <= _entryPoint)
{
scanStart = entryRegion.AllocationBase;
}
PatchTlsPatternsInRange(scanStart, scanStart + MaxScanBytes, announce: true);
// Scan both windows unconditionally; overlap is safe, patched bytes just stop matching.
var mainImageBase = _entryPoint >= Ps5MainImageBase ? Ps5MainImageBase : Ps4MainImageBase;
if (mainImageBase < scanStart)
{
PatchTlsPatternsInRange(mainImageBase, mainImageBase + MaxScanBytes, announce: false);
}
}
private unsafe void PatchTlsPatternsInRange(ulong rangeStart, ulong rangeEnd, bool announce)
{
ulong num = rangeStart;
ulong num2 = rangeEnd;
int num3 = 0;
int num4 = 0;
int num9 = 0;
@@ -3195,7 +3220,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
}
num = num6 > num ? num6 : num + 4096uL;
}
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS loads, {num9} TLS stores, {num4} stack-canary accesses, {sse4aPatchCount} SSE4a EXTRQ blends");
if (announce || num3 + num4 + num9 + sse4aPatchCount > 0)
{
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS loads, {num9} TLS stores, {num4} stack-canary accesses, {sse4aPatchCount} SSE4a EXTRQ blends" +
(announce ? string.Empty : $" (lazy-commit rescan 0x{rangeStart:X16}-0x{rangeEnd:X16})"));
}
}
private unsafe bool TryPatchSse4aExtrqBlend(nint address, byte* source)