[AudioOut2] Write a single size_t from sceAudioOut2ContextQueryMemory

The export returns the context memory size through one size_t. For heap
output it also wrote a second qword (an alignment) after it, and titles keep
other locals there: Octopath Traveler II's allocator reads the dword at +12 of
that block as a divisor, so it faulted with #DE during boot. Write only the
size, wherever the output lives.
This commit is contained in:
kuba 2026-09-30 00:11:14 +02:00
parent 4d65ff6d51
commit 066df8e754
2 changed files with 46 additions and 24 deletions

View File

@ -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<byte> 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<byte> 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<byte> 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);
}

View File

@ -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<byte> param = stackalloc byte[0x40];
param.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(param[0x0C..], 8);
Assert.True(memory.TryWrite(ParamAddress, param));
Span<byte> 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<byte> 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));
}
}