mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 06:29:46 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1bba8d99d |
@@ -2,9 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace SharpEmu.Libs.Audio;
|
namespace SharpEmu.Libs.Audio;
|
||||||
@@ -14,70 +12,7 @@ public static class AudioOutExports
|
|||||||
private static readonly ConcurrentDictionary<int, PortState> Ports = new();
|
private static readonly ConcurrentDictionary<int, PortState> Ports = new();
|
||||||
private static int _nextPortHandle;
|
private static int _nextPortHandle;
|
||||||
|
|
||||||
private sealed class PortState : IDisposable
|
private sealed record PortState(int UserId, int Type, uint BufferLength, uint Frequency, int Format);
|
||||||
{
|
|
||||||
private readonly object _paceGate = new();
|
|
||||||
private long _nextSilentOutput;
|
|
||||||
|
|
||||||
public PortState(
|
|
||||||
int userId,
|
|
||||||
int type,
|
|
||||||
uint bufferLength,
|
|
||||||
uint frequency,
|
|
||||||
int format,
|
|
||||||
int channels,
|
|
||||||
int bytesPerSample,
|
|
||||||
bool isFloat,
|
|
||||||
WinMmAudioPort? backend)
|
|
||||||
{
|
|
||||||
UserId = userId;
|
|
||||||
Type = type;
|
|
||||||
BufferLength = bufferLength;
|
|
||||||
Frequency = frequency;
|
|
||||||
Format = format;
|
|
||||||
Channels = channels;
|
|
||||||
BytesPerSample = bytesPerSample;
|
|
||||||
IsFloat = isFloat;
|
|
||||||
Backend = backend;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int UserId { get; }
|
|
||||||
public int Type { get; }
|
|
||||||
public uint BufferLength { get; }
|
|
||||||
public uint Frequency { get; }
|
|
||||||
public int Format { get; }
|
|
||||||
public int Channels { get; }
|
|
||||||
public int BytesPerSample { get; }
|
|
||||||
public bool IsFloat { get; }
|
|
||||||
public WinMmAudioPort? Backend { get; }
|
|
||||||
public int BufferByteLength =>
|
|
||||||
checked((int)BufferLength * Channels * BytesPerSample);
|
|
||||||
|
|
||||||
public void PaceSilence()
|
|
||||||
{
|
|
||||||
long delay;
|
|
||||||
lock (_paceGate)
|
|
||||||
{
|
|
||||||
var now = Stopwatch.GetTimestamp();
|
|
||||||
if (_nextSilentOutput < now)
|
|
||||||
{
|
|
||||||
_nextSilentOutput = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
delay = _nextSilentOutput - now;
|
|
||||||
_nextSilentOutput += checked(
|
|
||||||
(long)Math.Ceiling(
|
|
||||||
Stopwatch.Frequency * (double)BufferLength / Frequency));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delay > 0)
|
|
||||||
{
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds((double)delay / Stopwatch.Frequency));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => Backend?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "JfEPXVxhFqA",
|
Nid = "JfEPXVxhFqA",
|
||||||
@@ -98,41 +33,13 @@ public static class AudioOutExports
|
|||||||
var bufferLength = unchecked((uint)ctx[CpuRegister.Rcx]);
|
var bufferLength = unchecked((uint)ctx[CpuRegister.Rcx]);
|
||||||
var frequency = unchecked((uint)ctx[CpuRegister.R8]);
|
var frequency = unchecked((uint)ctx[CpuRegister.R8]);
|
||||||
var format = unchecked((int)ctx[CpuRegister.R9]);
|
var format = unchecked((int)ctx[CpuRegister.R9]);
|
||||||
if (bufferLength == 0 || frequency == 0 ||
|
if (bufferLength == 0 || frequency == 0)
|
||||||
!TryGetFormat(format, out var channels, out var bytesPerSample, out var isFloat))
|
|
||||||
{
|
{
|
||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
WinMmAudioPort? backend = null;
|
|
||||||
string backendName;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
backend = new WinMmAudioPort(frequency);
|
|
||||||
backendName = "winmm";
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
backendName = "silent";
|
|
||||||
Console.Error.WriteLine(
|
|
||||||
$"[LOADER][WARN] AudioOut host backend unavailable: {exception.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var handle = Interlocked.Increment(ref _nextPortHandle);
|
var handle = Interlocked.Increment(ref _nextPortHandle);
|
||||||
Ports[handle] = new PortState(
|
Ports[handle] = new PortState(userId, type, bufferLength, frequency, format);
|
||||||
userId,
|
|
||||||
type,
|
|
||||||
bufferLength,
|
|
||||||
frequency,
|
|
||||||
format,
|
|
||||||
channels,
|
|
||||||
bytesPerSample,
|
|
||||||
isFloat,
|
|
||||||
backend);
|
|
||||||
Console.Error.WriteLine(
|
|
||||||
$"[LOADER][INFO] AudioOut port {handle}: {frequency} Hz, " +
|
|
||||||
$"{channels} ch, {(isFloat ? "float32" : "s16")}, " +
|
|
||||||
$"{bufferLength} frames, backend={backendName}");
|
|
||||||
return SetReturn(ctx, handle);
|
return SetReturn(ctx, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +51,11 @@ public static class AudioOutExports
|
|||||||
public static int AudioOutClose(CpuContext ctx)
|
public static int AudioOutClose(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||||
if (!Ports.TryRemove(handle, out var port))
|
return SetReturn(
|
||||||
{
|
ctx,
|
||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
Ports.TryRemove(handle, out _)
|
||||||
}
|
? 0
|
||||||
|
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
port.Dispose();
|
|
||||||
return SetReturn(ctx, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
@@ -160,44 +65,8 @@ public static class AudioOutExports
|
|||||||
LibraryName = "libSceAudioOut")]
|
LibraryName = "libSceAudioOut")]
|
||||||
public static int AudioOutOutput(CpuContext ctx)
|
public static int AudioOutOutput(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
ctx[CpuRegister.Rax] = 0;
|
||||||
var sourceAddress = ctx[CpuRegister.Rsi];
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
if (!Ports.TryGetValue(handle, out var port))
|
|
||||||
{
|
|
||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceAddress == 0)
|
|
||||||
{
|
|
||||||
return SetReturn(ctx, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(port.BufferByteLength);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var source = buffer.AsSpan(0, port.BufferByteLength);
|
|
||||||
if (!ctx.Memory.TryRead(sourceAddress, source))
|
|
||||||
{
|
|
||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port.Backend is null ||
|
|
||||||
!port.Backend.Submit(
|
|
||||||
source,
|
|
||||||
port.BufferLength,
|
|
||||||
port.Channels,
|
|
||||||
port.BytesPerSample,
|
|
||||||
port.IsFloat))
|
|
||||||
{
|
|
||||||
port.PaceSilence();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SetReturn(ctx, 0);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
@@ -220,23 +89,4 @@ public static class AudioOutExports
|
|||||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetFormat(
|
|
||||||
int rawFormat,
|
|
||||||
out int channels,
|
|
||||||
out int bytesPerSample,
|
|
||||||
out bool isFloat)
|
|
||||||
{
|
|
||||||
var format = rawFormat & 0xFF;
|
|
||||||
channels = format switch
|
|
||||||
{
|
|
||||||
0 or 3 => 1,
|
|
||||||
1 or 4 => 2,
|
|
||||||
2 or 5 or 6 or 7 => 8,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
bytesPerSample = format is >= 3 and <= 5 or 7 ? 4 : 2;
|
|
||||||
isFloat = bytesPerSample == 4;
|
|
||||||
return channels != 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace SharpEmu.Libs.Audio;
|
|
||||||
|
|
||||||
internal sealed class WinMmAudioPort : IDisposable
|
|
||||||
{
|
|
||||||
private const uint WaveMapper = uint.MaxValue;
|
|
||||||
private const uint CallbackEvent = 0x0005_0000;
|
|
||||||
private const ushort WaveFormatPcm = 1;
|
|
||||||
private const uint WaveHeaderDone = 0x0000_0001;
|
|
||||||
private const int MaximumQueuedPcmBytes = 32 * 1024;
|
|
||||||
|
|
||||||
private readonly object _gate = new();
|
|
||||||
private readonly AutoResetEvent _completion = new(false);
|
|
||||||
private readonly Queue<NativeBuffer> _buffers = new();
|
|
||||||
private IntPtr _device;
|
|
||||||
private int _queuedPcmBytes;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public WinMmAudioPort(uint sampleRate)
|
|
||||||
{
|
|
||||||
if (!OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
throw new PlatformNotSupportedException("WinMM audio is only available on Windows.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var format = new WaveFormat
|
|
||||||
{
|
|
||||||
FormatTag = WaveFormatPcm,
|
|
||||||
Channels = 2,
|
|
||||||
SamplesPerSecond = sampleRate,
|
|
||||||
AverageBytesPerSecond = checked(sampleRate * 4),
|
|
||||||
BlockAlign = 4,
|
|
||||||
BitsPerSample = 16,
|
|
||||||
ExtraSize = 0,
|
|
||||||
};
|
|
||||||
var result = WaveOutOpen(
|
|
||||||
out _device,
|
|
||||||
WaveMapper,
|
|
||||||
ref format,
|
|
||||||
_completion.SafeWaitHandle.DangerousGetHandle(),
|
|
||||||
IntPtr.Zero,
|
|
||||||
CallbackEvent);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"waveOutOpen failed with MMRESULT {result}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Submit(
|
|
||||||
ReadOnlySpan<byte> source,
|
|
||||||
uint frames,
|
|
||||||
int channels,
|
|
||||||
int bytesPerSample,
|
|
||||||
bool isFloat)
|
|
||||||
{
|
|
||||||
lock (_gate)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var outputLength = checked((int)frames * 4);
|
|
||||||
ReapCompletedBuffers();
|
|
||||||
while (_queuedPcmBytes != 0 &&
|
|
||||||
_queuedPcmBytes + outputLength > MaximumQueuedPcmBytes)
|
|
||||||
{
|
|
||||||
if (!_completion.WaitOne(TimeSpan.FromSeconds(1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReapCompletedBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = ArrayPool<byte>.Shared.Rent(outputLength);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ConvertToStereoPcm16(
|
|
||||||
source,
|
|
||||||
output.AsSpan(0, outputLength),
|
|
||||||
checked((int)frames),
|
|
||||||
channels,
|
|
||||||
bytesPerSample,
|
|
||||||
isFloat);
|
|
||||||
return QueueBuffer(output.AsSpan(0, outputLength));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
lock (_gate)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
if (_device != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
WaveOutReset(_device);
|
|
||||||
while (_buffers.TryDequeue(out var buffer))
|
|
||||||
{
|
|
||||||
ReleaseBuffer(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
WaveOutClose(_device);
|
|
||||||
_device = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
_completion.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool QueueBuffer(ReadOnlySpan<byte> data)
|
|
||||||
{
|
|
||||||
var dataAddress = Marshal.AllocHGlobal(data.Length);
|
|
||||||
var headerAddress = IntPtr.Zero;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
data.CopyTo(new Span<byte>((void*)dataAddress, data.Length));
|
|
||||||
}
|
|
||||||
|
|
||||||
var header = new WaveHeader
|
|
||||||
{
|
|
||||||
Data = dataAddress,
|
|
||||||
BufferLength = checked((uint)data.Length),
|
|
||||||
};
|
|
||||||
headerAddress = Marshal.AllocHGlobal(Marshal.SizeOf<WaveHeader>());
|
|
||||||
Marshal.StructureToPtr(header, headerAddress, false);
|
|
||||||
|
|
||||||
var result = WaveOutPrepareHeader(
|
|
||||||
_device,
|
|
||||||
headerAddress,
|
|
||||||
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = WaveOutWrite(
|
|
||||||
_device,
|
|
||||||
headerAddress,
|
|
||||||
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
WaveOutUnprepareHeader(
|
|
||||||
_device,
|
|
||||||
headerAddress,
|
|
||||||
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffers.Enqueue(new NativeBuffer(dataAddress, headerAddress, data.Length));
|
|
||||||
_queuedPcmBytes += data.Length;
|
|
||||||
dataAddress = IntPtr.Zero;
|
|
||||||
headerAddress = IntPtr.Zero;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (headerAddress != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(headerAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataAddress != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(dataAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReapCompletedBuffers()
|
|
||||||
{
|
|
||||||
while (_buffers.TryPeek(out var buffer))
|
|
||||||
{
|
|
||||||
var header = Marshal.PtrToStructure<WaveHeader>(buffer.Header);
|
|
||||||
if ((header.Flags & WaveHeaderDone) == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffers.Dequeue();
|
|
||||||
ReleaseBuffer(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReleaseBuffer(NativeBuffer buffer)
|
|
||||||
{
|
|
||||||
WaveOutUnprepareHeader(
|
|
||||||
_device,
|
|
||||||
buffer.Header,
|
|
||||||
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
|
||||||
_queuedPcmBytes -= buffer.Length;
|
|
||||||
Marshal.FreeHGlobal(buffer.Header);
|
|
||||||
Marshal.FreeHGlobal(buffer.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConvertToStereoPcm16(
|
|
||||||
ReadOnlySpan<byte> source,
|
|
||||||
Span<byte> destination,
|
|
||||||
int frames,
|
|
||||||
int channels,
|
|
||||||
int bytesPerSample,
|
|
||||||
bool isFloat)
|
|
||||||
{
|
|
||||||
var sourceFrameSize = checked(channels * bytesPerSample);
|
|
||||||
for (var frame = 0; frame < frames; frame++)
|
|
||||||
{
|
|
||||||
var sourceFrame = source.Slice(frame * sourceFrameSize, sourceFrameSize);
|
|
||||||
var left = ReadSample(sourceFrame, 0, bytesPerSample, isFloat);
|
|
||||||
var right = channels == 1
|
|
||||||
? left
|
|
||||||
: ReadSample(sourceFrame, 1, bytesPerSample, isFloat);
|
|
||||||
BinaryPrimitives.WriteInt16LittleEndian(destination[(frame * 4)..], left);
|
|
||||||
BinaryPrimitives.WriteInt16LittleEndian(destination[((frame * 4) + 2)..], right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static short ReadSample(
|
|
||||||
ReadOnlySpan<byte> frame,
|
|
||||||
int channel,
|
|
||||||
int bytesPerSample,
|
|
||||||
bool isFloat)
|
|
||||||
{
|
|
||||||
var sample = frame.Slice(channel * bytesPerSample, bytesPerSample);
|
|
||||||
if (!isFloat)
|
|
||||||
{
|
|
||||||
return BinaryPrimitives.ReadInt16LittleEndian(sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bits = BinaryPrimitives.ReadInt32LittleEndian(sample);
|
|
||||||
var value = Math.Clamp(BitConverter.Int32BitsToSingle(bits), -1.0f, 1.0f);
|
|
||||||
return checked((short)MathF.Round(value * short.MaxValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly record struct NativeBuffer(IntPtr Data, IntPtr Header, int Length);
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
|
||||||
private struct WaveFormat
|
|
||||||
{
|
|
||||||
public ushort FormatTag;
|
|
||||||
public ushort Channels;
|
|
||||||
public uint SamplesPerSecond;
|
|
||||||
public uint AverageBytesPerSecond;
|
|
||||||
public ushort BlockAlign;
|
|
||||||
public ushort BitsPerSample;
|
|
||||||
public ushort ExtraSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
private struct WaveHeader
|
|
||||||
{
|
|
||||||
public IntPtr Data;
|
|
||||||
public uint BufferLength;
|
|
||||||
public uint BytesRecorded;
|
|
||||||
public nuint User;
|
|
||||||
public uint Flags;
|
|
||||||
public uint Loops;
|
|
||||||
public IntPtr Next;
|
|
||||||
public nuint Reserved;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutOpen")]
|
|
||||||
private static extern uint WaveOutOpen(
|
|
||||||
out IntPtr device,
|
|
||||||
uint deviceId,
|
|
||||||
ref WaveFormat format,
|
|
||||||
IntPtr callback,
|
|
||||||
IntPtr instance,
|
|
||||||
uint flags);
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutPrepareHeader")]
|
|
||||||
private static extern uint WaveOutPrepareHeader(IntPtr device, IntPtr header, uint headerSize);
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutWrite")]
|
|
||||||
private static extern uint WaveOutWrite(IntPtr device, IntPtr header, uint headerSize);
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutUnprepareHeader")]
|
|
||||||
private static extern uint WaveOutUnprepareHeader(IntPtr device, IntPtr header, uint headerSize);
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutReset")]
|
|
||||||
private static extern uint WaveOutReset(IntPtr device);
|
|
||||||
|
|
||||||
[DllImport("winmm.dll", EntryPoint = "waveOutClose")]
|
|
||||||
private static extern uint WaveOutClose(IntPtr device);
|
|
||||||
}
|
|
||||||
@@ -2531,17 +2531,6 @@ public static class KernelMemoryCompatExports
|
|||||||
if (fixedMapping && requestedAddress != 0)
|
if (fixedMapping && requestedAddress != 0)
|
||||||
{
|
{
|
||||||
mappedAddress = requestedAddress;
|
mappedAddress = requestedAddress;
|
||||||
reserved = IsGuestRangeBacked(ctx, requestedAddress, length);
|
|
||||||
if (!reserved)
|
|
||||||
{
|
|
||||||
TryReserveExactGuestVirtualRange(ctx, requestedAddress, length, protection);
|
|
||||||
reserved = IsGuestRangeBacked(ctx, requestedAddress, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reserved)
|
|
||||||
{
|
|
||||||
mappedAddress = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2552,7 +2541,7 @@ public static class KernelMemoryCompatExports
|
|||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}");
|
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}");
|
||||||
}
|
}
|
||||||
if (!reserved && !fixedMapping)
|
if (!reserved)
|
||||||
{
|
{
|
||||||
if (mappedAddress == 0)
|
if (mappedAddress == 0)
|
||||||
{
|
{
|
||||||
@@ -2636,18 +2625,12 @@ public static class KernelMemoryCompatExports
|
|||||||
if (fixedMapping && requestedAddress != 0)
|
if (fixedMapping && requestedAddress != 0)
|
||||||
{
|
{
|
||||||
mappedAddress = requestedAddress;
|
mappedAddress = requestedAddress;
|
||||||
if (!IsGuestRangeBacked(ctx, requestedAddress, length))
|
|
||||||
{
|
|
||||||
TryReserveExactGuestVirtualRange(ctx, requestedAddress, length, protection);
|
|
||||||
if (!IsGuestRangeBacked(ctx, requestedAddress, length))
|
|
||||||
{
|
|
||||||
mappedAddress = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, OrbisPageSize, out mappedAddress))
|
else if (!TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, OrbisPageSize, out mappedAddress))
|
||||||
{
|
{
|
||||||
mappedAddress = AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
mappedAddress = requestedAddress != 0 && fixedMapping
|
||||||
|
? requestedAddress
|
||||||
|
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldTraceDirectMemory())
|
if (ShouldTraceDirectMemory())
|
||||||
@@ -4012,37 +3995,6 @@ public static class KernelMemoryCompatExports
|
|||||||
out mappedAddress);
|
out mappedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryReserveExactGuestVirtualRange(
|
|
||||||
CpuContext ctx,
|
|
||||||
ulong desiredAddress,
|
|
||||||
ulong length,
|
|
||||||
int protection)
|
|
||||||
{
|
|
||||||
var executable = (protection & OrbisProtCpuExec) != 0;
|
|
||||||
return KernelVirtualRangeAllocator.TryReserve(
|
|
||||||
ctx,
|
|
||||||
desiredAddress,
|
|
||||||
length,
|
|
||||||
executable,
|
|
||||||
alignment: 0,
|
|
||||||
allowSearch: false,
|
|
||||||
allowAllocateAtAlternative: false,
|
|
||||||
"reserve fixed range",
|
|
||||||
out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsGuestRangeBacked(CpuContext ctx, ulong address, ulong length)
|
|
||||||
{
|
|
||||||
if (address == 0 || length == 0 || ulong.MaxValue - address < length - 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<byte> probe = stackalloc byte[1];
|
|
||||||
return ctx.Memory.TryRead(address, probe) &&
|
|
||||||
ctx.Memory.TryRead(address + length - 1, probe);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsMappedGuestRangeAvailable(
|
private static bool IsMappedGuestRangeAvailable(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong address,
|
ulong address,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
private const int DefaultSchedPriority = DefaultThreadPriority;
|
private const int DefaultSchedPriority = DefaultThreadPriority;
|
||||||
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
||||||
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
||||||
private const ulong SyntheticRwlockAttrHandleBase = 0x00006005_0000_0000;
|
|
||||||
|
|
||||||
private static readonly object _stateGate = new();
|
private static readonly object _stateGate = new();
|
||||||
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
|
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
|
||||||
@@ -32,7 +31,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
private static int _nextTlsKey = 1;
|
private static int _nextTlsKey = 1;
|
||||||
private static long _nextSyntheticRwlockHandleId = 1;
|
private static long _nextSyntheticRwlockHandleId = 1;
|
||||||
private static long _nextSyntheticPthreadAttrHandleId = 1;
|
private static long _nextSyntheticPthreadAttrHandleId = 1;
|
||||||
private static long _nextSyntheticRwlockAttrHandleId = 1;
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<int, ulong>> _threadLocalSpecific = new();
|
private static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<int, ulong>> _threadLocalSpecific = new();
|
||||||
|
|
||||||
@@ -841,37 +839,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return PthreadAttrSetstacksize(ctx);
|
return PthreadAttrSetstacksize(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
|
||||||
Nid = "Bvn74vj6oLo",
|
|
||||||
ExportName = "scePthreadAttrSetstack",
|
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
|
||||||
LibraryName = "libKernel")]
|
|
||||||
public static int PthreadAttrSetstack(CpuContext ctx)
|
|
||||||
{
|
|
||||||
var attrAddress = ctx[CpuRegister.Rdi];
|
|
||||||
var stackAddress = ctx[CpuRegister.Rsi];
|
|
||||||
var stackSize = ctx[CpuRegister.Rdx];
|
|
||||||
if (attrAddress == 0 || stackAddress == 0 || stackSize == 0)
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
|
||||||
lock (_stateGate)
|
|
||||||
{
|
|
||||||
var state = GetOrCreateAttrStateLocked(resolvedAddress);
|
|
||||||
var updated = state with { StackAddress = stackAddress, StackSize = stackSize };
|
|
||||||
_attrStates[resolvedAddress] = updated;
|
|
||||||
if (resolvedAddress != attrAddress)
|
|
||||||
{
|
|
||||||
_attrStates[attrAddress] = updated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "6ULAa0fq4jA",
|
Nid = "6ULAa0fq4jA",
|
||||||
ExportName = "scePthreadRwlockInit",
|
ExportName = "scePthreadRwlockInit",
|
||||||
@@ -1049,47 +1016,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int PosixPthreadRwlockUnlock(CpuContext ctx) => PthreadRwlockUnlock(ctx);
|
public static int PosixPthreadRwlockUnlock(CpuContext ctx) => PthreadRwlockUnlock(ctx);
|
||||||
|
|
||||||
[SysAbiExport(
|
|
||||||
Nid = "yOfGg-I1ZII",
|
|
||||||
ExportName = "scePthreadRwlockattrInit",
|
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
|
||||||
LibraryName = "libKernel")]
|
|
||||||
public static int PthreadRwlockattrInit(CpuContext ctx)
|
|
||||||
{
|
|
||||||
var attrAddress = ctx[CpuRegister.Rdi];
|
|
||||||
if (attrAddress == 0)
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockAttrHandleBase, ref _nextSyntheticRwlockAttrHandleId);
|
|
||||||
if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, attrAddress, syntheticHandle))
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SysAbiExport(
|
|
||||||
Nid = "i2ifZ3fS2fo",
|
|
||||||
ExportName = "scePthreadRwlockattrDestroy",
|
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
|
||||||
LibraryName = "libKernel")]
|
|
||||||
public static int PthreadRwlockattrDestroy(CpuContext ctx)
|
|
||||||
{
|
|
||||||
var attrAddress = ctx[CpuRegister.Rdi];
|
|
||||||
if (attrAddress == 0)
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, attrAddress, 0);
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "mqULNdimTn0",
|
Nid = "mqULNdimTn0",
|
||||||
ExportName = "pthread_key_create",
|
ExportName = "pthread_key_create",
|
||||||
@@ -1119,7 +1045,7 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = unchecked((uint)key);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user