From 40068f68b6e96b52fc0e8d66a24bb74a81eca310 Mon Sep 17 00:00:00 2001 From: Berk <12572227+par274@users.noreply.github.com> Date: Sun, 4 Oct 2026 05:48:01 +0300 Subject: [PATCH] =?UTF-8?q?Revert=20"[DeS]=20Fixes=20for=20page=20faults?= =?UTF-8?q?=20and=20stack=20bounds=20issues=20in=20the=20emulator=20a?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ec0198f2d059c0cc5da39e1bc574730f4beb9f21. --- .../DirectExecutionBackend.StackBounds.cs | 31 +--- src/SharpEmu.HLE/GpuMemory/GuestGpuMemory.cs | 5 - src/SharpEmu.Libs/Audio/AjmExports.cs | 15 +- src/SharpEmu.Libs/Audio/Atrac9DecodeState.cs | 63 +------ src/SharpEmu.Libs/Audio/AudioOut2Exports.cs | 126 ++------------ .../Gpu/Buffers/GuestBufferCache.cs | 19 +-- .../VulkanVideoPresenter.Descriptors.cs | 27 +-- .../Resources/ResourceMaterializer.cs | 27 +-- .../Audio/Atrac9GaplessTests.cs | 113 ------------- .../Audio/AudioOut2MixTests.cs | 158 ------------------ .../Cpu/GuestStackBoundsTests.cs | 44 ----- 11 files changed, 36 insertions(+), 592 deletions(-) delete mode 100644 tests/SharpEmu.Libs.Tests/Audio/Atrac9GaplessTests.cs delete mode 100644 tests/SharpEmu.Libs.Tests/Audio/AudioOut2MixTests.cs delete mode 100644 tests/SharpEmu.Libs.Tests/Cpu/GuestStackBoundsTests.cs diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.StackBounds.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.StackBounds.cs index f5c713c..b6ec6e9 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.StackBounds.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.StackBounds.cs @@ -9,23 +9,10 @@ public sealed partial class DirectExecutionBackend { private const int HostStackStateBytes = 3 * sizeof(ulong); - internal static unsafe bool TryGetGuestStackBounds(ulong stackPointer, out ulong bottom, out ulong top) + private static unsafe bool TryGetGuestStackBounds(ulong stackPointer, out ulong bottom, out ulong top) { - if (OperatingSystem.IsWindows() && - QueryVirtualMemoryInformation(-1, (void*)stackPointer, 0, out var allocation, - (nuint)sizeof(StackMemoryRegionInformation), null) != 0 && - ((allocation.Flags & 1) == 0 || allocation.CommitSize == allocation.RegionSize) && - allocation.RegionSize != 0 && allocation.RegionSize <= ulong.MaxValue - allocation.AllocationBase && - stackPointer >= allocation.AllocationBase && stackPointer - allocation.AllocationBase < allocation.RegionSize) - { - bottom = allocation.AllocationBase; - top = bottom + allocation.RegionSize; - return true; - } - bottom = top = 0; - var queried = VirtualQuery((void*)stackPointer, out var region, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)); - if (queried == 0 || + if (VirtualQuery((void*)stackPointer, out var region, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || region.State != 0x1000 || region.RegionSize == 0 || region.RegionSize > ulong.MaxValue - region.BaseAddress) { return false; @@ -189,18 +176,4 @@ public sealed partial class DirectExecutionBackend [DllImport("kernel32.dll", ExactSpelling = true)] private static extern void GetCurrentThreadStackLimits(out ulong lowLimit, out ulong highLimit); - - [StructLayout(LayoutKind.Sequential)] - private struct StackMemoryRegionInformation - { - public ulong AllocationBase; - public uint AllocationProtect; - public uint Flags; - public ulong RegionSize; - public ulong CommitSize; - } - - [DllImport("api-ms-win-core-memory-l1-1-4.dll", ExactSpelling = true)] - private static extern unsafe int QueryVirtualMemoryInformation(nint process, void* address, int informationClass, - out StackMemoryRegionInformation information, nuint informationSize, nuint* returnSize); } diff --git a/src/SharpEmu.HLE/GpuMemory/GuestGpuMemory.cs b/src/SharpEmu.HLE/GpuMemory/GuestGpuMemory.cs index a99f4b5..4e55ca6 100644 --- a/src/SharpEmu.HLE/GpuMemory/GuestGpuMemory.cs +++ b/src/SharpEmu.HLE/GpuMemory/GuestGpuMemory.cs @@ -30,9 +30,6 @@ public sealed class GuestGpuMemory : IDisposable private readonly PageGuard _pages; private readonly ReaderWriterLockSlim _spansLock = new(); private readonly SpanSet _spans = new(); - private long _spanVersion; - - public long SpanVersion => Interlocked.Read(ref _spanVersion); private sealed record GpuAttachment(IGpuQueueRelay? Gpu, IGpuTickScheduler? Scheduler); private readonly object _attachGate = new(); @@ -207,7 +204,6 @@ public sealed class GuestGpuMemory : IDisposable try { _spans.Add(address, size); - Interlocked.Increment(ref _spanVersion); } finally { @@ -280,7 +276,6 @@ public sealed class GuestGpuMemory : IDisposable try { _spans.Remove(address, size); - Interlocked.Increment(ref _spanVersion); } finally { diff --git a/src/SharpEmu.Libs/Audio/AjmExports.cs b/src/SharpEmu.Libs/Audio/AjmExports.cs index a9e09fb..b7ad235 100644 --- a/src/SharpEmu.Libs/Audio/AjmExports.cs +++ b/src/SharpEmu.Libs/Audio/AjmExports.cs @@ -590,16 +590,7 @@ public static class AjmExports return ctx.SetReturn(OrbisAjmErrorJobCreation); } - var status = TryGetInstance(instanceId, out var instance) ? 0 : Atrac9DecodeState.ResultInvalidParameter; - Span gapless = stackalloc byte[AjmSidebandGaplessDecodeBytes]; - if (status == 0 && gaplessAddress != 0 && ctx.Memory.TryRead(gaplessAddress, gapless)) - { - instance!.Atrac9?.SetGapless( - BinaryPrimitives.ReadUInt32LittleEndian(gapless), - BinaryPrimitives.ReadUInt16LittleEndian(gapless[4..]), - reset != 0); - } - + var status = TryGetInstance(instanceId, out _) ? 0 : Atrac9DecodeState.ResultInvalidParameter; WriteBasicResult(ctx, resultAddress, status); Trace( $"batch_job_set_gapless_decode instance=0x{instanceId:X8} " + @@ -1172,10 +1163,6 @@ public static class AjmExports if ((flags & AjmJobSidebandFlagGaplessDecode) != 0 && (ulong)(offset + AjmSidebandGaplessDecodeBytes) <= size) { - var gapless = instance?.Atrac9?.Gapless ?? default; - BinaryPrimitives.WriteUInt32LittleEndian(sideband[offset..], gapless.TotalSamples); - BinaryPrimitives.WriteUInt16LittleEndian(sideband[(offset + 4)..], gapless.SkipSamples); - BinaryPrimitives.WriteUInt16LittleEndian(sideband[(offset + 6)..], gapless.SkippedSamples); offset += AjmSidebandGaplessDecodeBytes; } diff --git a/src/SharpEmu.Libs/Audio/Atrac9DecodeState.cs b/src/SharpEmu.Libs/Audio/Atrac9DecodeState.cs index 92feeb7..6f02d3b 100644 --- a/src/SharpEmu.Libs/Audio/Atrac9DecodeState.cs +++ b/src/SharpEmu.Libs/Audio/Atrac9DecodeState.cs @@ -20,8 +20,6 @@ internal readonly record struct Atrac9DecodeResult( ulong TotalDecodedSamples, uint Frames); -internal readonly record struct Atrac9Gapless(uint TotalSamples, ushort SkipSamples, ushort SkippedSamples); - internal sealed class Atrac9DecodeState { internal const int ResultNotInitialized = 0x00000001; @@ -49,33 +47,6 @@ internal sealed class Atrac9DecodeState private int _containerHeaderLength; private int _compressedLength; private ulong _totalDecodedSamples; - private Atrac9Gapless _gaplessInit; - private Atrac9Gapless _gapless; - - public Atrac9Gapless Gapless - { - get - { - lock (_gate) - { - return _gapless; - } - } - } - - public void SetGapless(uint totalSamples, ushort skipSamples, bool reset) - { - lock (_gate) - { - _gaplessInit = new Atrac9Gapless(totalSamples, skipSamples, 0); - if (reset || _gapless == default) - { - _gapless = _gaplessInit; - } - } - } - - private bool HasSampleLimit => _gaplessInit.TotalSamples is not 0 and not uint.MaxValue; public Atrac9Config? Config { @@ -111,7 +82,6 @@ internal sealed class Atrac9DecodeState _compressedLength = 0; _totalDecodedSamples = 0; _containerHeaderLength = 0; - _gapless = _gaplessInit; Trace( $"initialized config={Convert.ToHexString(normalizedConfig)} channels={config.ChannelCount} " + $"rate={config.SampleRate} frame_samples={config.FrameSamples} " + @@ -144,7 +114,6 @@ internal sealed class Atrac9DecodeState _compressedLength = 0; _totalDecodedSamples = 0; _containerHeaderLength = 0; - _gapless = _gaplessInit; if (_compressed is not null) { Array.Clear(_compressed); @@ -184,6 +153,7 @@ internal sealed class Atrac9DecodeState } var bytesPerSample = GetBytesPerSample(encoding); + var outputBytesPerSuperframe = checked(config.SuperframeSamples * channels * bytesPerSample); var consumed = 0; var written = 0; uint frames = 0; @@ -192,11 +162,6 @@ internal sealed class Atrac9DecodeState while (_compressedLength == config.SuperframeBytes || consumed < input.Length) { - if (HasSampleLimit && _gapless.TotalSamples == 0) - { - break; - } - // Titles that stream whole .at9 files hand AJM the RIFF/WAVE // container rather than a pointer into its `data` chunk, so the // stream has to be advanced past the header before the first @@ -237,15 +202,7 @@ internal sealed class Atrac9DecodeState break; } - var skip = Math.Min(config.SuperframeSamples, (int)_gapless.SkipSamples); - var samples = config.SuperframeSamples - skip; - if (HasSampleLimit) - { - samples = (int)Math.Min((uint)samples, _gapless.TotalSamples); - } - - var outputBytes = checked(samples * channels * bytesPerSample); - if (output.Length - written < outputBytes) + if (output.Length - written < outputBytesPerSuperframe) { status |= ResultNotEnoughRoom; break; @@ -274,19 +231,14 @@ internal sealed class Atrac9DecodeState WriteInterleaved( _planarPcm, - output.Slice(written, outputBytes), - skip, - samples, + output.Slice(written, outputBytesPerSuperframe), + config.SuperframeSamples, channels, encoding); - _gapless = new Atrac9Gapless( - HasSampleLimit ? _gapless.TotalSamples - (uint)samples : _gapless.TotalSamples, - (ushort)(_gapless.SkipSamples - skip), - (ushort)Math.Min(ushort.MaxValue, _gapless.SkippedSamples + config.SuperframeSamples - samples)); - written += outputBytes; + written += outputBytesPerSuperframe; _compressedLength = 0; - _totalDecodedSamples += unchecked((uint)samples); + _totalDecodedSamples += unchecked((uint)config.SuperframeSamples); frames += unchecked((uint)config.FramesPerSuperframe); if (!multipleFrames) @@ -401,13 +353,12 @@ internal sealed class Atrac9DecodeState private static void WriteInterleaved( short[][] source, Span destination, - int firstSample, int samples, int channels, Atrac9PcmEncoding encoding) { var offset = 0; - for (var sample = firstSample; sample < firstSample + samples; sample++) + for (var sample = 0; sample < samples; sample++) { for (var channel = 0; channel < channels; channel++) { diff --git a/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs b/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs index 59da5f3..07b17a5 100644 --- a/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs +++ b/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs @@ -60,14 +60,6 @@ public static class AudioOut2Exports // SceAudioOut2Attribute: u32 attributeId, 4 pad, const void* value, size_t valueSize. private const int AttributeEntrySize = 0x18; private const uint PortAttributeIdPcm = 0; - private const uint PortAttributeIdGain = 1; - private const uint PortAttributeIdPassthrough = 5; - private const uint PortAttributeIdAmbisonics = 8; - internal const uint AmbisonicsNone = uint.MaxValue; - internal const uint AmbisonicsAcnBase = 64; - internal const uint PassthroughLeft = 1; - internal const uint PassthroughRight = 2; - private const float HalfPower = 0.70710677f; private const ushort PortStateOutputConnectedPrimary = 0x01; // A host device that already holds this much audio is pacing the context; anything beyond the @@ -205,9 +197,6 @@ public static class AudioOut2Exports public uint SamplingFrequency { get; } public uint GrainSamples { get; } public ulong PcmAddress; - public float Gain = 1f; - public uint Ambisonics = AmbisonicsNone; - public uint Passthrough; public int PcmPending; @@ -556,7 +545,6 @@ public static class AudioOut2Exports Span entry = stackalloc byte[AttributeEntrySize]; Span pcm = stackalloc byte[8]; - Span word = stackalloc byte[sizeof(uint)]; for (uint i = 0; i < attributeCount; i++) { if (!ctx.Memory.TryRead(attributesAddress + (i * AttributeEntrySize), entry)) @@ -567,41 +555,7 @@ public static class AudioOut2Exports var attributeId = BinaryPrimitives.ReadUInt32LittleEndian(entry); var valueAddress = BinaryPrimitives.ReadUInt64LittleEndian(entry[0x08..]); var valueSize = BinaryPrimitives.ReadUInt64LittleEndian(entry[0x10..]); - if (valueAddress == 0) - { - continue; - } - - if (attributeId is PortAttributeIdGain or PortAttributeIdPassthrough or PortAttributeIdAmbisonics) - { - if (valueSize < sizeof(uint) || !ctx.Memory.TryRead(valueAddress, word)) - { - continue; - } - - var value = BinaryPrimitives.ReadUInt32LittleEndian(word); - switch (attributeId) - { - case PortAttributeIdGain: - var gain = BitConverter.UInt32BitsToSingle(value); - if (float.IsFinite(gain)) - { - port.Gain = gain; - } - - break; - case PortAttributeIdPassthrough: - port.Passthrough = value; - break; - default: - port.Ambisonics = value; - break; - } - - continue; - } - - if (attributeId != PortAttributeIdPcm || valueSize < 8) + if (attributeId != PortAttributeIdPcm || valueAddress == 0 || valueSize < 8) { continue; } @@ -1133,9 +1087,7 @@ public static class AudioOut2Exports if (port.ContextHandle != context.Handle || port.PcmAddress == 0 || Interlocked.Exchange(ref port.PcmPending, 0) == 0 || - !TryDecodeDataFormat(port.DataFormat, out var ch, out var bps, out var isFloat) || - !TryGetStereoGains(port.PortType, ch, port.Gain, port.Ambisonics, port.Passthrough, - out var leftGain, out var rightGain)) + !TryDecodeDataFormat(port.DataFormat, out var ch, out var bps, out var isFloat)) { continue; } @@ -1159,8 +1111,7 @@ public static class AudioOut2Exports ch, bps, isFloat, - leftGain, - rightGain); + additive: mixedPorts > 0); mixedPorts++; } @@ -1211,69 +1162,20 @@ public static class AudioOut2Exports } } - internal static bool RoutesToSpeakers(ushort portType) => (portType & 0xFF) is 0 or 1 or 2; - - internal static (float Gain, uint Ambisonics, uint Passthrough)? PortMixAttributes(ulong portHandle) => - Ports.TryGetValue(portHandle, out var port) ? (port.Gain, port.Ambisonics, port.Passthrough) : null; - - internal static bool TryGetStereoGains( - ushort portType, - int channels, - float gain, - uint ambisonics, - uint passthrough, - out float left, - out float right) + private static bool IsMainOrBgmPort(ushort portType) { - left = 0f; - right = 0f; - if (!RoutesToSpeakers(portType) || gain == 0f) - { - return false; - } - - if (!IsObjectPort(portType) || channels != 1) - { - left = gain; - right = gain; - return true; - } - - if (ambisonics == AmbisonicsNone) - { - left = passthrough == PassthroughRight ? 0f : passthrough == PassthroughLeft ? gain : gain * HalfPower; - right = passthrough == PassthroughLeft ? 0f : passthrough == PassthroughRight ? gain : gain * HalfPower; - return true; - } - - var acn = ambisonics >= AmbisonicsAcnBase; - var component = acn ? ambisonics - AmbisonicsAcnBase : ambisonics; - if (component == 0) - { - left = acn ? gain * HalfPower : gain; - right = left; - return true; - } - - if (component == (acn ? 1u : 2u)) - { - left = gain * HalfPower; - right = -left; - return true; - } - - return false; + var kind = portType & 0xFF; + return kind is 0 or 1; } - internal static void MixPortIntoStereo( + private static void MixPortIntoStereo( ReadOnlySpan source, Span mix, int frames, int channels, int bytesPerSample, bool isFloat, - float leftGain, - float rightGain) + bool additive) { var frameSize = channels * bytesPerSample; for (var frame = 0; frame < frames; frame++) @@ -1302,8 +1204,16 @@ public static class AudioOut2Exports : ReadNormalizedSample(frameBytes, 1, bytesPerSample, isFloat); } - mix[frame * 2] += left * leftGain; - mix[(frame * 2) + 1] += right * rightGain; + if (additive) + { + mix[frame * 2] += left; + mix[(frame * 2) + 1] += right; + } + else + { + mix[frame * 2] = left; + mix[(frame * 2) + 1] = right; + } } } diff --git a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs index 4ee950a..8bd5cd9 100644 --- a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs +++ b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs @@ -557,21 +557,7 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable private ulong _bdaTouchTick = ulong.MaxValue; private ulong _bdaTouchMapping; - public static ulong MappingKey(IReadOnlyCollection spans) - { - var mapping = (ulong)spans.Count; - foreach (var span in spans) - mapping = (mapping ^ span.Address ^ (span.Size << 17)) * 0x100000001B3UL; - return mapping; - } - public void PrepareBda(IEnumerable mapped) - { - var spans = mapped as IReadOnlyCollection ?? mapped.ToList(); - PrepareBda(spans, MappingKey(spans)); - } - - public void PrepareBda(IReadOnlyCollection mapped, ulong mapping) { var traceAddress = GuestGpuMemoryHook.TraceAddress; var traceCovered = false; @@ -586,7 +572,10 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable } else { - var spans = mapped; + var spans = mapped as IReadOnlyCollection ?? mapped.ToList(); + var mapping = (ulong)spans.Count; + foreach (var span in spans) + mapping = (mapping ^ span.Address ^ (span.Size << 17)) * 0x100000001B3UL; if (_retirementPolicy.CurrentTick != _bdaTouchTick || mapping != _bdaTouchMapping) { foreach (var span in spans) diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs index 885b7f5..b93ca9b 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs @@ -402,7 +402,8 @@ internal static unsafe partial class VulkanVideoPresenter } using var profileScope = BufferUploadProfile.BeginSweep(vertexProgramHash, pixelProgramHash, computeProgramHash); var memory = GuestGpuMemoryHook.Current ?? throw SubmissionScheduler.Fatal("A device-address program needs the guest GPU memory registry."); - var spans = DeviceAddressSpans(memory); + var spans = new List(); + memory.ForEachSpan((address, size) => spans.Add(new GuestSpan(address, size))); var traceAddress = GuestGpuMemoryHook.TraceAddress; if (traceAddress != 0) { @@ -419,29 +420,7 @@ internal static unsafe partial class VulkanVideoPresenter if (traceAddress != 0 && !memory.Covers(traceAddress, 1)) GuestGpuMemoryHook.Trace(traceAddress, 1, $"device-address-mapping-check readable={_guestMemory.CanRead(traceAddress, 1)} backed={_guestBacking.IsBackedView(traceAddress)}"); - _bufferCache.PrepareBda(spans, _bdaSpanMapping); - } - - private List? _bdaSpans; - private GuestGpuMemory? _bdaSpanMemory; - private long _bdaSpanVersion = -1; - private ulong _bdaSpanMapping; - - private List DeviceAddressSpans(GuestGpuMemory memory) - { - var version = memory.SpanVersion; - if (_bdaSpans is { } cached && ReferenceEquals(_bdaSpanMemory, memory) && _bdaSpanVersion == version) - { - return cached; - } - - var spans = new List(); - memory.ForEachSpan((address, size) => spans.Add(new GuestSpan(address, size))); - _bdaSpans = spans; - _bdaSpanMemory = memory; - _bdaSpanVersion = version; - _bdaSpanMapping = GuestBufferCache.MappingKey(spans); - return spans; + _bufferCache.PrepareBda(spans); } public void BindResources(IPreparedBindings prepared) diff --git a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs index b65eb7e..6a48a5b 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs @@ -473,19 +473,6 @@ public static class ResourceMaterializer return value.ToString(); } - private static readonly HashSet NulledSampledFormats = new(); - - private static void ReportNulledSampledFormat(uint format) - { - lock (NulledSampledFormats) - { - if (NulledSampledFormats.Add(format)) - { - Console.Error.WriteLine($"[GPU][WARN] A sampled image descriptor uses unsupported format {format}; it is bound as a null texture."); - } - } - } - private static bool NullImageDescriptor(ReadOnlySpan descriptor) => descriptor[0] == 0 && (descriptor[1] & 0xFF) == 0; @@ -1067,19 +1054,7 @@ public static class ResourceMaterializer numericClass = ImageNumericClass.Uint; } } - else if (numericClass == ImageNumericClass.Unsupported) - { - ReportNulledSampledFormat(format); - Array.Clear(descriptor); - images[index] = image with - { - NumericClass = ImageNumericClass.Float, - Dimension = ImageDimension.Dim2D, - Cube = false, - }; - continue; - } - else if (baseImage.DepthCompare && numericClass != ImageNumericClass.Float) + else if (numericClass == ImageNumericClass.Unsupported || (baseImage.DepthCompare && numericClass != ImageNumericClass.Float)) { return Fail($"sampled image descriptor {index} uses unsupported format {format}"); } diff --git a/tests/SharpEmu.Libs.Tests/Audio/Atrac9GaplessTests.cs b/tests/SharpEmu.Libs.Tests/Audio/Atrac9GaplessTests.cs deleted file mode 100644 index 2004c99..0000000 --- a/tests/SharpEmu.Libs.Tests/Audio/Atrac9GaplessTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -using SharpEmu.Libs.Audio; -using Xunit; - -namespace SharpEmu.Libs.Tests.Audio; - -public sealed class Atrac9GaplessTests -{ - private static readonly byte[] MonoConfig = [0xFE, 0x70, 0x17, 0xE0]; - private const int SuperframeBytes = 192; - private const int SuperframeSamples = 256; - private static readonly byte[] SilentFrame = [0x00, 0x84, 0x00, 0x40]; - - private static Atrac9DecodeState CreateDecoder() - { - var decoder = new Atrac9DecodeState(); - Assert.True(decoder.TryInitialize(MonoConfig)); - return decoder; - } - - private static Atrac9DecodeResult DecodeSuperframes(Atrac9DecodeState decoder, int superframes, int outputSamples) - { - var input = new byte[SuperframeBytes * superframes]; - for (var superframe = 0; superframe < superframes; superframe++) - { - SilentFrame.CopyTo(input, superframe * SuperframeBytes); - } - - var output = new byte[outputSamples * sizeof(short)]; - return decoder.Decode(input, output, Atrac9PcmEncoding.Signed16, requestedChannels: 0, multipleFrames: true); - } - - [Fact] - public void WithoutAWindowEverySuperframeSampleIsWritten() - { - var decoder = CreateDecoder(); - - var result = DecodeSuperframes(decoder, 2, SuperframeSamples * 2); - - Assert.Equal(SuperframeSamples * 2 * sizeof(short), result.OutputWritten); - Assert.Equal((ulong)(SuperframeSamples * 2), result.TotalDecodedSamples); - Assert.Equal(default, decoder.Gapless); - } - - [Fact] - public void TheWindowSkipsTheLeadInAndStopsAtTheTotal() - { - var decoder = CreateDecoder(); - decoder.SetGapless(totalSamples: 500, skipSamples: 100, reset: false); - - var first = DecodeSuperframes(decoder, 1, SuperframeSamples); - Assert.Equal(156 * sizeof(short), first.OutputWritten); - Assert.Equal(new Atrac9Gapless(344, 0, 100), decoder.Gapless); - - var rest = DecodeSuperframes(decoder, 3, SuperframeSamples * 3); - Assert.Equal((256 + 88) * sizeof(short), rest.OutputWritten); - Assert.Equal(SuperframeBytes * 2, rest.InputConsumed); - Assert.Equal(new Atrac9Gapless(0, 0, 100 + 256 - 88), decoder.Gapless); - Assert.Equal(500ul, rest.TotalDecodedSamples); - - var ended = DecodeSuperframes(decoder, 1, SuperframeSamples); - Assert.Equal(0, ended.OutputWritten); - Assert.Equal(0, ended.InputConsumed); - } - - [Fact] - public void ALeadInLongerThanASuperframeWritesNothingForIt() - { - var decoder = CreateDecoder(); - decoder.SetGapless(totalSamples: 0, skipSamples: 300, reset: false); - - var first = DecodeSuperframes(decoder, 1, 0); - Assert.Equal(0, first.OutputWritten); - Assert.Equal(SuperframeBytes, first.InputConsumed); - Assert.Equal(new Atrac9Gapless(0, 44, 256), decoder.Gapless); - - var second = DecodeSuperframes(decoder, 1, SuperframeSamples); - Assert.Equal((256 - 44) * sizeof(short), second.OutputWritten); - Assert.Equal(new Atrac9Gapless(0, 0, 300), decoder.Gapless); - } - - [Fact] - public void TheRoomCheckUsesTheTrimmedSize() - { - var decoder = CreateDecoder(); - decoder.SetGapless(totalSamples: 100, skipSamples: 0, reset: false); - - var result = DecodeSuperframes(decoder, 1, 100); - - Assert.Equal(100 * sizeof(short), result.OutputWritten); - Assert.Equal(0, result.Status & Atrac9DecodeState.ResultNotEnoughRoom); - } - - [Fact] - public void ResetRestoresTheWindowAndASetWithoutResetKeepsProgress() - { - var decoder = CreateDecoder(); - decoder.SetGapless(totalSamples: 500, skipSamples: 100, reset: false); - DecodeSuperframes(decoder, 1, SuperframeSamples); - - decoder.SetGapless(totalSamples: 500, skipSamples: 100, reset: false); - Assert.Equal(new Atrac9Gapless(344, 0, 100), decoder.Gapless); - - decoder.Reset(); - Assert.Equal(new Atrac9Gapless(500, 100, 0), decoder.Gapless); - - DecodeSuperframes(decoder, 1, SuperframeSamples); - decoder.SetGapless(totalSamples: 2000, skipSamples: 10, reset: true); - Assert.Equal(new Atrac9Gapless(2000, 10, 0), decoder.Gapless); - } -} diff --git a/tests/SharpEmu.Libs.Tests/Audio/AudioOut2MixTests.cs b/tests/SharpEmu.Libs.Tests/Audio/AudioOut2MixTests.cs deleted file mode 100644 index a59626c..0000000 --- a/tests/SharpEmu.Libs.Tests/Audio/AudioOut2MixTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// 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 AudioOut2MixTests -{ - private const ulong MemoryBase = 0x1_0000_0000; - private const ushort ObjectMain = 0x100; - private const float HalfPower = 0.70710677f; - - [Theory] - [InlineData((ushort)0, true)] - [InlineData((ushort)1, true)] - [InlineData((ushort)2, true)] - [InlineData(ObjectMain, true)] - [InlineData((ushort)3, false)] - [InlineData((ushort)4, false)] - [InlineData((ushort)5, false)] - [InlineData((ushort)6, false)] - public void OnlyMainBgmAndVoicePortsReachTheSpeakers(ushort portType, bool expected) - { - Assert.Equal(expected, AudioOut2Exports.RoutesToSpeakers(portType)); - Assert.Equal(expected, AudioOut2Exports.TryGetStereoGains(portType, 2, 1f, AudioOut2Exports.AmbisonicsNone, 0, out _, out _)); - } - - [Theory] - [InlineData(64u, HalfPower, HalfPower)] - [InlineData(65u, HalfPower, -HalfPower)] - [InlineData(0u, 1f, 1f)] - [InlineData(2u, HalfPower, -HalfPower)] - public void FirstOrderAmbisonicsDecodeToStereoCardioids(uint ambisonics, float left, float right) - { - Assert.True(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 0.5f, ambisonics, 0, out var leftGain, out var rightGain)); - Assert.Equal(left * 0.5f, leftGain, 5); - Assert.Equal(right * 0.5f, rightGain, 5); - } - - [Theory] - [InlineData(66u)] - [InlineData(67u)] - [InlineData(99u)] - [InlineData(1u)] - [InlineData(3u)] - [InlineData(15u)] - public void HigherAmbisonicComponentsDoNotFeedStereo(uint ambisonics) - { - Assert.False(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 1f, ambisonics, 0, out _, out _)); - } - - [Fact] - public void PlainObjectsPlayCentredOrPassThroughToOneSide() - { - Assert.True(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 1f, AudioOut2Exports.AmbisonicsNone, 0, out var left, out var right)); - Assert.Equal(HalfPower, left, 5); - Assert.Equal(HalfPower, right, 5); - Assert.True(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 1f, AudioOut2Exports.AmbisonicsNone, AudioOut2Exports.PassthroughLeft, out left, out right)); - Assert.Equal((1f, 0f), (left, right)); - Assert.True(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 1f, AudioOut2Exports.AmbisonicsNone, AudioOut2Exports.PassthroughRight, out left, out right)); - Assert.Equal((0f, 1f), (left, right)); - Assert.False(AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 0f, AudioOut2Exports.AmbisonicsNone, 0, out _, out _)); - } - - [Fact] - public void FifthOrderSceneDecodesASourceToItsSide() - { - const int frames = 4; - var leftSource = DecodeScene(frames, azimuthSine: 1f); - var centreSource = DecodeScene(frames, azimuthSine: 0f); - - Assert.Equal(2f * HalfPower, leftSource[0], 4); - Assert.Equal(0f, leftSource[1], 4); - Assert.Equal(HalfPower, centreSource[0], 4); - Assert.Equal(HalfPower, centreSource[1], 4); - } - - [Fact] - public void SetAttributesStoresGainAmbisonicsAndPassthrough() - { - var memory = new FakeCpuMemory(MemoryBase, 0x1000); - var ctx = new CpuContext(memory, Generation.Gen5); - var param = new byte[0x40]; - BinaryPrimitives.WriteUInt16LittleEndian(param, ObjectMain); - BinaryPrimitives.WriteUInt32LittleEndian(param.AsSpan(4), 0x100); - Assert.True(memory.TryWrite(MemoryBase, param)); - ctx[CpuRegister.Rdi] = 0; - ctx[CpuRegister.Rsi] = MemoryBase; - ctx[CpuRegister.Rdx] = MemoryBase + 0x80; - Assert.Equal(0, AudioOut2Exports.AudioOut2PortCreate(ctx)); - var handle = BinaryPrimitives.ReadUInt64LittleEndian(Read(memory, MemoryBase + 0x80, 8)); - - var values = new byte[12]; - BinaryPrimitives.WriteSingleLittleEndian(values, 0.25f); - BinaryPrimitives.WriteUInt32LittleEndian(values.AsSpan(4), 65); - BinaryPrimitives.WriteUInt32LittleEndian(values.AsSpan(8), AudioOut2Exports.PassthroughLeft); - Assert.True(memory.TryWrite(MemoryBase + 0x100, values)); - var attributes = new byte[0x18 * 3]; - WriteAttribute(attributes, 0, 1, MemoryBase + 0x100); - WriteAttribute(attributes, 1, 8, MemoryBase + 0x104); - WriteAttribute(attributes, 2, 5, MemoryBase + 0x108); - Assert.True(memory.TryWrite(MemoryBase + 0x200, attributes)); - ctx[CpuRegister.Rdi] = handle; - ctx[CpuRegister.Rsi] = MemoryBase + 0x200; - ctx[CpuRegister.Rdx] = 3; - - Assert.Equal(0, AudioOut2Exports.AudioOut2PortSetAttributes(ctx)); - - Assert.Equal((0.25f, 65u, AudioOut2Exports.PassthroughLeft), AudioOut2Exports.PortMixAttributes(handle)); - ctx[CpuRegister.Rdi] = handle; - AudioOut2Exports.AudioOut2PortDestroy(ctx); - } - - private static float[] DecodeScene(int frames, float azimuthSine) - { - var mix = new float[frames * 2]; - var source = new byte[frames * sizeof(float)]; - for (var acn = 0u; acn < 36; acn++) - { - var component = acn switch - { - 0 => 1f, - 1 => azimuthSine, - _ => 0.6f, - }; - for (var frame = 0; frame < frames; frame++) - { - BinaryPrimitives.WriteSingleLittleEndian(source.AsSpan(frame * sizeof(float)), component); - } - - if (AudioOut2Exports.TryGetStereoGains(ObjectMain, 1, 1f, AudioOut2Exports.AmbisonicsAcnBase + acn, 0, out var left, out var right)) - { - AudioOut2Exports.MixPortIntoStereo(source, mix, frames, 1, sizeof(float), true, left, right); - } - } - - return mix; - } - - private static void WriteAttribute(byte[] attributes, int index, uint id, ulong value) - { - var entry = attributes.AsSpan(index * 0x18, 0x18); - BinaryPrimitives.WriteUInt32LittleEndian(entry, id); - BinaryPrimitives.WriteUInt64LittleEndian(entry[0x08..], value); - BinaryPrimitives.WriteUInt64LittleEndian(entry[0x10..], sizeof(uint)); - } - - private static byte[] Read(FakeCpuMemory memory, ulong address, int length) - { - var bytes = new byte[length]; - Assert.True(memory.TryRead(address, bytes)); - return bytes; - } -} diff --git a/tests/SharpEmu.Libs.Tests/Cpu/GuestStackBoundsTests.cs b/tests/SharpEmu.Libs.Tests/Cpu/GuestStackBoundsTests.cs deleted file mode 100644 index b8c3d4a..0000000 --- a/tests/SharpEmu.Libs.Tests/Cpu/GuestStackBoundsTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -using SharpEmu.Core.Cpu.Native; -using SharpEmu.HLE; -using Xunit; - -namespace SharpEmu.Libs.Tests.Cpu; - -public sealed class GuestStackBoundsTests -{ - [Fact] - public unsafe void AllocationQueryHandlesProtectionSplitsAndFallsBackForPartialCommit() - { - if (!OperatingSystem.IsWindows()) return; - - var pageSize = (nuint)Environment.SystemPageSize; - var memory = (byte*)HostMemory.Alloc(null, pageSize * 3, - HostMemory.MEM_RESERVE | HostMemory.MEM_COMMIT, HostMemory.PAGE_READWRITE); - Assert.True(memory != null); - try - { - Assert.True(HostMemory.Protect(memory + pageSize, pageSize, HostMemory.PAGE_READONLY, out _)); - // VirtualQuery at the last page would return only that page, not the allocation. - Assert.True(DirectExecutionBackend.TryGetGuestStackBounds((ulong)(memory + pageSize * 2 + 16), - out var bottom, out var top)); - Assert.Equal((ulong)memory, bottom); - Assert.Equal((ulong)(memory + pageSize * 3), top); - - Assert.True(HostMemory.Free(memory, pageSize, 0x4000)); // MEM_DECOMMIT - Assert.True(DirectExecutionBackend.TryGetGuestStackBounds((ulong)(memory + pageSize * 2 + 16), - out bottom, out top)); - Assert.Equal((ulong)(memory + pageSize * 2), bottom); - Assert.Equal((ulong)(memory + pageSize * 3), top); - Assert.False(DirectExecutionBackend.TryGetGuestStackBounds((ulong)memory, out _, out _)); - } - finally - { - Assert.True(HostMemory.Free(memory, 0, HostMemory.MEM_RELEASE)); - } - - Assert.False(DirectExecutionBackend.TryGetGuestStackBounds(0, out _, out _)); - } -}