Merge branch 'master' into 20-empty-nca-lockup

This commit is contained in:
Shyanne
2025-12-28 14:16:39 -05:00
88 changed files with 1369 additions and 1311 deletions

View File

@@ -1,43 +1,91 @@
using Gommon;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Memory;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Ryujinx.HLE.Debugger
{
public partial class Debugger
{
private sealed record RcmdEntry(string[] Names, Func<Debugger, string, string> Handler, string[] HelpLines);
// Atmosphere/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp
private static readonly string[] _memoryStateNames =
{
"----- Free -----",
"Io ",
"Static ",
"Code ",
"CodeData ",
"Normal ",
"Shared ",
"Alias ",
"AliasCode ",
"AliasCodeData ",
"Ipc ",
"Stack ",
"ThreadLocal ",
"Transfered ",
"SharedTransfered",
"SharedCode ",
"Inaccessible ",
"NonSecureIpc ",
"NonDeviceIpc ",
"Kernel ",
"GeneratedCode ",
"CodeOut ",
"Coverage ",
};
static Debugger()
{
_rcmdDelegates.Add(["help"],
_ => _rcmdDelegates.Keys
.Where(x => !x[0].Equals("help"))
.Select(x => x.JoinToString('\n'))
.JoinToString('\n') + '\n'
);
_rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo());
_rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace());
_rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters());
_rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump());
_rcmdDelegates.Add(new RcmdEntry(
["help"],
(dbgr, _) => _rcmdDelegates
.Where(entry => entry.HelpLines.Length > 0)
.SelectMany(entry => entry.HelpLines)
.JoinToString('\n') + '\n',
Array.Empty<string>()));
_rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"]));
_rcmdDelegates.Add(new RcmdEntry(["backtrace", "bt"], (dbgr, _) => dbgr.GetStackTrace(), ["backtrace", "bt"]));
_rcmdDelegates.Add(new RcmdEntry(["registers", "reg"], (dbgr, _) => dbgr.GetRegisters(), ["registers", "reg"]));
_rcmdDelegates.Add(new RcmdEntry(["minidump"], (dbgr, _) => dbgr.GetMinidump(), ["minidump"]));
_rcmdDelegates.Add(new RcmdEntry(["get mappings"], (dbgr, args) => dbgr.GetMemoryMappings(args), ["get mappings", "get mappings {address}"]));
_rcmdDelegates.Add(new RcmdEntry(["get mapping"], (dbgr, args) => dbgr.GetMemoryMapping(args), ["get mapping {address}"]));
}
private static readonly Dictionary<string[], Func<Debugger, string>> _rcmdDelegates = new();
private static readonly List<RcmdEntry> _rcmdDelegates = [];
public static Func<Debugger, string> FindRcmdDelegate(string command)
public static string CallRcmdDelegate(Debugger debugger, string command)
{
Func<Debugger, string> searchResult = _ => $"Unknown command: {command}\n";
string originalCommand = command ?? string.Empty;
string trimmedCommand = originalCommand.Trim();
foreach ((string[] names, Func<Debugger, string> dlg) in _rcmdDelegates)
foreach (RcmdEntry entry in _rcmdDelegates)
{
if (names.ContainsIgnoreCase(command.Trim()))
foreach (string name in entry.Names)
{
searchResult = dlg;
break;
if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return entry.Handler(debugger, string.Empty);
}
if (trimmedCommand.Length > name.Length &&
trimmedCommand.StartsWith(name, StringComparison.OrdinalIgnoreCase) &&
char.IsWhiteSpace(trimmedCommand[name.Length]))
{
string arguments = trimmedCommand[name.Length..].TrimStart();
return entry.Handler(debugger, arguments);
}
}
}
return searchResult;
return $"Unknown command: {originalCommand}\n";
}
public string GetStackTrace()
@@ -86,5 +134,181 @@ namespace Ryujinx.HLE.Debugger
return $"Error getting process info: {e.Message}\n";
}
}
public string GetMemoryMappings(string arguments)
{
if (Process?.MemoryManager is not { } memoryManager)
{
return "No application process found\n";
}
string trimmedArgs = arguments?.Trim() ?? string.Empty;
ulong startAddress = 0;
if (!string.IsNullOrEmpty(trimmedArgs))
{
if (!TryParseAddressArgument(trimmedArgs, out startAddress))
{
return $"Invalid address: {trimmedArgs}\n";
}
}
ulong requestedAddress = startAddress;
ulong currentAddress = Math.Max(requestedAddress, memoryManager.AddrSpaceStart);
StringBuilder sb = new();
sb.AppendLine($"Mappings (starting from 0x{requestedAddress:x10}):");
if (currentAddress >= memoryManager.AddrSpaceEnd)
{
return sb.ToString();
}
while (currentAddress < memoryManager.AddrSpaceEnd)
{
KMemoryInfo info = memoryManager.QueryMemory(currentAddress);
try
{
if (info.Size == 0 || info.Address >= memoryManager.AddrSpaceEnd)
{
break;
}
sb.AppendLine(FormatMapping(info, indent: true));
if (info.Address > ulong.MaxValue - info.Size)
{
break;
}
ulong nextAddress = info.Address + info.Size;
if (nextAddress <= currentAddress)
{
break;
}
currentAddress = nextAddress;
}
finally
{
KMemoryInfo.Pool.Release(info);
}
}
return sb.ToString();
}
public string GetMemoryMapping(string arguments)
{
if (Process?.MemoryManager is not { } memoryManager)
{
return "No application process found\n";
}
string trimmedArgs = arguments?.Trim() ?? string.Empty;
if (string.IsNullOrEmpty(trimmedArgs))
{
return "Missing address argument for `get mapping`\n";
}
if (!TryParseAddressArgument(trimmedArgs, out ulong address))
{
return $"Invalid address: {trimmedArgs}\n";
}
KMemoryInfo info = memoryManager.QueryMemory(address);
try
{
return FormatMapping(info, indent: false) + '\n';
}
finally
{
KMemoryInfo.Pool.Release(info);
}
}
private static string FormatMapping(KMemoryInfo info, bool indent)
{
ulong endAddress;
if (info.Size == 0)
{
endAddress = info.Address;
}
else if (info.Address > ulong.MaxValue - (info.Size - 1))
{
endAddress = ulong.MaxValue;
}
else
{
endAddress = info.Address + info.Size - 1;
}
string prefix = indent ? " " : string.Empty;
return $"{prefix}0x{info.Address:x10} - 0x{endAddress:x10} {GetPermissionString(info)} {GetMemoryStateName(info.State)} {GetAttributeFlags(info)} [{info.IpcRefCount}, {info.DeviceRefCount}]";
}
private static string GetPermissionString(KMemoryInfo info)
{
if ((info.State & MemoryState.UserMask) == MemoryState.Unmapped)
{
return " ";
}
return info.Permission switch
{
KMemoryPermission.ReadAndExecute => "r-x",
KMemoryPermission.Read => "r--",
KMemoryPermission.ReadAndWrite => "rw-",
_ => "---"
};
}
private static string GetMemoryStateName(MemoryState state)
{
int stateIndex = (int)(state & MemoryState.UserMask);
if ((uint)stateIndex < _memoryStateNames.Length)
{
return _memoryStateNames[stateIndex];
}
return "Unknown ";
}
private static bool TryParseAddressArgument(string text, out ulong value)
{
value = 0;
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
string trimmed = text.Trim();
if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
trimmed = trimmed[2..];
}
if (trimmed.Length == 0)
{
return false;
}
return ulong.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
}
private static string GetAttributeFlags(KMemoryInfo info)
{
char locked = info.Attribute.HasFlag(MemoryAttribute.Borrowed) ? 'L' : '-';
char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-';
char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-';
char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-';
return $"{locked}{ipc}{device}{uncached}";
}
}
}

