diff --git a/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs b/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs index 9d14d19..0ca3653 100644 --- a/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs +++ b/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs @@ -247,31 +247,13 @@ public static class AudioOut2Exports contextMemorySize = checked(0x10000UL + (queueDepth * 0x590UL)); } - // Heap: {size, alignment} (16 bytes), matching sceAudioPropagationSystemQueryMemory. - // Stack: SIZE ONLY as a full ulong (8 bytes). Writing alignment at +8 is how - // [rbp-0x30] became 0x100 on GTA V Enhanced. Do NOT shrink this to uint32 — - // Main reads the out as a 64-bit size; a 4-byte write leaves a garbage high - // dword (observed 0x7<<32|0x4000) and the allocator aborts with int 0x41. - if (IsGuestStackAddress(memoryInfoAddress)) - { - Span sizeOnly = stackalloc byte[sizeof(ulong)]; - BinaryPrimitives.WriteUInt64LittleEndian(sizeOnly, contextMemorySize); - Console.Error.WriteLine( - $"[LOADER][TRACE] audio_out2.context-query-memory stack-size-only " + - $"out=0x{memoryInfoAddress:X} size=0x{contextMemorySize:X}"); - return ctx.Memory.TryWrite(memoryInfoAddress, sizeOnly) - ? SetReturn(ctx, 0) - : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); - } - - Span memoryInfo = stackalloc byte[0x10]; - memoryInfo.Clear(); - BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x00..], contextMemorySize); - BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x08..], AudioOut2ContextMemoryAlignment); + // The output is a single size_t wherever it lives. Titles keep other locals right after it + // (Octopath Traveler II divides by the dword at +12), so nothing else may be written. + Span memorySize = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian(memorySize, contextMemorySize); Console.Error.WriteLine( - $"[LOADER][TRACE] audio_out2.context-query-memory out=0x{memoryInfoAddress:X} " + - $"size=0x{contextMemorySize:X} align=0x{AudioOut2ContextMemoryAlignment:X}"); - return ctx.Memory.TryWrite(memoryInfoAddress, memoryInfo) + $"[LOADER][TRACE] audio_out2.context-query-memory out=0x{memoryInfoAddress:X} size=0x{contextMemorySize:X}"); + return ctx.Memory.TryWrite(memoryInfoAddress, memorySize) ? SetReturn(ctx, 0) : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } diff --git a/tests/SharpEmu.Libs.Tests/Audio/AudioOut2ContextQueryMemoryTests.cs b/tests/SharpEmu.Libs.Tests/Audio/AudioOut2ContextQueryMemoryTests.cs new file mode 100644 index 0000000..c181417 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Audio/AudioOut2ContextQueryMemoryTests.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using SharpEmu.HLE; +using SharpEmu.Libs.Audio; +using Xunit; + +namespace SharpEmu.Libs.Tests.Audio; + +public sealed class AudioOut2ContextQueryMemoryTests +{ + private const ulong MemoryBase = 0x1_0000_0000; + private const ulong ParamAddress = MemoryBase + 0x100; + private const ulong SizeAddress = MemoryBase + 0x200; + + [Fact] + public void ContextQueryMemory_WritesOneSizeAndNothingAfterIt() + { + var memory = new FakeCpuMemory(MemoryBase, 0x1000); + var ctx = new CpuContext(memory, Generation.Gen5); + Span param = stackalloc byte[0x40]; + param.Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(param[0x0C..], 8); + Assert.True(memory.TryWrite(ParamAddress, param)); + Span paint = stackalloc byte[0x20]; + paint.Fill(0xAB); + Assert.True(memory.TryWrite(SizeAddress, paint)); + + ctx[CpuRegister.Rdi] = ParamAddress; + ctx[CpuRegister.Rsi] = SizeAddress; + + Assert.Equal(0, AudioOut2Exports.AudioOut2ContextQueryMemory(ctx)); + Span written = stackalloc byte[0x20]; + Assert.True(memory.TryRead(SizeAddress, written)); + Assert.Equal(0x10000UL + (8 * 0x590UL), BinaryPrimitives.ReadUInt64LittleEndian(written)); + // The caller's locals after the size_t stay untouched. + Assert.True(written[8..].ToArray().All(static value => value == 0xAB)); + } +}