[HLE] Fix AJM/ACM no-op stubs and accept Gen5 AudioOut2 mastering/batch calls (#807)

AjmModuleUnregister/AjmFinalize were pure no-ops (always returned success
without touching state); they now validate the context and actually remove
the codec registration / context entry, returning a real error when the
context is unknown. AjmModuleUnregister traces whether the codec was
actually registered, to help spot a title unregistering something it never
registered.

MaxCodecType was hardcoded to 25 based on known Sony codec ids, incorrectly
rejecting valid Gen5 codec types (e.g. 24). Registration is pure bookkeeping
(HashSet.Add); the only real constraint is that codecType must not overflow
the 32-bit instanceId it gets packed into, i.e. codecType < 2^18. Named the
instanceId bit-packing constants (InstanceIdSlotBits/InstanceIdSlotMask) so
the four call sites that used to hardcode 14/0x3FFF independently can't
drift out of sync, and MaxCodecType's formula is self-evident.

Accept sceAcmBatchInitialize/InitializeLite/Start/StartMultiple/Process as
successful no-ops -- the emulator runs no ACM DSP jobs, but Scream's workers
trap on int 0x41/0x42 asserts whenever a submission call reports failure.

Accept sceAudioOut2MasteringInit/Set3DLatency as no-ops -- the host mixer
has no mastering/object pipeline to tune, but returning failure makes
titles tear down their whole ACM context and abort audio arena bring-up.

Tested on Ghost of Yotei (PPSA26344): sceAudioOut2MasteringInit/Set3DLatency
unresolved-import warnings go from 2 to 0, unblocking 5 audio-related guest
threads that never spawned before (MovieDecoder, snd_stream_parsing_thread,
snd_stream_reader_thread, Psn, NCA::PumpThread), watchdog stall errors from
1 to 0. This alone gets the title past its Scream audio-init stall into
further boot, but it still hits an unrelated infinite retry loop in
sceVideoOutGetFlipStatus shortly after -- fixed by the separate
videoout-agc-hle-misc change; combined, the title renders and presents a
real GPU frame.

Regression-checked on Demon's Souls, Astro Bot, Outer Wilds, Cult of the
Lamb, Ghost of Tsushima, GTA, Minecraft, Quake: 0 calls to any export
touched by this change on any of them, byte-for-byte identical to
upstream/main.
This commit is contained in:
Foued Attar
2026-08-09 17:52:15 +02:00
committed by GitHub
parent 62c3852556
commit 4b8d45520e
4 changed files with 139 additions and 10 deletions
@@ -83,6 +83,30 @@ public sealed class AjmExportsTests : IDisposable
Assert.Equal(InvalidContext, RegisterCodec(contextId + 1, 1));
}
[Fact]
public void ModuleUnregister_RemovesRegisteredCodecAndRejectsUnknownContext()
{
var contextId = Initialize();
Assert.Equal(0, RegisterCodec(contextId, 1));
Assert.Equal(0, UnregisterCodec(contextId, 1));
// The codec is actually gone, not just a no-op stub: it's unusable
// for a new instance, and re-registering no longer hits
// CodecAlreadyRegistered.
Assert.Equal(CodecNotRegistered, CreateInstance(contextId, 1, 0x401, InstanceAddress));
Assert.Equal(0, RegisterCodec(contextId, 1));
Assert.Equal(InvalidContext, UnregisterCodec(contextId + 1, 1));
}
[Fact]
public void ModuleUnregister_UnknownCodecIsToleratedAsANoOp()
{
var contextId = Initialize();
Assert.Equal(0, UnregisterCodec(contextId, 1));
}
[Fact]
public void MemoryRegistration_TracksValidContextAndToleratesRepeatedUnregister()
{
@@ -353,6 +377,13 @@ public sealed class AjmExportsTests : IDisposable
return AjmExports.AjmModuleRegister(_ctx);
}
private int UnregisterCodec(uint contextId, uint codecType)
{
_ctx[CpuRegister.Rdi] = contextId;
_ctx[CpuRegister.Rsi] = codecType;
return AjmExports.AjmModuleUnregister(_ctx);
}
private int RegisterMemory(uint contextId, ulong address, ulong pages)
{
_ctx[CpuRegister.Rdi] = contextId;