View File

@@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
string command = Helpers.FromHex(hexCommand);
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
Func<Debugger, string> rcmd = Debugger.FindRcmdDelegate(command);
Processor.ReplyHex(rcmd(Debugger));
string response = Debugger.CallRcmdDelegate(Debugger, command);
Processor.ReplyHex(response);
}
catch (Exception e)
{

View File

@@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem
{
if (Directory.Exists(keysSource))
{
foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
foreach (string filePath in keyPaths)
{
VerifyKeysFile(filePath);
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
try
{
VerifyKeysFile(filePath);
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
if (File.Exists(destPath))
File.Delete(destPath);
File.Copy(filePath, destPath, true);
}
return;
@@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is ".keys")
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
try
{
VerifyKeysFile(keysSource);
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
}
else
}
catch
{
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
}
string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
@@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem
public static void VerifyKeysFile(string filePath)
{
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
if (File.Exists(filePath))
{
@@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem
string fileName = Path.GetFileName(filePath);
string[] lines = File.ReadAllLines(filePath);
bool verified;
switch (fileName)
bool verified = fileName switch
{
case "prod.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "title.keys":
verified = VerifyKeys(lines, titlePattern);
break;
case "console.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "dev.keys":
verified = VerifyKeys(lines, genericPattern);
break;
default:
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
}
"prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
"title.keys" => VerifyKeys(lines, titlePattern),
_ => throw new FormatException(
$"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
};
if (!verified)
{

View File

@@ -2,6 +2,7 @@ using Microsoft.IO;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
{
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
BinaryReader reader = new(ms);
@@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
}
ObjectIds = [];
MemoryStreamManager.Shared.ReleaseStream(ms);
}
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)

View File

@@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
}
public KBufferDescriptorTable Clear()
{
_sendBufferDescriptors.Clear();
_receiveBufferDescriptors.Clear();
_exchangeBufferDescriptors.Clear();
return this;
}
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
{
return Add(_sendBufferDescriptors, src, dst, size, state);

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
@@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize);
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
KernelContext.CriticalSection.Enter();
@@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
KernelContext.CriticalSection.Enter();

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KServerSession : KSynchronizationObject
{
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
private static readonly MemoryState[] _ipcMemoryStates =
[
MemoryState.IpcBuffer3,
@@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
KernelContext.CriticalSection.Leave();
WakeClientThread(request, clientResult);
RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CloseAllHandles(clientMsg, serverHeader, clientProcess);
FinishRequest(request, clientResult);
RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
// Unmap buffers from server.
FinishRequest(request, clientResult);
RequestPool.Release(request);
return serverResult;
}
@@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
{
FinishRequest(request, KernelResult.PortRemoteClosed);
RequestPool.Release(request);
}
}
@@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
}
RequestPool.Release(request);
}
WakeServerThreads(KernelResult.PortRemoteClosed);

View File

@@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KSessionRequest
{
public KBufferDescriptorTable BufferDescriptorTable { get; }
public KBufferDescriptorTable BufferDescriptorTable { get; private set; }
public KThread ClientThread { get; }
public KThread ClientThread { get; private set; }
public KProcess ServerProcess { get; set; }
public KWritableEvent AsyncEvent { get; }
public KWritableEvent AsyncEvent { get; private set; }
public ulong CustomCmdBuffAddr { get; }
public ulong CustomCmdBuffSize { get; }
public ulong CustomCmdBuffAddr { get; private set; }
public ulong CustomCmdBuffSize { get; private set; }
public KSessionRequest(
public KSessionRequest Set(
KThread clientThread,
ulong customCmdBuffAddr,
ulong customCmdBuffSize,
@@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CustomCmdBuffSize = customCmdBuffSize;
AsyncEvent = asyncEvent;
BufferDescriptorTable = new KBufferDescriptorTable();
BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
return this;
}
}
}

View File

@@ -1,10 +1,8 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter
{
private const int HasListenersMask = 0x40000000;
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
private readonly KernelContext _context;
private readonly List<KThread> _condVarThreads;
private readonly List<KThread> _arbiterThreads;
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
private readonly ByDynamicPriority _byDynamicPriority;
public KAddressArbiter(KernelContext context)
{
@@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_condVarThreads = [];
_arbiterThreads = [];
_byDynamicPriority = new ByDynamicPriority();
}
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
@@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = mutexAddress;
currentThread.ThreadHandleForUserMutex = threadHandle;
currentThread.CondVarAddress = condVarAddress;
_condVarThreads.Add(currentThread);
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
{
int i = 0;
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_condVarThreads.Add(condVarAddress, [currentThread]);
}
if (timeout != 0)
{
@@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
_condVarThreads.Remove(currentThread);
_condVarThreads[condVarAddress].Remove(currentThread);
_context.CriticalSection.Leave();
@@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
_context.CriticalSection.Enter();
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
int validThreads = 0;
_condVarThreads.TryGetValue(address, out List<KThread> threads);
if (threads is not null && threads.Count > 0)
{
return thread.CondVarAddress == address;
validThreads = WakeThreads(threads, count, TryAcquireMutex);
}
int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
if (validThreads == 0)
{
KernelTransfer.KernelToUser(address, 0);
@@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
int i = 0;
_arbiterThreads.Add(currentThread);
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
_arbiterThreads.Remove(currentThread);
_arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
int i = 0;
_arbiterThreads.Add(currentThread);
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
_arbiterThreads.Remove(currentThread);
_arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// or equal to the Count of threads to be signaled, or Count is zero
// or negative. It is incremented if there are no threads waiting.
int waitingCount = 0;
foreach (KThread thread in _arbiterThreads)
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
if (thread.MutexAddress == address &&
++waitingCount >= count)
{
break;
}
waitingCount = threads.Count;
}
if (waitingCount > 0)
{
@@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false;
}
static bool ArbiterThreadPredecate(KThread thread, ulong address)
{
return thread.MutexAddress == address;
}
_arbiterThreads.TryGetValue(address, out List<KThread> threads);
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
if (threads is not null && threads.Count > 0)
{
WakeThreads(threads, count, RemoveArbiterThread);
}
}
private static int WakeThreads(
List<KThread> threads,
int count,
Action<KThread> removeCallback,
Func<KThread, ulong, bool> predicate,
ulong address = 0)
Action<KThread> removeCallback)
{
KThread[] candidates = _threadArrayPool.Allocate();
if (candidates.Length < threads.Count)
{
Array.Resize(ref candidates, threads.Count);
}
int validCount = 0;
for (int i = 0; i < threads.Count; i++)
{
if (predicate(threads[i], address))
{
candidates[validCount++] = threads[i];
}
}
Span<KThread> candidatesSpan = candidates.AsSpan(..validCount);
candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
if (count > 0)
{
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
}
foreach (KThread thread in candidatesSpan)
for (int i = 0; i < validCount; i++)
{
KThread thread = threads[i];
removeCallback(thread);
threads.Remove(thread);
}
_threadArrayPool.Release(candidates);
threads.RemoveRange(0, validCount);
return validCount;
}
private class ByDynamicPriority : IComparer<KThread>
{
public int Compare(KThread x, KThread y)
{
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
}
}
}
}

View File

@@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KSynchronizationObject SignaledObj { get; set; }
public ulong CondVarAddress { get; set; }
private ulong _entrypoint;
private ThreadStart _customThreadStart;
private bool _forcedUnschedulable;

View File

@@ -1,9 +1,8 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
[JsonConverter(typeof(JsonStringEnumConverter<AccountState>))]
public enum AccountState
{
Closed,

View File

@@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.InvalidParameters;
}
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
context.Device.UIHandler.TakeScreenshot();
return ResultCode.Success;
}

View File

@@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
private int _selfId;
private bool _isDomain;
// cache array so we don't recreate it all the time
private object[] _parameters = [null];
public IpcService(ServerBase server = null, bool registerTipc = false)
{
Stopwatch sw = Stopwatch.StartNew();
@@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
result = (ResultCode)processRequest.Invoke(service, [context]);
_parameters[0] = context;
result = (ResultCode)processRequest.Invoke(service, _parameters);
}
else
{
@@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
result = (ResultCode)processRequest.Invoke(this, [context]);
_parameters[0] = context;
result = (ResultCode)processRequest.Invoke(this, _parameters);
}
else
{

View File

@@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
// TODO: This should call set:sys::GetDebugModeFlag
private readonly bool _debugModeEnabled = false;
private byte[] _ioctl2Buffer = [];
private byte[] _ioctlArgumentBuffer = [];
private byte[] _ioctl3Buffer = [];
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
{
@@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
{
arguments = new byte[inputDataSize];
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
context.Memory.Read(inputDataPosition, arguments);
}
else
{
arguments = arguments.ToArray();
}
}
else if (isWrite)
{
byte[] outputData = new byte[outputDataSize];
arguments = new Span<byte>(outputData);
if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
}
else
{
byte[] temp = new byte[inputDataSize];
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
{
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
context.Memory.Read(inputDataPosition, temp);
arguments = new Span<byte>(temp);
context.Memory.Read(inputDataPosition, arguments);
}
}
return NvResult.Success;
@@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
@@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineInBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
{
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
inlineInBufferSpan = inlineInBuffer;
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
{
Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
}
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
if (inlineInBuffer is not null)
{
_byteArrayPool.Return(inlineInBuffer);
}
}
context.ResponseData.Write((uint)errorCode);
@@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineOutBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
{
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
inlineOutBufferSpan = inlineOutBuffer;
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
{
Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
}
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
}
}
}
if (inlineOutBuffer is not null)
{
_byteArrayPool.Return(inlineOutBuffer);
}
}
context.ResponseData.Write((uint)errorCode);

View File

@@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray();
using RecyclableMemoryStream responseStream = response.GetStreamTipc();
RecyclableMemoryStream responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
else
{
@@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication)
{
using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
return shouldReply;

View File

@@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI
/// Displays the player select dialog and returns the selected profile.
/// </summary>
UserProfile ShowPlayerSelectDialog();
/// <summary>
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
/// </summary>
void TakeScreenshot();
}
}