Move solution and projects to src

This commit is contained in:
TSR Berry 2023-04-08 01:22:00 +02:00 committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,100 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Common
{
public sealed class AsyncWorkQueue<T> : IDisposable
{
private readonly Thread _workerThread;
private readonly CancellationTokenSource _cts;
private readonly Action<T> _workerAction;
private readonly BlockingCollection<T> _queue;
public bool IsCancellationRequested => _cts.IsCancellationRequested;
public AsyncWorkQueue(Action<T> callback, string name = null) : this(callback, name, new BlockingCollection<T>())
{
}
public AsyncWorkQueue(Action<T> callback, string name, BlockingCollection<T> collection)
{
_cts = new CancellationTokenSource();
_queue = collection;
_workerAction = callback;
_workerThread = new Thread(DoWork) { Name = name };
_workerThread.IsBackground = true;
_workerThread.Start();
}
private void DoWork()
{
try
{
foreach (var item in _queue.GetConsumingEnumerable(_cts.Token))
{
_workerAction(item);
}
}
catch (OperationCanceledException)
{
}
}
public void Cancel()
{
_cts.Cancel();
}
public void CancelAfter(int millisecondsDelay)
{
_cts.CancelAfter(millisecondsDelay);
}
public void CancelAfter(TimeSpan delay)
{
_cts.CancelAfter(delay);
}
public void Add(T workItem)
{
_queue.Add(workItem);
}
public void Add(T workItem, CancellationToken cancellationToken)
{
_queue.Add(workItem, cancellationToken);
}
public bool TryAdd(T workItem)
{
return _queue.TryAdd(workItem);
}
public bool TryAdd(T workItem, int millisecondsDelay)
{
return _queue.TryAdd(workItem, millisecondsDelay);
}
public bool TryAdd(T workItem, int millisecondsDelay, CancellationToken cancellationToken)
{
return _queue.TryAdd(workItem, millisecondsDelay, cancellationToken);
}
public bool TryAdd(T workItem, TimeSpan timeout)
{
return _queue.TryAdd(workItem, timeout);
}
public void Dispose()
{
_queue.CompleteAdding();
_cts.Cancel();
_workerThread.Join();
_queue.Dispose();
_cts.Dispose();
}
}
}

View file

@ -0,0 +1,499 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Common.Collections
{
/// <summary>
/// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges.
/// </summary>
/// <typeparam name="K">Key</typeparam>
/// <typeparam name="V">Value</typeparam>
public class IntervalTree<K, V> : IntrusiveRedBlackTreeImpl<IntervalTreeNode<K, V>> where K : IComparable<K>
{
private const int ArrayGrowthSize = 32;
#region Public Methods
/// <summary>
/// Gets the values of the interval whose key is <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node value to get</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <returns>Number of values found</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
public int Get(K key, ref V[] overlaps)
{
ArgumentNullException.ThrowIfNull(key);
IntervalTreeNode<K, V> node = GetNode(key);
if (node == null)
{
return 0;
}
if (node.Values.Count > overlaps.Length)
{
Array.Resize(ref overlaps, node.Values.Count);
}
int overlapsCount = 0;
foreach (RangeNode<K, V> value in node.Values)
{
overlaps[overlapsCount++] = value.Value;
}
return overlapsCount;
}
/// <summary>
/// Returns the values of the intervals whose start and end keys overlap the given range.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Index to start writing results into the array. Defaults to 0</param>
/// <returns>Number of values found</returns>
/// <exception cref="ArgumentNullException"><paramref name="start"/> or <paramref name="end"/> is null</exception>
public int Get(K start, K end, ref V[] overlaps, int overlapCount = 0)
{
ArgumentNullException.ThrowIfNull(start);
ArgumentNullException.ThrowIfNull(end);
GetValues(Root, start, end, ref overlaps, ref overlapCount);
return overlapCount;
}
/// <summary>
/// Adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to add</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to add</param>
/// <exception cref="ArgumentNullException"><paramref name="start"/>, <paramref name="end"/> or <paramref name="value"/> are null</exception>
public void Add(K start, K end, V value)
{
ArgumentNullException.ThrowIfNull(start);
ArgumentNullException.ThrowIfNull(end);
ArgumentNullException.ThrowIfNull(value);
Insert(start, end, value);
}
/// <summary>
/// Removes the given <paramref name="value"/> from the tree, searching for it with <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node to remove</param>
/// <param name="value">Value to remove</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
/// <returns>Number of deleted values</returns>
public int Remove(K key, V value)
{
ArgumentNullException.ThrowIfNull(key);
int removed = Delete(key, value);
Count -= removed;
return removed;
}
/// <summary>
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
/// </summary>
/// <returns>A list of all RangeNodes sorted by Key Order</returns>
public List<RangeNode<K, V>> AsList()
{
List<RangeNode<K, V>> list = new List<RangeNode<K, V>>();
AddToList(Root, list);
return list;
}
#endregion
#region Private Methods (BST)
/// <summary>
/// Adds all RangeNodes that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
/// </summary>
/// <param name="node">The node to search for RangeNodes within</param>
/// <param name="list">The list to add RangeNodes to</param>
private void AddToList(IntervalTreeNode<K, V> node, List<RangeNode<K, V>> list)
{
if (node == null)
{
return;
}
AddToList(node.Left, list);
list.AddRange(node.Values);
AddToList(node.Right, list);
}
/// <summary>
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
/// </summary>
/// <param name="key">Key of the node to get</param>
/// <returns>Node reference in the tree</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
private IntervalTreeNode<K, V> GetNode(K key)
{
ArgumentNullException.ThrowIfNull(key);
IntervalTreeNode<K, V> node = Root;
while (node != null)
{
int cmp = key.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
return null;
}
/// <summary>
/// Retrieve all values that overlap the given start and end keys.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Overlaps count to update</param>
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref V[] overlaps, ref int overlapCount)
{
if (node == null || start.CompareTo(node.Max) >= 0)
{
return;
}
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
bool endsOnRight = end.CompareTo(node.Start) > 0;
if (endsOnRight)
{
if (start.CompareTo(node.End) < 0)
{
// Contains this node. Add overlaps to list.
foreach (RangeNode<K,V> overlap in node.Values)
{
if (start.CompareTo(overlap.End) < 0)
{
if (overlaps.Length >= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}
overlaps[overlapCount++] = overlap.Value;
}
}
}
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
}
}
/// <summary>
/// Inserts a new node into the tree with a given <paramref name="start"/>, <paramref name="end"/> and <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to insert</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to insert</param>
private void Insert(K start, K end, V value)
{
IntervalTreeNode<K, V> newNode = BSTInsert(start, end, value);
RestoreBalanceAfterInsertion(newNode);
}
/// <summary>
/// Propagate an increase in max value starting at the given node, heading up the tree.
/// This should only be called if the max increases - not for rebalancing or removals.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateIncrease(IntervalTreeNode<K, V> node)
{
K max = node.Max;
IntervalTreeNode<K, V> ptr = node;
while ((ptr = ptr.Parent) != null)
{
if (max.CompareTo(ptr.Max) > 0)
{
ptr.Max = max;
}
else
{
break;
}
}
}
/// <summary>
/// Propagate recalculating max value starting at the given node, heading up the tree.
/// This fully recalculates the max value from all children when there is potential for it to decrease.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateFull(IntervalTreeNode<K, V> node)
{
IntervalTreeNode<K, V> ptr = node;
do
{
K max = ptr.End;
if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0)
{
max = ptr.Left.Max;
}
if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0)
{
max = ptr.Right.Max;
}
ptr.Max = max;
} while ((ptr = ptr.Parent) != null);
}
/// <summary>
/// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key.
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="start"/>, and all children in the right subtree are greater than <paramref name="start"/>.
/// Each node can contain multiple values, and has an end address which is the maximum of all those values.
/// Post insertion, the "max" value of the node and all parents are updated.
/// </summary>
/// <param name="start">Start of the range to insert</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to insert</param>
/// <returns>The inserted Node</returns>
private IntervalTreeNode<K, V> BSTInsert(K start, K end, V value)
{
IntervalTreeNode<K, V> parent = null;
IntervalTreeNode<K, V> node = Root;
while (node != null)
{
parent = node;
int cmp = start.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
node.Values.Add(new RangeNode<K, V>(start, end, value));
if (end.CompareTo(node.End) > 0)
{
node.End = end;
if (end.CompareTo(node.Max) > 0)
{
node.Max = end;
PropagateIncrease(node);
}
}
Count++;
return node;
}
}
IntervalTreeNode<K, V> newNode = new IntervalTreeNode<K, V>(start, end, value, parent);
if (newNode.Parent == null)
{
Root = newNode;
}
else if (start.CompareTo(parent.Start) < 0)
{
parent.Left = newNode;
}
else
{
parent.Right = newNode;
}
PropagateIncrease(newNode);
Count++;
return newNode;
}
/// <summary>
/// Removes instances of <paramref name="value"> from the dictionary after searching for it with <paramref name="key">.
/// </summary>
/// <param name="key">Key to search for</param>
/// <param name="value">Value to delete</param>
/// <returns>Number of deleted values</returns>
private int Delete(K key, V value)
{
IntervalTreeNode<K, V> nodeToDelete = GetNode(key);
if (nodeToDelete == null)
{
return 0;
}
int removed = nodeToDelete.Values.RemoveAll(node => node.Value.Equals(value));
if (nodeToDelete.Values.Count > 0)
{
if (removed > 0)
{
nodeToDelete.End = nodeToDelete.Values.Max(node => node.End);
// Recalculate max from children and new end.
PropagateFull(nodeToDelete);
}
return removed;
}
IntervalTreeNode<K, V> replacementNode;
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
{
replacementNode = nodeToDelete;
}
else
{
replacementNode = PredecessorOf(nodeToDelete);
}
IntervalTreeNode<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{
Root = tmp;
}
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
{
ParentOf(replacementNode).Left = tmp;
}
else
{
ParentOf(replacementNode).Right = tmp;
}
if (replacementNode != nodeToDelete)
{
nodeToDelete.Start = replacementNode.Start;
nodeToDelete.Values = replacementNode.Values;
nodeToDelete.End = replacementNode.End;
nodeToDelete.Max = replacementNode.Max;
}
PropagateFull(replacementNode);
if (tmp != null && ColorOf(replacementNode) == Black)
{
RestoreBalanceAfterRemoval(tmp);
}
return removed;
}
#endregion
protected override void RotateLeft(IntervalTreeNode<K, V> node)
{
if (node != null)
{
base.RotateLeft(node);
PropagateFull(node);
}
}
protected override void RotateRight(IntervalTreeNode<K, V> node)
{
if (node != null)
{
base.RotateRight(node);
PropagateFull(node);
}
}
public bool ContainsKey(K key)
{
ArgumentNullException.ThrowIfNull(key);
return GetNode(key) != null;
}
}
/// <summary>
/// Represents a value and its start and end keys.
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
public readonly struct RangeNode<K, V>
{
public readonly K Start;
public readonly K End;
public readonly V Value;
public RangeNode(K start, K end, V value)
{
Start = start;
End = end;
Value = value;
}
}
/// <summary>
/// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V.
/// </summary>
/// <typeparam name="K">Key type of the node</typeparam>
/// <typeparam name="V">Value type of the node</typeparam>
public class IntervalTreeNode<K, V> : IntrusiveRedBlackTreeNode<IntervalTreeNode<K, V>>
{
/// <summary>
/// The start of the range.
/// </summary>
internal K Start;
/// <summary>
/// The end of the range - maximum of all in the Values list.
/// </summary>
internal K End;
/// <summary>
/// The maximum end value of this node and all its children.
/// </summary>
internal K Max;
/// <summary>
/// Values contained on the node that shares a common Start value.
/// </summary>
internal List<RangeNode<K, V>> Values;
internal IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
{
Start = start;
End = end;
Max = end;
Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
Parent = parent;
}
}
}

View file

@ -0,0 +1,285 @@
using System;
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key.
/// </summary>
/// <typeparam name="T">Derived node type</typeparam>
public class IntrusiveRedBlackTree<T> : IntrusiveRedBlackTreeImpl<T> where T : IntrusiveRedBlackTreeNode<T>, IComparable<T>
{
#region Public Methods
/// <summary>
/// Adds a new node into the tree.
/// </summary>
/// <param name="node">Node to be added</param>
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
public void Add(T node)
{
ArgumentNullException.ThrowIfNull(node);
Insert(node);
}
/// <summary>
/// Removes a node from the tree.
/// </summary>
/// <param name="node">Note to be removed</param>
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
public void Remove(T node)
{
ArgumentNullException.ThrowIfNull(node);
if (Delete(node) != null)
{
Count--;
}
}
/// <summary>
/// Retrieve the node that is considered equal to the specified node by the comparator.
/// </summary>
/// <param name="searchNode">Node to compare with</param>
/// <returns>Node that is equal to <paramref name="searchNode"/></returns>
/// <exception cref="ArgumentNullException"><paramref name="searchNode"/> is null</exception>
public T GetNode(T searchNode)
{
ArgumentNullException.ThrowIfNull(searchNode);
T node = Root;
while (node != null)
{
int cmp = searchNode.CompareTo(node);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
return null;
}
#endregion
#region Private Methods (BST)
/// <summary>
/// Inserts a new node into the tree.
/// </summary>
/// <param name="node">Node to be inserted</param>
private void Insert(T node)
{
T newNode = BSTInsert(node);
RestoreBalanceAfterInsertion(newNode);
}
/// <summary>
/// Insertion Mechanism for a Binary Search Tree (BST).
/// <br></br>
/// Iterates the tree starting from the root and inserts a new node
/// where all children in the left subtree are less than <paramref name="newNode"/>,
/// and all children in the right subtree are greater than <paramref name="newNode"/>.
/// </summary>
/// <param name="newNode">Node to be inserted</param>
/// <returns>The inserted Node</returns>
private T BSTInsert(T newNode)
{
T parent = null;
T node = Root;
while (node != null)
{
parent = node;
int cmp = newNode.CompareTo(node);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
newNode.Parent = parent;
if (parent == null)
{
Root = newNode;
}
else if (newNode.CompareTo(parent) < 0)
{
parent.Left = newNode;
}
else
{
parent.Right = newNode;
}
Count++;
return newNode;
}
/// <summary>
/// Removes <paramref name="nodeToDelete"/> from the tree, if it exists.
/// </summary>
/// <param name="nodeToDelete">Node to be removed</param>
/// <returns>The deleted Node</returns>
private T Delete(T nodeToDelete)
{
if (nodeToDelete == null)
{
return null;
}
T old = nodeToDelete;
T child;
T parent;
bool color;
if (LeftOf(nodeToDelete) == null)
{
child = RightOf(nodeToDelete);
}
else if (RightOf(nodeToDelete) == null)
{
child = LeftOf(nodeToDelete);
}
else
{
T element = Minimum(RightOf(nodeToDelete));
child = RightOf(element);
parent = ParentOf(element);
color = ColorOf(element);
if (child != null)
{
child.Parent = parent;
}
if (parent == null)
{
Root = child;
}
else if (element == LeftOf(parent))
{
parent.Left = child;
}
else
{
parent.Right = child;
}
if (ParentOf(element) == old)
{
parent = element;
}
element.Color = old.Color;
element.Left = old.Left;
element.Right = old.Right;
element.Parent = old.Parent;
if (ParentOf(old) == null)
{
Root = element;
}
else if (old == LeftOf(ParentOf(old)))
{
ParentOf(old).Left = element;
}
else
{
ParentOf(old).Right = element;
}
LeftOf(old).Parent = element;
if (RightOf(old) != null)
{
RightOf(old).Parent = element;
}
if (child != null && color == Black)
{
RestoreBalanceAfterRemoval(child);
}
return old;
}
parent = ParentOf(nodeToDelete);
color = ColorOf(nodeToDelete);
if (child != null)
{
child.Parent = parent;
}
if (parent == null)
{
Root = child;
}
else if (nodeToDelete == LeftOf(parent))
{
parent.Left = child;
}
else
{
parent.Right = child;
}
if (child != null && color == Black)
{
RestoreBalanceAfterRemoval(child);
}
return old;
}
#endregion
}
public static class IntrusiveRedBlackTreeExtensions
{
/// <summary>
/// Retrieve the node that is considered equal to the key by the comparator.
/// </summary>
/// <param name="tree">Tree to search at</param>
/// <param name="key">Key of the node to be found</param>
/// <returns>Node that is equal to <paramref name="key"/></returns>
public static N GetNodeByKey<N, K>(this IntrusiveRedBlackTree<N> tree, K key)
where N : IntrusiveRedBlackTreeNode<N>, IComparable<N>, IComparable<K>
where K : struct
{
N node = tree.RootNode;
while (node != null)
{
int cmp = node.CompareTo(key);
if (cmp < 0)
{
node = node.Right;
}
else if (cmp > 0)
{
node = node.Left;
}
else
{
return node;
}
}
return null;
}
}
}

View file

@ -0,0 +1,354 @@
using System;
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key.
/// </summary>
/// <typeparam name="T">Derived node type</typeparam>
public class IntrusiveRedBlackTreeImpl<T> where T : IntrusiveRedBlackTreeNode<T>
{
protected const bool Black = true;
protected const bool Red = false;
protected T Root = null;
internal T RootNode => Root;
/// <summary>
/// Number of nodes on the tree.
/// </summary>
public int Count { get; protected set; }
/// <summary>
/// Removes all nodes on the tree.
/// </summary>
public void Clear()
{
Root = null;
Count = 0;
}
/// <summary>
/// Finds the node whose key is immediately greater than <paramref name="node"/>.
/// </summary>
/// <param name="node">Node to find the successor of</param>
/// <returns>Successor of <paramref name="node"/></returns>
internal static T SuccessorOf(T node)
{
if (node.Right != null)
{
return Minimum(node.Right);
}
T parent = node.Parent;
while (parent != null && node == parent.Right)
{
node = parent;
parent = parent.Parent;
}
return parent;
}
/// <summary>
/// Finds the node whose key is immediately less than <paramref name="node"/>.
/// </summary>
/// <param name="node">Node to find the predecessor of</param>
/// <returns>Predecessor of <paramref name="node"/></returns>
internal static T PredecessorOf(T node)
{
if (node.Left != null)
{
return Maximum(node.Left);
}
T parent = node.Parent;
while (parent != null && node == parent.Left)
{
node = parent;
parent = parent.Parent;
}
return parent;
}
/// <summary>
/// Returns the node with the largest key where <paramref name="node"/> is considered the root node.
/// </summary>
/// <param name="node">Root node</param>
/// <returns>Node with the maximum key in the tree of <paramref name="node"/></returns>
protected static T Maximum(T node)
{
T tmp = node;
while (tmp.Right != null)
{
tmp = tmp.Right;
}
return tmp;
}
/// <summary>
/// Returns the node with the smallest key where <paramref name="node"/> is considered the root node.
/// </summary>
/// <param name="node">Root node</param>
/// <returns>Node with the minimum key in the tree of <paramref name="node"/></returns>
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
protected static T Minimum(T node)
{
ArgumentNullException.ThrowIfNull(node);
T tmp = node;
while (tmp.Left != null)
{
tmp = tmp.Left;
}
return tmp;
}
protected void RestoreBalanceAfterRemoval(T balanceNode)
{
T ptr = balanceNode;
while (ptr != Root && ColorOf(ptr) == Black)
{
if (ptr == LeftOf(ParentOf(ptr)))
{
T sibling = RightOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateLeft(ParentOf(ptr));
sibling = RightOf(ParentOf(ptr));
}
if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(RightOf(sibling)) == Black)
{
SetColor(LeftOf(sibling), Black);
SetColor(sibling, Red);
RotateRight(sibling);
sibling = RightOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(RightOf(sibling), Black);
RotateLeft(ParentOf(ptr));
ptr = Root;
}
}
else
{
T sibling = LeftOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateRight(ParentOf(ptr));
sibling = LeftOf(ParentOf(ptr));
}
if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(LeftOf(sibling)) == Black)
{
SetColor(RightOf(sibling), Black);
SetColor(sibling, Red);
RotateLeft(sibling);
sibling = LeftOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(LeftOf(sibling), Black);
RotateRight(ParentOf(ptr));
ptr = Root;
}
}
}
SetColor(ptr, Black);
}
protected void RestoreBalanceAfterInsertion(T balanceNode)
{
SetColor(balanceNode, Red);
while (balanceNode != null && balanceNode != Root && ColorOf(ParentOf(balanceNode)) == Red)
{
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
{
T sibling = RightOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == RightOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateLeft(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateRight(ParentOf(ParentOf(balanceNode)));
}
}
else
{
T sibling = LeftOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == LeftOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateRight(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateLeft(ParentOf(ParentOf(balanceNode)));
}
}
}
SetColor(Root, Black);
}
protected virtual void RotateLeft(T node)
{
if (node != null)
{
T right = RightOf(node);
node.Right = LeftOf(right);
if (node.Right != null)
{
node.Right.Parent = node;
}
T nodeParent = ParentOf(node);
right.Parent = nodeParent;
if (nodeParent == null)
{
Root = right;
}
else if (node == LeftOf(nodeParent))
{
nodeParent.Left = right;
}
else
{
nodeParent.Right = right;
}
right.Left = node;
node.Parent = right;
}
}
protected virtual void RotateRight(T node)
{
if (node != null)
{
T left = LeftOf(node);
node.Left = RightOf(left);
if (node.Left != null)
{
node.Left.Parent = node;
}
T nodeParent = ParentOf(node);
left.Parent = nodeParent;
if (nodeParent == null)
{
Root = left;
}
else if (node == RightOf(nodeParent))
{
nodeParent.Right = left;
}
else
{
nodeParent.Left = left;
}
left.Right = node;
node.Parent = left;
}
}
#region Safety-Methods
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions.
/// <summary>
/// Returns the color of <paramref name="node"/>, or Black if it is null.
/// </summary>
/// <param name="node">Node</param>
/// <returns>The boolean color of <paramref name="node"/>, or black if null</returns>
protected static bool ColorOf(T node)
{
return node == null || node.Color;
}
/// <summary>
/// Sets the color of <paramref name="node"/> node to <paramref name="color"/>.
/// <br></br>
/// This method does nothing if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to set the color of</param>
/// <param name="color">Color (Boolean)</param>
protected static void SetColor(T node, bool color)
{
if (node != null)
{
node.Color = color;
}
}
/// <summary>
/// This method returns the left node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the left child from</param>
/// <returns>Left child of <paramref name="node"/></returns>
protected static T LeftOf(T node)
{
return node?.Left;
}
/// <summary>
/// This method returns the right node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the right child from</param>
/// <returns>Right child of <paramref name="node"/></returns>
protected static T RightOf(T node)
{
return node?.Right;
}
/// <summary>
/// Returns the parent node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the parent from</param>
/// <returns>Parent of <paramref name="node"/></returns>
protected static T ParentOf(T node)
{
return node?.Parent;
}
#endregion
}
}

View file

@ -0,0 +1,16 @@
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Represents a node in the Red-Black Tree.
/// </summary>
public class IntrusiveRedBlackTreeNode<T> where T : IntrusiveRedBlackTreeNode<T>
{
internal bool Color = true;
internal T Left;
internal T Right;
internal T Parent;
public T Predecessor => IntrusiveRedBlackTreeImpl<T>.PredecessorOf((T)this);
public T Successor => IntrusiveRedBlackTreeImpl<T>.SuccessorOf((T)this);
}
}

View file

@ -0,0 +1,617 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Dictionary that provides the ability for O(logN) Lookups for keys that exist in the Dictionary, and O(logN) lookups for keys immediately greater than or less than a specified key.
/// </summary>
/// <typeparam name="K">Key</typeparam>
/// <typeparam name="V">Value</typeparam>
public class TreeDictionary<K, V> : IntrusiveRedBlackTreeImpl<Node<K, V>>, IDictionary<K, V> where K : IComparable<K>
{
#region Public Methods
/// <summary>
/// Returns the value of the node whose key is <paramref name="key"/>, or the default value if no such node exists.
/// </summary>
/// <param name="key">Key of the node value to get</param>
/// <returns>Value associated w/ <paramref name="key"/></returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
public V Get(K key)
{
ArgumentNullException.ThrowIfNull(key);
Node<K, V> node = GetNode(key);
if (node == null)
{
return default;
}
return node.Value;
}
/// <summary>
/// Adds a new node into the tree whose key is <paramref name="key"/> key and value is <paramref name="value"/>.
/// <br></br>
/// <b>Note:</b> Adding the same key multiple times will cause the value for that key to be overwritten.
/// </summary>
/// <param name="key">Key of the node to add</param>
/// <param name="value">Value of the node to add</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> are null</exception>
public void Add(K key, V value)
{
ArgumentNullException.ThrowIfNull(key);
ArgumentNullException.ThrowIfNull(value);
Insert(key, value);
}
/// <summary>
/// Removes the node whose key is <paramref name="key"/> from the tree.
/// </summary>
/// <param name="key">Key of the node to remove</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
public void Remove(K key)
{
ArgumentNullException.ThrowIfNull(key);
if (Delete(key) != null)
{
Count--;
}
}
/// <summary>
/// Returns the value whose key is equal to or immediately less than <paramref name="key"/>.
/// </summary>
/// <param name="key">Key for which to find the floor value of</param>
/// <returns>Key of node immediately less than <paramref name="key"/></returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
public K Floor(K key)
{
Node<K, V> node = FloorNode(key);
if (node != null)
{
return node.Key;
}
return default;
}
/// <summary>
/// Returns the node whose key is equal to or immediately greater than <paramref name="key"/>.
/// </summary>
/// <param name="key">Key for which to find the ceiling node of</param>
/// <returns>Key of node immediately greater than <paramref name="key"/></returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
public K Ceiling(K key)
{
Node<K, V> node = CeilingNode(key);
if (node != null)
{
return node.Key;
}
return default;
}
/// <summary>
/// Finds the value whose key is immediately greater than <paramref name="key"/>.
/// </summary>
/// <param name="key">Key to find the successor of</param>
/// <returns>Value</returns>
public K SuccessorOf(K key)
{
Node<K, V> node = GetNode(key);
if (node != null)
{
Node<K, V> successor = SuccessorOf(node);
return successor != null ? successor.Key : default;
}
return default;
}
/// <summary>
/// Finds the value whose key is immediately less than <paramref name="key"/>.
/// </summary>
/// <param name="key">Key to find the predecessor of</param>
/// <returns>Value</returns>
public K PredecessorOf(K key)
{
Node<K, V> node = GetNode(key);
if (node != null)
{
Node<K, V> predecessor = PredecessorOf(node);
return predecessor != null ? predecessor.Key : default;
}
return default;
}
/// <summary>
/// Adds all the nodes in the dictionary as key/value pairs into <paramref name="list"/>.
/// <br></br>
/// The key/value pairs will be added in Level Order.
/// </summary>
/// <param name="list">List to add the tree pairs into</param>
public List<KeyValuePair<K, V>> AsLevelOrderList()
{
List<KeyValuePair<K, V>> list = new List<KeyValuePair<K, V>>();
Queue<Node<K, V>> nodes = new Queue<Node<K, V>>();
if (this.Root != null)
{
nodes.Enqueue(this.Root);
}
while (nodes.TryDequeue(out Node<K, V> node))
{
list.Add(new KeyValuePair<K, V>(node.Key, node.Value));
if (node.Left != null)
{
nodes.Enqueue(node.Left);
}
if (node.Right != null)
{
nodes.Enqueue(node.Right);
}
}
return list;
}
/// <summary>
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
/// </summary>
/// <returns>A list of all KeyValuePairs sorted by Key Order</returns>
public List<KeyValuePair<K, V>> AsList()
{
List<KeyValuePair<K, V>> list = new List<KeyValuePair<K, V>>();
AddToList(Root, list);
return list;
}
#endregion
#region Private Methods (BST)
/// <summary>
/// Adds all nodes that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
/// </summary>
/// <param name="node">The node to search for nodes within</param>
/// <param name="list">The list to add node to</param>
private void AddToList(Node<K, V> node, List<KeyValuePair<K, V>> list)
{
if (node == null)
{
return;
}
AddToList(node.Left, list);
list.Add(new KeyValuePair<K, V>(node.Key, node.Value));
AddToList(node.Right, list);
}
/// <summary>
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
/// </summary>
/// <param name="key">Key of the node to get</param>
/// <returns>Node reference in the tree</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
private Node<K, V> GetNode(K key)
{
ArgumentNullException.ThrowIfNull(key);
Node<K, V> node = Root;
while (node != null)
{
int cmp = key.CompareTo(node.Key);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
return null;
}
/// <summary>
/// Inserts a new node into the tree whose key is <paramref name="key"/> and value is <paramref name="value"/>.
/// <br></br>
/// Adding the same key multiple times will overwrite the previous value.
/// </summary>
/// <param name="key">Key of the node to insert</param>
/// <param name="value">Value of the node to insert</param>
private void Insert(K key, V value)
{
Node<K, V> newNode = BSTInsert(key, value);
RestoreBalanceAfterInsertion(newNode);
}
/// <summary>
/// Insertion Mechanism for a Binary Search Tree (BST).
/// <br></br>
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="key"/>, and all children in the right subtree are greater than <paramref name="key"/>.
/// <br></br>
/// <b>Note: </b> If a node whose key is <paramref name="key"/> already exists, it's value will be overwritten.
/// </summary>
/// <param name="key">Key of the node to insert</param>
/// <param name="value">Value of the node to insert</param>
/// <returns>The inserted Node</returns>
private Node<K, V> BSTInsert(K key, V value)
{
Node<K, V> parent = null;
Node<K, V> node = Root;
while (node != null)
{
parent = node;
int cmp = key.CompareTo(node.Key);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
node.Value = value;
return node;
}
}
Node<K, V> newNode = new Node<K, V>(key, value, parent);
if (newNode.Parent == null)
{
Root = newNode;
}
else if (key.CompareTo(parent.Key) < 0)
{
parent.Left = newNode;
}
else
{
parent.Right = newNode;
}
Count++;
return newNode;
}
/// <summary>
/// Removes <paramref name="key"/> from the dictionary, if it exists.
/// </summary>
/// <param name="key">Key of the node to delete</param>
/// <returns>The deleted Node</returns>
private Node<K, V> Delete(K key)
{
// O(1) Retrieval
Node<K, V> nodeToDelete = GetNode(key);
if (nodeToDelete == null) return null;
Node<K, V> replacementNode;
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
{
replacementNode = nodeToDelete;
}
else
{
replacementNode = PredecessorOf(nodeToDelete);
}
Node<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{
Root = tmp;
}
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
{
ParentOf(replacementNode).Left = tmp;
}
else
{
ParentOf(replacementNode).Right = tmp;
}
if (replacementNode != nodeToDelete)
{
nodeToDelete.Key = replacementNode.Key;
nodeToDelete.Value = replacementNode.Value;
}
if (tmp != null && ColorOf(replacementNode) == Black)
{
RestoreBalanceAfterRemoval(tmp);
}
return replacementNode;
}
/// <summary>
/// Returns the node whose key immediately less than or equal to <paramref name="key"/>.
/// </summary>
/// <param name="key">Key for which to find the floor node of</param>
/// <returns>Node whose key is immediately less than or equal to <paramref name="key"/>, or null if no such node is found.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
private Node<K, V> FloorNode(K key)
{
ArgumentNullException.ThrowIfNull(key);
Node<K, V> tmp = Root;
while (tmp != null)
{
int cmp = key.CompareTo(tmp.Key);
if (cmp > 0)
{
if (tmp.Right != null)
{
tmp = tmp.Right;
}
else
{
return tmp;
}
}
else if (cmp < 0)
{
if (tmp.Left != null)
{
tmp = tmp.Left;
}
else
{
Node<K, V> parent = tmp.Parent;
Node<K, V> ptr = tmp;
while (parent != null && ptr == parent.Left)
{
ptr = parent;
parent = parent.Parent;
}
return parent;
}
}
else
{
return tmp;
}
}
return null;
}
/// <summary>
/// Returns the node whose key is immediately greater than or equal to than <paramref name="key"/>.
/// </summary>
/// <param name="key">Key for which to find the ceiling node of</param>
/// <returns>Node whose key is immediately greater than or equal to <paramref name="key"/>, or null if no such node is found.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
private Node<K, V> CeilingNode(K key)
{
ArgumentNullException.ThrowIfNull(key);
Node<K, V> tmp = Root;
while (tmp != null)
{
int cmp = key.CompareTo(tmp.Key);
if (cmp < 0)
{
if (tmp.Left != null)
{
tmp = tmp.Left;
}
else
{
return tmp;
}
}
else if (cmp > 0)
{
if (tmp.Right != null)
{
tmp = tmp.Right;
}
else
{
Node<K, V> parent = tmp.Parent;
Node<K, V> ptr = tmp;
while (parent != null && ptr == parent.Right)
{
ptr = parent;
parent = parent.Parent;
}
return parent;
}
}
else
{
return tmp;
}
}
return null;
}
#endregion
#region Interface Implementations
// Method descriptions are not provided as they are already included as part of the interface.
public bool ContainsKey(K key)
{
ArgumentNullException.ThrowIfNull(key);
return GetNode(key) != null;
}
bool IDictionary<K, V>.Remove(K key)
{
int count = Count;
Remove(key);
return count > Count;
}
public bool TryGetValue(K key, [MaybeNullWhen(false)] out V value)
{
ArgumentNullException.ThrowIfNull(key);
Node<K, V> node = GetNode(key);
value = node != null ? node.Value : default;
return node != null;
}
public void Add(KeyValuePair<K, V> item)
{
ArgumentNullException.ThrowIfNull(item.Key);
Add(item.Key, item.Value);
}
public bool Contains(KeyValuePair<K, V> item)
{
if (item.Key == null)
{
return false;
}
Node<K, V> node = GetNode(item.Key);
if (node != null)
{
return node.Key.Equals(item.Key) && node.Value.Equals(item.Value);
}
return false;
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
if (arrayIndex < 0 || array.Length - arrayIndex < this.Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
SortedList<K, V> list = GetKeyValues();
int offset = 0;
for (int i = arrayIndex; i < array.Length && offset < list.Count; i++)
{
array[i] = new KeyValuePair<K, V>(list.Keys[i], list.Values[i]);
offset++;
}
}
public bool Remove(KeyValuePair<K, V> item)
{
Node<K, V> node = GetNode(item.Key);
if (node == null)
{
return false;
}
if (node.Value.Equals(item.Value))
{
int count = Count;
Remove(item.Key);
return count > Count;
}
return false;
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
return GetKeyValues().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetKeyValues().GetEnumerator();
}
public ICollection<K> Keys => GetKeyValues().Keys;
public ICollection<V> Values => GetKeyValues().Values;
public bool IsReadOnly => false;
public V this[K key]
{
get => Get(key);
set => Add(key, value);
}
#endregion
#region Private Interface Helper Methods
/// <summary>
/// Returns a sorted list of all the node keys / values in the tree.
/// </summary>
/// <returns>List of node keys</returns>
private SortedList<K, V> GetKeyValues()
{
SortedList<K, V> set = new SortedList<K, V>();
Queue<Node<K, V>> queue = new Queue<Node<K, V>>();
if (Root != null)
{
queue.Enqueue(Root);
}
while (queue.TryDequeue(out Node<K, V> node))
{
set.Add(node.Key, node.Value);
if (null != node.Left)
{
queue.Enqueue(node.Left);
}
if (null != node.Right)
{
queue.Enqueue(node.Right);
}
}
return set;
}
#endregion
}
/// <summary>
/// Represents a node in the TreeDictionary which contains a key and value of generic type K and V, respectively.
/// </summary>
/// <typeparam name="K">Key of the node</typeparam>
/// <typeparam name="V">Value of the node</typeparam>
public class Node<K, V> : IntrusiveRedBlackTreeNode<Node<K, V>> where K : IComparable<K>
{
internal K Key;
internal V Value;
internal Node(K key, V value, Node<K, V> parent)
{
Key = key;
Value = value;
Parent = parent;
}
}
}

View file

@ -0,0 +1,16 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra
}
}

View file

@ -0,0 +1,149 @@
using Ryujinx.Common.Logging;
using System;
using System.IO;
namespace Ryujinx.Common.Configuration
{
public static class AppDataManager
{
public const string DefaultBaseDir = "Ryujinx";
public const string DefaultPortableDir = "portable";
// The following 3 are always part of Base Directory
private const string GamesDir = "games";
private const string ProfilesDir = "profiles";
private const string KeysDir = "system";
public enum LaunchMode
{
UserProfile,
Portable,
Custom
}
public static LaunchMode Mode { get; private set; }
public static string BaseDirPath { get; private set; }
public static string GamesDirPath { get; private set; }
public static string ProfilesDirPath { get; private set; }
public static string KeysDirPath { get; private set; }
public static string KeysDirPathUser { get; }
public const string DefaultNandDir = "bis";
public const string DefaultSdcardDir = "sdcard";
private const string DefaultModsDir = "mods";
public static string CustomModsPath { get; set; }
public static string CustomSdModsPath {get; set; }
public static string CustomNandPath { get; set; } // TODO: Actually implement this into VFS
public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS
static AppDataManager()
{
KeysDirPathUser = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch");
}
public static void Initialize(string baseDirPath)
{
string appDataPath;
if (OperatingSystem.IsMacOS())
{
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support");
}
else
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
if (appDataPath.Length == 0)
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
if (Directory.Exists(portablePath))
{
BaseDirPath = portablePath;
Mode = LaunchMode.Portable;
}
else
{
BaseDirPath = userProfilePath;
Mode = LaunchMode.UserProfile;
}
if (baseDirPath != null && baseDirPath != userProfilePath)
{
if (!Directory.Exists(baseDirPath))
{
Logger.Error?.Print(LogClass.Application, $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}...");
}
else
{
BaseDirPath = baseDirPath;
Mode = LaunchMode.Custom;
}
}
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
// and a Ryujinx folder does not already exist in Application Support.
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
// This should be removed in the future.
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
{
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
if (Path.Exists(oldConfigPath) && !Path.Exists(BaseDirPath))
{
CopyDirectory(oldConfigPath, BaseDirPath);
Directory.Delete(oldConfigPath, true);
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
}
}
SetupBasePaths();
}
private static void SetupBasePaths()
{
Directory.CreateDirectory(BaseDirPath);
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
}
private static void CopyDirectory(string sourceDir, string destinationDir)
{
var dir = new DirectoryInfo(sourceDir);
if (!dir.Exists)
{
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
}
DirectoryInfo[] subDirs = dir.GetDirectories();
Directory.CreateDirectory(destinationDir);
foreach (FileInfo file in dir.GetFiles())
{
if (file.Name == ".DS_Store")
{
continue;
}
file.CopyTo(Path.Combine(destinationDir, file.Name));
}
foreach (DirectoryInfo subDir in subDirs)
{
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
}
}
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
}
}

View file

@ -0,0 +1,63 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
public enum AspectRatio
{
Fixed4x3,
Fixed16x9,
Fixed16x10,
Fixed21x9,
Fixed32x9,
Stretched
}
public static class AspectRatioExtensions
{
public static float ToFloat(this AspectRatio aspectRatio)
{
return aspectRatio.ToFloatX() / aspectRatio.ToFloatY();
}
public static float ToFloatX(this AspectRatio aspectRatio)
{
return aspectRatio switch
{
AspectRatio.Fixed4x3 => 4.0f,
AspectRatio.Fixed16x9 => 16.0f,
AspectRatio.Fixed16x10 => 16.0f,
AspectRatio.Fixed21x9 => 21.0f,
AspectRatio.Fixed32x9 => 32.0f,
_ => 16.0f
};
}
public static float ToFloatY(this AspectRatio aspectRatio)
{
return aspectRatio switch
{
AspectRatio.Fixed4x3 => 3.0f,
AspectRatio.Fixed16x9 => 9.0f,
AspectRatio.Fixed16x10 => 10.0f,
AspectRatio.Fixed21x9 => 9.0f,
AspectRatio.Fixed32x9 => 9.0f,
_ => 9.0f
};
}
public static string ToText(this AspectRatio aspectRatio)
{
return aspectRatio switch
{
AspectRatio.Fixed4x3 => "4:3",
AspectRatio.Fixed16x9 => "16:9",
AspectRatio.Fixed16x10 => "16:10",
AspectRatio.Fixed21x9 => "21:9",
AspectRatio.Fixed32x9 => "32:9",
_ => "Stretched"
};
}
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
public enum BackendThreading
{
Auto,
Off,
On
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
public struct DownloadableContentContainer
{
[JsonPropertyName("path")]
public string ContainerPath { get; set; }
[JsonPropertyName("dlc_nca_list")]
public List<DownloadableContentNca> DownloadableContentNcaList { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
public struct DownloadableContentNca
{
[JsonPropertyName("path")]
public string FullPath { get; set; }
[JsonPropertyName("title_id")]
public ulong TitleId { get; set; }
[JsonPropertyName("is_enabled")]
public bool Enabled { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend
{
Vulkan,
OpenGl
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
public enum GraphicsDebugLevel
{
None,
Error,
Slowdowns,
All
}
}

View file

@ -0,0 +1,58 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
public enum GamepadInputId : byte
{
Unbound,
A,
B,
X,
Y,
LeftStick,
RightStick,
LeftShoulder,
RightShoulder,
// Likely axis
LeftTrigger,
// Likely axis
RightTrigger,
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
// Special buttons
Minus,
Plus,
Back = Minus,
Start = Plus,
Guide,
Misc1,
// Xbox Elite paddle
Paddle1,
Paddle2,
Paddle3,
Paddle4,
// PS5 touchpad button
Touchpad,
// Virtual buttons for single joycon
SingleLeftTrigger0,
SingleRightTrigger0,
SingleLeftTrigger1,
SingleRightTrigger1,
Count
}
}

View file

@ -0,0 +1,82 @@
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class GenericControllerInputConfig<Button, Stick> : GenericInputConfigurationCommon<Button> where Button : unmanaged where Stick : unmanaged
{
[JsonIgnore]
private float _deadzoneLeft;
[JsonIgnore]
private float _deadzoneRight;
[JsonIgnore]
private float _triggerThreshold;
/// <summary>
/// Left JoyCon Controller Stick Bindings
/// </summary>
public JoyconConfigControllerStick<Button, Stick> LeftJoyconStick { get; set; }
/// <summary>
/// Right JoyCon Controller Stick Bindings
/// </summary>
public JoyconConfigControllerStick<Button, Stick> RightJoyconStick { get; set; }
/// <summary>
/// Controller Left Analog Stick Deadzone
/// </summary>
public float DeadzoneLeft
{
get => _deadzoneLeft; set
{
_deadzoneLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
/// <summary>
/// Controller Right Analog Stick Deadzone
/// </summary>
public float DeadzoneRight
{
get => _deadzoneRight; set
{
_deadzoneRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
/// <summary>
/// Controller Left Analog Stick Range
/// </summary>
public float RangeLeft { get; set; }
/// <summary>
/// Controller Right Analog Stick Range
/// </summary>
public float RangeRight { get; set; }
/// <summary>
/// Controller Trigger Threshold
/// </summary>
public float TriggerThreshold
{
get => _triggerThreshold; set
{
_triggerThreshold = MathF.Round(value, 3);
OnPropertyChanged();
}
}
/// <summary>
/// Controller Motion Settings
/// </summary>
public MotionConfigController Motion { get; set; }
/// <summary>
/// Controller Rumble Settings
/// </summary>
public RumbleConfigController Rumble { get; set; }
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class JoyconConfigControllerStick<Button, Stick> where Button: unmanaged where Stick: unmanaged
{
public Stick Joystick { get; set; }
public bool InvertStickX { get; set; }
public bool InvertStickY { get; set; }
public bool Rotate90CW { get; set; }
public Button StickButton { get; set; }
}
}

View file

@ -0,0 +1,30 @@
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
public class CemuHookMotionConfigController : MotionConfigController
{
/// <summary>
/// Motion Controller Slot
/// </summary>
public int Slot { get; set; }
/// <summary>
/// Motion Controller Alternative Slot, for RightJoyCon in Pair mode
/// </summary>
public int AltSlot { get; set; }
/// <summary>
/// Mirror motion input in Pair mode
/// </summary>
public bool MirrorInput { get; set; }
/// <summary>
/// Host address of the DSU Server
/// </summary>
public string DsuServerHost { get; set; }
/// <summary>
/// Port of the DSU Server
/// </summary>
public int DsuServerPort { get; set; }
}
}

View file

@ -0,0 +1,79 @@
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
{
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
{
// Temporary reader to get the backend type
Utf8JsonReader tempReader = reader;
MotionInputBackendType result = MotionInputBackendType.Invalid;
while (tempReader.Read())
{
// NOTE: We scan all properties ignoring the depth entirely on purpose.
// The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
// As such, this code will try to parse very field named "motion_backend" to the correct enum.
if (tempReader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = tempReader.GetString();
if (propertyName.Equals("motion_backend"))
{
tempReader.Read();
if (tempReader.TokenType == JsonTokenType.String)
{
string backendTypeRaw = tempReader.GetString();
if (!Enum.TryParse(backendTypeRaw, out result))
{
result = MotionInputBackendType.Invalid;
}
else
{
break;
}
}
}
}
}
return result;
}
public override MotionConfigController Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
MotionInputBackendType motionBackendType = GetMotionInputBackendType(ref reader);
return motionBackendType switch
{
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
};
}
public override void Write(Utf8JsonWriter writer, MotionConfigController value, JsonSerializerOptions options)
{
switch (value.MotionBackend)
{
case MotionInputBackendType.GamepadDriver:
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
break;
case MotionInputBackendType.CemuHook:
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
break;
default:
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
}
}
}
}

View file

@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
[JsonConverter(typeof(JsonMotionConfigControllerConverter))]
public class MotionConfigController
{
public MotionInputBackendType MotionBackend { get; set; }
/// <summary>
/// Gyro Sensitivity
/// </summary>
public int Sensitivity { get; set; }
/// <summary>
/// Gyro Deadzone
/// </summary>
public double GyroDeadzone { get; set; }
/// <summary>
/// Enable Motion Controls
/// </summary>
public bool EnableMotion { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(MotionConfigController))]
[JsonSerializable(typeof(CemuHookMotionConfigController))]
[JsonSerializable(typeof(StandardMotionConfigController))]
public partial class MotionConfigJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
public enum MotionInputBackendType : byte
{
Invalid,
GamepadDriver,
CemuHook
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
public class StandardMotionConfigController : MotionConfigController { }
}

View file

@ -0,0 +1,20 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class RumbleConfigController
{
/// <summary>
/// Controller Strong Rumble Multiplier
/// </summary>
public float StrongRumble { get; set; }
/// <summary>
/// Controller Weak Rumble Multiplier
/// </summary>
public float WeakRumble { get; set; }
/// <summary>
/// Enable Rumble
/// </summary>
public bool EnableRumble { get; set; }
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class StandardControllerInputConfig : GenericControllerInputConfig<GamepadInputId, StickInputId> { }
}

View file

@ -0,0 +1,15 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
public enum StickInputId : byte
{
Unbound,
Left,
Right,
Count
}
}

View file

@ -0,0 +1,23 @@
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[Flags]
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
public enum ControllerType : int
{
None,
ProController = 1 << 0,
Handheld = 1 << 1,
JoyconPair = 1 << 2,
JoyconLeft = 1 << 3,
JoyconRight = 1 << 4,
Invalid = 1 << 5,
Pokeball = 1 << 6,
SystemExternal = 1 << 29,
System = 1 << 30
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid
{
public class GenericInputConfigurationCommon<Button> : InputConfig where Button : unmanaged
{
/// <summary>
/// Left JoyCon Controller Bindings
/// </summary>
public LeftJoyconCommonConfig<Button> LeftJoycon { get; set; }
/// <summary>
/// Right JoyCon Controller Bindings
/// </summary>
public RightJoyconCommonConfig<Button> RightJoycon { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
public enum InputBackendType
{
Invalid,
WindowKeyboard,
GamepadSDL2,
}
}

View file

@ -0,0 +1,41 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonInputConfigConverter))]
public class InputConfig : INotifyPropertyChanged
{
/// <summary>
/// The current version of the input file format
/// </summary>
public const int CurrentVersion = 1;
public int Version { get; set; }
public InputBackendType Backend { get; set; }
/// <summary>
/// Controller id
/// </summary>
public string Id { get; set; }
/// <summary>
/// Controller's Type
/// </summary>
public ControllerType ControllerType { get; set; }
/// <summary>
/// Player's Index for the controller
/// </summary>
public PlayerIndex PlayerIndex { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(InputConfig))]
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
[JsonSerializable(typeof(StandardControllerInputConfig))]
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,81 @@
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
public class JsonInputConfigConverter : JsonConverter<InputConfig>
{
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
{
// Temporary reader to get the backend type
Utf8JsonReader tempReader = reader;
InputBackendType result = InputBackendType.Invalid;
while (tempReader.Read())
{
// NOTE: We scan all properties ignoring the depth entirely on purpose.
// The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
// As such, this code will try to parse very field named "backend" to the correct enum.
if (tempReader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = tempReader.GetString();
if (propertyName.Equals("backend"))
{
tempReader.Read();
if (tempReader.TokenType == JsonTokenType.String)
{
string backendTypeRaw = tempReader.GetString();
if (!Enum.TryParse(backendTypeRaw, out result))
{
result = InputBackendType.Invalid;
}
else
{
break;
}
}
}
}
}
return result;
}
public override InputConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
InputBackendType backendType = GetInputBackendType(ref reader);
return backendType switch
{
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
};
}
public override void Write(Utf8JsonWriter writer, InputConfig value, JsonSerializerOptions options)
{
switch (value.Backend)
{
case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
break;
case InputBackendType.GamepadSDL2:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
break;
default:
throw new ArgumentException($"Unknown backend type {value.Backend}");
}
}
}
}

View file

@ -0,0 +1,143 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
public enum Key
{
Unknown,
ShiftLeft,
ShiftRight,
ControlLeft,
ControlRight,
AltLeft,
AltRight,
WinLeft,
WinRight,
Menu,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
F25,
F26,
F27,
F28,
F29,
F30,
F31,
F32,
F33,
F34,
F35,
Up,
Down,
Left,
Right,
Enter,
Escape,
Space,
Tab,
BackSpace,
Insert,
Delete,
PageUp,
PageDown,
Home,
End,
CapsLock,
ScrollLock,
PrintScreen,
Pause,
NumLock,
Clear,
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
KeypadDivide,
KeypadMultiply,
KeypadSubtract,
KeypadAdd,
KeypadDecimal,
KeypadEnter,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Number0,
Number1,
Number2,
Number3,
Number4,
Number5,
Number6,
Number7,
Number8,
Number9,
Tilde,
Grave,
Minus,
Plus,
BracketLeft,
BracketRight,
Semicolon,
Quote,
Comma,
Period,
Slash,
BackSlash,
Unbound,
Count
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard
{
public class GenericKeyboardInputConfig<Key> : GenericInputConfigurationCommon<Key> where Key : unmanaged
{
/// <summary>
/// Left JoyCon Controller Stick Bindings
/// </summary>
public JoyconConfigKeyboardStick<Key> LeftJoyconStick { get; set; }
/// <summary>
/// Right JoyCon Controller Stick Bindings
/// </summary>
public JoyconConfigKeyboardStick<Key> RightJoyconStick { get; set; }
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard
{
public class JoyconConfigKeyboardStick<Key> where Key: unmanaged
{
public Key StickUp { get; set; }
public Key StickDown { get; set; }
public Key StickLeft { get; set; }
public Key StickRight { get; set; }
public Key StickButton { get; set; }
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard
{
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Common.Configuration.Hid
{
// NOTE: Please don't change this to struct.
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
public class KeyboardHotkeys
{
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }
public Key ShowUi { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
public Key ResScaleUp { get; set; }
public Key ResScaleDown { get; set; }
public Key VolumeUp { get; set; }
public Key VolumeDown { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid
{
public class LeftJoyconCommonConfig<Button>
{
public Button ButtonMinus { get; set; }
public Button ButtonL { get; set; }
public Button ButtonZl { get; set; }
public Button ButtonSl { get; set; }
public Button ButtonSr { get; set; }
public Button DpadUp { get; set; }
public Button DpadDown { get; set; }
public Button DpadLeft { get; set; }
public Button DpadRight { get; set; }
}
}

View file

@ -0,0 +1,22 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
public enum PlayerIndex : int
{
Player1 = 0,
Player2 = 1,
Player3 = 2,
Player4 = 3,
Player5 = 4,
Player6 = 5,
Player7 = 6,
Player8 = 7,
Handheld = 8,
Unknown = 9,
Auto = 10 // Shouldn't be used directly
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid
{
public class RightJoyconCommonConfig<Button>
{
public Button ButtonPlus { get; set; }
public Button ButtonR { get; set; }
public Button ButtonZr { get; set; }
public Button ButtonSl { get; set; }
public Button ButtonSr { get; set; }
public Button ButtonX { get; set; }
public Button ButtonB { get; set; }
public Button ButtonY { get; set; }
public Button ButtonA { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
public enum MemoryManagerMode : byte
{
SoftwarePageTable,
HostMapped,
HostMappedUnsafe
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ryujinx.Common.Configuration
{
public struct TitleUpdateMetadata
{
public string Selected { get; set; }
public List<string> Paths { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(TitleUpdateMetadata))]
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class BinaryReaderExtensions
{
public unsafe static T ReadStruct<T>(this BinaryReader reader)
where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class BinaryWriterExtensions
{
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : unmanaged
{
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}
public static void Write(this BinaryWriter writer, UInt128 value)
{
writer.Write((ulong)value);
writer.Write((ulong)(value >> 64));
}
public static void Write(this BinaryWriter writer, MemoryStream stream)
{
stream.CopyTo(writer.BaseStream);
}
}
}

View file

@ -0,0 +1,138 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class StreamExtensions
{
/// <summary>
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
///
/// This default implementation converts each buffer value to a stack-allocated
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="buffer">The buffer of values to be written</param>
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
{
if (buffer.Length == 0)
{
return;
}
if (BitConverter.IsLittleEndian)
{
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
stream.Write(byteBuffer);
}
else
{
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
foreach (int value in buffer)
{
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
stream.Write(byteBuffer);
}
}
}
/// <summary>
/// Writes a four-byte signed integer to this stream. The current position
/// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, int value)
{
Span<byte> buffer = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte signed integer to this stream. The current position
/// of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, long value)
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
// Writes a four-byte unsigned integer to this stream. The current position
// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte unsigned integer to this stream. The current
/// position of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes the contents of source to stream by calling source.CopyTo(stream).
/// Provides consistency with other Stream.Write methods.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="source">The stream to be read from</param>
public static void Write(this Stream stream, Stream source)
{
source.CopyTo(stream);
}
/// <summary>
/// Writes a sequence of bytes to the Stream.
/// </summary>
/// <param name="stream">The stream to be written to.</param>
/// <param name="value">The byte to be written</param>
/// <param name="count">The number of times the value should be written</param>
public static void WriteByte(this Stream stream, byte value, int count)
{
if (count <= 0)
{
return;
}
const int BlockSize = 16;
int blockCount = count / BlockSize;
if (blockCount > 0)
{
Span<byte> span = stackalloc byte[BlockSize];
span.Fill(value);
for (int x = 0; x < blockCount; x++)
{
stream.Write(span);
}
}
int nonBlockBytes = count % BlockSize;
for (int x = 0; x < nonBlockBytes; x++)
{
stream.WriteByte(value);
}
}
}
}

View file

@ -0,0 +1,22 @@
using System;
namespace Ryujinx.Common.GraphicsDriver
{
public static class DriverUtilities
{
public static void ToggleOGLThreading(bool enabled)
{
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
try
{
NVThreadedOptimization.SetThreadedOptimization(enabled);
}
catch
{
// NVAPI is not available, or couldn't change the application profile.
}
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Common.GraphicsDriver.NVAPI
{
enum Nvapi : uint
{
OglThreadControlId = 0x20C1221E,
OglThreadControlDefault = 0,
OglThreadControlEnable = 1,
OglThreadControlDisable = 2
}
}

View file

@ -0,0 +1,42 @@
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Common.GraphicsDriver.NVAPI
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public unsafe struct NvapiUnicodeString
{
private fixed byte _data[4096];
public NvapiUnicodeString(string text)
{
Set(text);
}
public string Get()
{
fixed (byte* data = _data)
{
string text = Encoding.Unicode.GetString(data, 4096);
int index = text.IndexOf('\0');
if (index > -1)
{
text = text.Remove(index);
}
return text;
}
}
public void Set(string text)
{
text += '\0';
fixed (char* textPtr = text)
fixed (byte* data = _data)
{
int written = Encoding.Unicode.GetBytes(textPtr, text.Length, data, 4096);
}
}
}
}

View file

@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Common.GraphicsDriver.NVAPI
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
unsafe struct NvdrsApplicationV4
{
public uint Version;
public uint IsPredefined;
public NvapiUnicodeString AppName;
public NvapiUnicodeString UserFriendlyName;
public NvapiUnicodeString Launcher;
public NvapiUnicodeString FileInFolder;
public uint Flags;
public NvapiUnicodeString CommandLine;
}
}

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Common.GraphicsDriver.NVAPI
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct NvdrsProfile
{
public uint Version;
public NvapiUnicodeString ProfileName;
public uint GpuSupport;
public uint IsPredefined;
public uint NumOfApps;
public uint NumOfSettings;
}
}

View file

@ -0,0 +1,49 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Common.GraphicsDriver.NVAPI
{
enum NvdrsSettingType : uint
{
NvdrsDwordType,
NvdrsBinaryType,
NvdrsStringType,
NvdrsWstringType,
}
enum NvdrsSettingLocation : uint
{
NvdrsCurrentProfileLocation,
NvdrsGlobalProfileLocation,
NvdrsBaseProfileLocation,
NvdrsDefaultProfileLocation,
}
[StructLayout(LayoutKind.Explicit, Size = 0x3020)]
unsafe struct NvdrsSetting
{
[FieldOffset(0x0)]
public uint Version;
[FieldOffset(0x4)]
public NvapiUnicodeString SettingName;
[FieldOffset(0x1004)]
public Nvapi SettingId;
[FieldOffset(0x1008)]
public NvdrsSettingType SettingType;
[FieldOffset(0x100C)]
public NvdrsSettingLocation SettingLocation;
[FieldOffset(0x1010)]
public uint IsCurrentPredefined;
[FieldOffset(0x1014)]
public uint IsPredefinedValid;
[FieldOffset(0x1018)]
public uint PredefinedValue;
[FieldOffset(0x1018)]
public NvapiUnicodeString PredefinedString;
[FieldOffset(0x201C)]
public uint CurrentValue;
[FieldOffset(0x201C)]
public NvapiUnicodeString CurrentString;
}
}

View file

@ -0,0 +1,163 @@
using Ryujinx.Common.GraphicsDriver.NVAPI;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.GraphicsDriver
{
static partial class NVThreadedOptimization
{
private const string ProfileName = "Ryujinx Nvidia Profile";
private const uint NvAPI_Initialize_ID = 0x0150E828;
private const uint NvAPI_DRS_CreateSession_ID = 0x0694D52E;
private const uint NvAPI_DRS_LoadSettings_ID = 0x375DBD6B;
private const uint NvAPI_DRS_FindProfileByName_ID = 0x7E4A9A0B;
private const uint NvAPI_DRS_CreateProfile_ID = 0x0CC176068;
private const uint NvAPI_DRS_CreateApplication_ID = 0x4347A9DE;
private const uint NvAPI_DRS_SetSetting_ID = 0x577DD202;
private const uint NvAPI_DRS_SaveSettings_ID = 0xFCBC7E14;
private const uint NvAPI_DRS_DestroySession_ID = 0x0DAD9CFF8;
[LibraryImport("nvapi64")]
private static partial IntPtr nvapi_QueryInterface(uint id);
private delegate int NvAPI_InitializeDelegate();
private static NvAPI_InitializeDelegate NvAPI_Initialize;
private delegate int NvAPI_DRS_CreateSessionDelegate(out IntPtr handle);
private static NvAPI_DRS_CreateSessionDelegate NvAPI_DRS_CreateSession;
private delegate int NvAPI_DRS_LoadSettingsDelegate(IntPtr handle);
private static NvAPI_DRS_LoadSettingsDelegate NvAPI_DRS_LoadSettings;
private delegate int NvAPI_DRS_FindProfileByNameDelegate(IntPtr handle, NvapiUnicodeString profileName, out IntPtr profileHandle);
private static NvAPI_DRS_FindProfileByNameDelegate NvAPI_DRS_FindProfileByName;
private delegate int NvAPI_DRS_CreateProfileDelegate(IntPtr handle, ref NvdrsProfile profileInfo, out IntPtr profileHandle);
private static NvAPI_DRS_CreateProfileDelegate NvAPI_DRS_CreateProfile;
private delegate int NvAPI_DRS_CreateApplicationDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsApplicationV4 app);
private static NvAPI_DRS_CreateApplicationDelegate NvAPI_DRS_CreateApplication;
private delegate int NvAPI_DRS_SetSettingDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsSetting setting);
private static NvAPI_DRS_SetSettingDelegate NvAPI_DRS_SetSetting;
private delegate int NvAPI_DRS_SaveSettingsDelegate(IntPtr handle);
private static NvAPI_DRS_SaveSettingsDelegate NvAPI_DRS_SaveSettings;
private delegate int NvAPI_DRS_DestroySessionDelegate(IntPtr handle);
private static NvAPI_DRS_DestroySessionDelegate NvAPI_DRS_DestroySession;
private static bool _initialized;
private static void Check(int status)
{
if (status != 0)
{
throw new Exception($"NVAPI Error: {status}");
}
}
private static void Initialize()
{
if (!_initialized)
{
NvAPI_Initialize = NvAPI_Delegate<NvAPI_InitializeDelegate>(NvAPI_Initialize_ID);
Check(NvAPI_Initialize());
NvAPI_DRS_CreateSession = NvAPI_Delegate<NvAPI_DRS_CreateSessionDelegate>(NvAPI_DRS_CreateSession_ID);
NvAPI_DRS_LoadSettings = NvAPI_Delegate<NvAPI_DRS_LoadSettingsDelegate>(NvAPI_DRS_LoadSettings_ID);
NvAPI_DRS_FindProfileByName = NvAPI_Delegate<NvAPI_DRS_FindProfileByNameDelegate>(NvAPI_DRS_FindProfileByName_ID);
NvAPI_DRS_CreateProfile = NvAPI_Delegate<NvAPI_DRS_CreateProfileDelegate>(NvAPI_DRS_CreateProfile_ID);
NvAPI_DRS_CreateApplication = NvAPI_Delegate<NvAPI_DRS_CreateApplicationDelegate>(NvAPI_DRS_CreateApplication_ID);
NvAPI_DRS_SetSetting = NvAPI_Delegate<NvAPI_DRS_SetSettingDelegate>(NvAPI_DRS_SetSetting_ID);
NvAPI_DRS_SaveSettings = NvAPI_Delegate<NvAPI_DRS_SaveSettingsDelegate>(NvAPI_DRS_SaveSettings_ID);
NvAPI_DRS_DestroySession = NvAPI_Delegate<NvAPI_DRS_DestroySessionDelegate>(NvAPI_DRS_DestroySession_ID);
_initialized = true;
}
}
private static uint MakeVersion<T>(uint version) where T : unmanaged
{
return (uint)Unsafe.SizeOf<T>() | version << 16;
}
public static void SetThreadedOptimization(bool enabled)
{
Initialize();
uint targetValue = (uint)(enabled ? Nvapi.OglThreadControlEnable : Nvapi.OglThreadControlDisable);
Check(NvAPI_Initialize());
Check(NvAPI_DRS_CreateSession(out IntPtr handle));
Check(NvAPI_DRS_LoadSettings(handle));
IntPtr profileHandle;
// Check if the profile already exists.
int status = NvAPI_DRS_FindProfileByName(handle, new NvapiUnicodeString(ProfileName), out profileHandle);
if (status != 0)
{
NvdrsProfile profile = new NvdrsProfile {
Version = MakeVersion<NvdrsProfile>(1),
IsPredefined = 0,
GpuSupport = uint.MaxValue
};
profile.ProfileName.Set(ProfileName);
Check(NvAPI_DRS_CreateProfile(handle, ref profile, out profileHandle));
NvdrsApplicationV4 application = new NvdrsApplicationV4
{
Version = MakeVersion<NvdrsApplicationV4>(4),
IsPredefined = 0,
Flags = 3 // IsMetro, IsCommandLine
};
application.AppName.Set("Ryujinx.exe");
application.UserFriendlyName.Set("Ryujinx");
application.Launcher.Set("");
application.FileInFolder.Set("");
Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application));
}
NvdrsSetting setting = new NvdrsSetting
{
Version = MakeVersion<NvdrsSetting>(1),
SettingId = Nvapi.OglThreadControlId,
SettingType = NvdrsSettingType.NvdrsDwordType,
SettingLocation = NvdrsSettingLocation.NvdrsCurrentProfileLocation,
IsCurrentPredefined = 0,
IsPredefinedValid = 0,
CurrentValue = targetValue,
PredefinedValue = targetValue
};
Check(NvAPI_DRS_SetSetting(handle, profileHandle, ref setting));
Check(NvAPI_DRS_SaveSettings(handle));
NvAPI_DRS_DestroySession(handle);
}
private static T NvAPI_Delegate<T>(uint id) where T : class
{
IntPtr ptr = nvapi_QueryInterface(id);
if (ptr != IntPtr.Zero)
{
return Marshal.GetDelegateForFunctionPointer<T>(ptr);
}
else
{
return null;
}
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
[StructLayout(LayoutKind.Sequential)]
public struct Hash128 : IEquatable<Hash128>
{
public ulong Low;
public ulong High;
public Hash128(ulong low, ulong high)
{
Low = low;
High = high;
}
public override string ToString()
{
return $"{High:x16}{Low:x16}";
}
public static bool operator ==(Hash128 x, Hash128 y)
{
return x.Equals(y);
}
public static bool operator !=(Hash128 x, Hash128 y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is Hash128 hash128 && Equals(hash128);
}
public bool Equals(Hash128 cmpObj)
{
return Low == cmpObj.Low && High == cmpObj.High;
}
public override int GetHashCode()
{
return HashCode.Combine(Low, High);
}
}
}

View file

@ -0,0 +1,42 @@
using System.Text;
namespace Ryujinx.Common.Logging
{
internal class DefaultLogFormatter : ILogFormatter
{
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
public string Format(LogEventArgs args)
{
StringBuilder sb = StringBuilderPool.Allocate();
try
{
sb.Clear();
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
sb.Append($" |{args.Level.ToString()[0]}| ");
if (args.ThreadName != null)
{
sb.Append(args.ThreadName);
sb.Append(' ');
}
sb.Append(args.Message);
if (args.Data is not null)
{
sb.Append(' ');
DynamicObjectFormatter.Format(sb, args.Data);
}
return sb.ToString();
}
finally
{
StringBuilderPool.Release(sb);
}
}
}
}

View file

@ -0,0 +1,84 @@
#nullable enable
using System;
using System.Reflection;
using System.Text;
namespace Ryujinx.Common.Logging
{
internal class DynamicObjectFormatter
{
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
public static string? Format(object? dynamicObject)
{
if (dynamicObject is null)
{
return null;
}
StringBuilder sb = StringBuilderPool.Allocate();
try
{
Format(sb, dynamicObject);
return sb.ToString();
}
finally
{
StringBuilderPool.Release(sb);
}
}
public static void Format(StringBuilder sb, object? dynamicObject)
{
if (dynamicObject is null)
{
return;
}
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
sb.Append('{');
foreach (var prop in props)
{
sb.Append(prop.Name);
sb.Append(": ");
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
{
Array? array = (Array?) prop.GetValue(dynamicObject);
if (array is not null)
{
foreach (var item in array)
{
sb.Append(item);
sb.Append(", ");
}
if (array.Length > 0)
{
sb.Remove(sb.Length - 2, 2);
}
}
}
else
{
sb.Append(prop.GetValue(dynamicObject));
}
sb.Append(" ; ");
}
// We remove the final ';' from the string
if (props.Length > 0)
{
sb.Remove(sb.Length - 3, 3);
}
sb.Append('}');
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Common.Logging
{
interface ILogFormatter
{
string Format(LogEventArgs args);
}
}

View file

@ -0,0 +1,76 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
public enum LogClass
{
Application,
Audio,
AudioRenderer,
Configuration,
Cpu,
Emulation,
FFmpeg,
Font,
Gpu,
Hid,
Host1x,
Kernel,
KernelIpc,
KernelScheduler,
KernelSvc,
Loader,
ModLoader,
Nvdec,
Ptc,
Service,
ServiceAcc,
ServiceAm,
ServiceApm,
ServiceAudio,
ServiceBcat,
ServiceBsd,
ServiceBtm,
ServiceCaps,
ServiceFatal,
ServiceFriend,
ServiceFs,
ServiceHid,
ServiceIrs,
ServiceLdn,
ServiceLdr,
ServiceLm,
ServiceMii,
ServiceMm,
ServiceMnpp,
ServiceNfc,
ServiceNfp,
ServiceNgct,
ServiceNifm,
ServiceNim,
ServiceNs,
ServiceNsd,
ServiceNtc,
ServiceNv,
ServiceOlsc,
ServicePctl,
ServicePcv,
ServicePl,
ServicePrepo,
ServicePsm,
ServicePtm,
ServiceSet,
ServiceSfdnsres,
ServiceSm,
ServiceSsl,
ServiceSss,
ServiceTime,
ServiceVi,
SurfaceFlinger,
TamperMachine,
Ui,
Vic
}
}

View file

@ -0,0 +1,23 @@
using System;
namespace Ryujinx.Common.Logging
{
public class LogEventArgs : EventArgs
{
public readonly LogLevel Level;
public readonly TimeSpan Time;
public readonly string ThreadName;
public readonly string Message;
public readonly object Data;
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
{
Level = level;
Time = time;
ThreadName = threadName;
Message = message;
Data = data;
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
internal class LogEventArgsJson
{
public LogLevel Level { get; }
public TimeSpan Time { get; }
public string ThreadName { get; }
public string Message { get; }
public string Data { get; }
[JsonConstructor]
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
{
Level = level;
Time = time;
ThreadName = threadName;
Message = message;
Data = data;
}
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
{
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
}
}
}

View file

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonSerializable(typeof(LogEventArgsJson))]
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,19 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
public enum LogLevel
{
Debug,
Stub,
Info,
Warning,
Error,
Guest,
AccessLog,
Notice,
Trace
}
}

View file

@ -0,0 +1,224 @@
using Ryujinx.Common.SystemInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Common.Logging
{
public static class Logger
{
private static readonly Stopwatch m_Time;
private static readonly bool[] m_EnabledClasses;
private static readonly List<ILogTarget> m_LogTargets;
private static readonly StdErrAdapter _stdErrAdapter;
public static event EventHandler<LogEventArgs> Updated;
public readonly struct Log
{
internal readonly LogLevel Level;
internal Log(LogLevel level)
{
Level = level;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintMsg(LogClass logClass, string message)
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message)));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message)));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message)));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintStub(LogClass logClass, object data, [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed."), data));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintStub(LogClass logClass, string message, object data, [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintRawMsg(string message)
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}";
}
public static Log? Debug { get; private set; }
public static Log? Info { get; private set; }
public static Log? Warning { get; private set; }
public static Log? Error { get; private set; }
public static Log? Guest { get; private set; }
public static Log? AccessLog { get; private set; }
public static Log? Stub { get; private set; }
public static Log? Trace { get; private set; }
public static Log Notice { get; } // Always enabled
static Logger()
{
m_EnabledClasses = new bool[Enum.GetNames<LogClass>().Length];
for (int index = 0; index < m_EnabledClasses.Length; index++)
{
m_EnabledClasses[index] = true;
}
m_LogTargets = new List<ILogTarget>();
m_Time = Stopwatch.StartNew();
// Logger should log to console by default
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
Notice = new Log(LogLevel.Notice);
// Enable important log levels before configuration is loaded
Error = new Log(LogLevel.Error);
Warning = new Log(LogLevel.Warning);
Info = new Log(LogLevel.Info);
Trace = new Log(LogLevel.Trace);
_stdErrAdapter = new StdErrAdapter();
}
public static void RestartTime()
{
m_Time.Restart();
}
private static ILogTarget GetTarget(string targetName)
{
foreach (var target in m_LogTargets)
{
if (target.Name.Equals(targetName))
{
return target;
}
}
return null;
}
public static void AddTarget(ILogTarget target)
{
m_LogTargets.Add(target);
Updated += target.Log;
}
public static void RemoveTarget(string target)
{
ILogTarget logTarget = GetTarget(target);
if (logTarget != null)
{
Updated -= logTarget.Log;
m_LogTargets.Remove(logTarget);
logTarget.Dispose();
}
}
public static void Shutdown()
{
Updated = null;
_stdErrAdapter.Dispose();
foreach (var target in m_LogTargets)
{
target.Dispose();
}
m_LogTargets.Clear();
}
public static IReadOnlyCollection<LogLevel> GetEnabledLevels()
{
var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
List<LogLevel> levels = new List<LogLevel>(logs.Length);
foreach (var log in logs)
{
if (log.HasValue)
{
levels.Add(log.Value.Level);
}
}
return levels;
}
public static void SetEnable(LogLevel logLevel, bool enabled)
{
switch (logLevel)
{
case LogLevel.Debug : Debug = enabled ? new Log(LogLevel.Debug) : new Log?(); break;
case LogLevel.Info : Info = enabled ? new Log(LogLevel.Info) : new Log?(); break;
case LogLevel.Warning : Warning = enabled ? new Log(LogLevel.Warning) : new Log?(); break;
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : new Log?(); break;
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break;
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): new Log?(); break;
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
default: throw new ArgumentException("Unknown Log Level");
}
}
public static void SetEnable(LogClass logClass, bool enabled)
{
m_EnabledClasses[(int)logClass] = enabled;
}
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Common.Logging
{
public enum AsyncLogTargetOverflowAction
{
/// <summary>
/// Block until there's more room in the queue
/// </summary>
Block = 0,
/// <summary>
/// Discard the overflowing item
/// </summary>
Discard = 1
}
public class AsyncLogTargetWrapper : ILogTarget
{
private ILogTarget _target;
private Thread _messageThread;
private BlockingCollection<LogEventArgs> _messageQueue;
private readonly int _overflowTimeout;
string ILogTarget.Name { get => _target.Name; }
public AsyncLogTargetWrapper(ILogTarget target)
: this(target, -1, AsyncLogTargetOverflowAction.Block)
{ }
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
{
_target = target;
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
_overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0;
_messageThread = new Thread(() => {
while (!_messageQueue.IsCompleted)
{
try
{
_target.Log(this, _messageQueue.Take());
}
catch (InvalidOperationException)
{
// IOE means that Take() was called on a completed collection.
// Some other thread can call CompleteAdding after we pass the
// IsCompleted check but before we call Take.
// We can simply catch the exception since the loop will break
// on the next iteration.
}
}
});
_messageThread.Name = "Logger.MessageThread";
_messageThread.IsBackground = true;
_messageThread.Start();
}
public void Log(object sender, LogEventArgs e)
{
if (!_messageQueue.IsAddingCompleted)
{
_messageQueue.TryAdd(e, _overflowTimeout);
}
}
public void Dispose()
{
_messageQueue.CompleteAdding();
_messageThread.Join();
}
}
}

View file

@ -0,0 +1,41 @@
using System;
namespace Ryujinx.Common.Logging
{
public class ConsoleLogTarget : ILogTarget
{
private readonly ILogFormatter _formatter;
private readonly string _name;
string ILogTarget.Name { get => _name; }
private static ConsoleColor GetLogColor(LogLevel level) => level switch {
LogLevel.Info => ConsoleColor.White,
LogLevel.Warning => ConsoleColor.Yellow,
LogLevel.Error => ConsoleColor.Red,
LogLevel.Stub => ConsoleColor.DarkGray,
LogLevel.Notice => ConsoleColor.Cyan,
LogLevel.Trace => ConsoleColor.DarkCyan,
_ => ConsoleColor.Gray,
};
public ConsoleLogTarget(string name)
{
_formatter = new DefaultLogFormatter();
_name = name;
}
public void Log(object sender, LogEventArgs args)
{
Console.ForegroundColor = GetLogColor(args.Level);
Console.WriteLine(_formatter.Format(args));
Console.ResetColor();
}
public void Dispose()
{
Console.ResetColor();
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Linq;
namespace Ryujinx.Common.Logging
{
public class FileLogTarget : ILogTarget
{
private readonly StreamWriter _logWriter;
private readonly ILogFormatter _formatter;
private readonly string _name;
string ILogTarget.Name { get => _name; }
public FileLogTarget(string path, string name)
: this(path, name, FileShare.Read, FileMode.Append)
{ }
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
{
// Ensure directory is present
DirectoryInfo logDir = new DirectoryInfo(Path.Combine(path, "Logs"));
logDir.Create();
// Clean up old logs, should only keep 3
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
for (int i = 0; i < files.Length - 2; i++)
{
files[i].Delete();
}
string version = ReleaseInformation.GetVersion();
// Get path for the current time
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log");
_name = name;
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
_formatter = new DefaultLogFormatter();
}
public void Log(object sender, LogEventArgs args)
{
_logWriter.WriteLine(_formatter.Format(args));
_logWriter.Flush();
}
public void Dispose()
{
_logWriter.WriteLine("---- End of Log ----");
_logWriter.Flush();
_logWriter.Dispose();
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.Common.Logging
{
public interface ILogTarget : IDisposable
{
void Log(object sender, LogEventArgs args);
string Name { get; }
}
}

View file

@ -0,0 +1,40 @@
using Ryujinx.Common.Utilities;
using System.IO;
namespace Ryujinx.Common.Logging
{
public class JsonLogTarget : ILogTarget
{
private Stream _stream;
private bool _leaveOpen;
private string _name;
string ILogTarget.Name { get => _name; }
public JsonLogTarget(Stream stream, string name)
{
_stream = stream;
_name = name;
}
public JsonLogTarget(Stream stream, bool leaveOpen)
{
_stream = stream;
_leaveOpen = leaveOpen;
}
public void Log(object sender, LogEventArgs e)
{
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
}
public void Dispose()
{
if (!_leaveOpen)
{
_stream.Dispose();
}
}
}
}

View file

@ -0,0 +1,123 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Represents an array of unmanaged resources.
/// </summary>
/// <typeparam name="T">Array element type</typeparam>
public unsafe struct ArrayPtr<T> : IEquatable<ArrayPtr<T>>, IArray<T> where T : unmanaged
{
private IntPtr _ptr;
/// <summary>
/// Null pointer.
/// </summary>
public static ArrayPtr<T> Null => new ArrayPtr<T>() { _ptr = IntPtr.Zero };
/// <summary>
/// True if the pointer is null, false otherwise.
/// </summary>
public bool IsNull => _ptr == IntPtr.Zero;
/// <summary>
/// Number of elements on the array.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets a reference to the item at the given index.
/// </summary>
/// <remarks>
/// No bounds checks are performed, this allows negative indexing,
/// but care must be taken if the index may be out of bounds.
/// </remarks>
/// <param name="index">Index of the element</param>
/// <returns>Reference to the element at the given index</returns>
public ref T this[int index] => ref Unsafe.AsRef<T>((T*)_ptr + index);
/// <summary>
/// Creates a new array from a given reference.
/// </summary>
/// <remarks>
/// For data on the heap, proper pinning is necessary during
/// use. Failure to do so will result in memory corruption and crashes.
/// </remarks>
/// <param name="value">Reference of the first array element</param>
/// <param name="length">Number of elements on the array</param>
public ArrayPtr(ref T value, int length)
{
_ptr = (IntPtr)Unsafe.AsPointer(ref value);
Length = length;
}
/// <summary>
/// Creates a new array from a given pointer.
/// </summary>
/// <param name="ptr">Array base pointer</param>
/// <param name="length">Number of elements on the array</param>
public ArrayPtr(T* ptr, int length)
{
_ptr = (IntPtr)ptr;
Length = length;
}
/// <summary>
/// Creates a new array from a given pointer.
/// </summary>
/// <param name="ptr">Array base pointer</param>
/// <param name="length">Number of elements on the array</param>
public ArrayPtr(IntPtr ptr, int length)
{
_ptr = ptr;
Length = length;
}
/// <summary>
/// Splits the array starting at the specified position.
/// </summary>
/// <param name="start">Index where the new array should start</param>
/// <returns>New array starting at the specified position</returns>
public ArrayPtr<T> Slice(int start) => new ArrayPtr<T>(ref this[start], Length - start);
/// <summary>
/// Gets a span from the array.
/// </summary>
/// <returns>Span of the array</returns>
public Span<T> AsSpan() => Length == 0 ? Span<T>.Empty : MemoryMarshal.CreateSpan(ref this[0], Length);
/// <summary>
/// Gets the array base pointer.
/// </summary>
/// <returns>Base pointer</returns>
public T* ToPointer() => (T*)_ptr;
public override bool Equals(object obj)
{
return obj is ArrayPtr<T> other && Equals(other);
}
public bool Equals([AllowNull] ArrayPtr<T> other)
{
return _ptr == other._ptr && Length == other.Length;
}
public override int GetHashCode()
{
return HashCode.Combine(_ptr, Length);
}
public static bool operator ==(ArrayPtr<T> left, ArrayPtr<T> right)
{
return left.Equals(right);
}
public static bool operator !=(ArrayPtr<T> left, ArrayPtr<T> right)
{
return !(left == right);
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Common.Memory
{
public class Box<T> where T : unmanaged
{
public T Data;
public Box()
{
Data = new T();
}
}
}

View file

@ -0,0 +1,51 @@
using System;
using System.Buffers;
using System.Threading;
namespace Ryujinx.Common.Memory
{
public sealed partial class ByteMemoryPool
{
/// <summary>
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
/// with a length of the requested size.
/// </summary>
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
{
private byte[] _array;
private readonly int _length;
public ByteMemoryPoolBuffer(int length)
{
_array = ArrayPool<byte>.Shared.Rent(length);
_length = length;
}
/// <summary>
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
/// </summary>
public Memory<byte> Memory
{
get
{
byte[] array = _array;
ObjectDisposedException.ThrowIf(array is null, this);
return new Memory<byte>(array, 0, _length);
}
}
public void Dispose()
{
var array = Interlocked.Exchange(ref _array, null);
if (array != null)
{
ArrayPool<byte>.Shared.Return(array);
}
}
}
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.Buffers;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Provides a pool of re-usable byte array instances.
/// </summary>
public sealed partial class ByteMemoryPool
{
private static readonly ByteMemoryPool _shared = new ByteMemoryPool();
/// <summary>
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
/// the <see cref="ByteMemoryPool.Shared"/> instance.
/// </summary>
private ByteMemoryPool()
{
// No implementation
}
/// <summary>
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
/// </summary>
public static ByteMemoryPool Shared => _shared;
/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
public int MaxBufferSize => Array.MaxLength;
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(long length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(ulong length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(int length)
=> RentImpl(length);
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(long length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(ulong length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(int length)
{
var buffer = RentImpl(length);
buffer.Memory.Span.Clear();
return buffer;
}
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)
{
throw new ArgumentOutOfRangeException(nameof(length), length, null);
}
return new ByteMemoryPoolBuffer(length);
}
}
}

View file

@ -0,0 +1,21 @@
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Array interface.
/// </summary>
/// <typeparam name="T">Element type</typeparam>
public interface IArray<T> where T : unmanaged
{
/// <summary>
/// Used to index the array.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element at the specified index</returns>
ref T this[int index] { get; }
/// <summary>
/// Number of elements on the array.
/// </summary>
int Length { get; }
}
}

View file

@ -0,0 +1,99 @@
using Microsoft.IO;
using System;
namespace Ryujinx.Common.Memory
{
public static class MemoryStreamManager
{
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
/// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
/// </summary>
public static class Shared
{
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
/// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream()
=> new RecyclableMemoryStream(_shared);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(byte[] buffer)
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
=> GetStream(Guid.NewGuid(), null, buffer);
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
stream.Write(buffer);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <param name="offset">The offset from the start of the buffer to copy from</param>
/// <param name="count">The number of bytes to copy from the buffer</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, count);
stream.Write(buffer, offset, count);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
}
}
}

View file

@ -0,0 +1,80 @@
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
namespace Ryujinx.Common.Memory.PartialUnmaps
{
/// <summary>
/// A simple implementation of a ReaderWriterLock which can be used from native code.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct NativeReaderWriterLock
{
public int WriteLock;
public int ReaderCount;
public static int WriteLockOffset;
public static int ReaderCountOffset;
/// <summary>
/// Populates the field offsets for use when emitting native code.
/// </summary>
static NativeReaderWriterLock()
{
NativeReaderWriterLock instance = new NativeReaderWriterLock();
WriteLockOffset = OffsetOf(ref instance, ref instance.WriteLock);
ReaderCountOffset = OffsetOf(ref instance, ref instance.ReaderCount);
}
/// <summary>
/// Acquires the reader lock.
/// </summary>
public void AcquireReaderLock()
{
// Must take write lock for a very short time to become a reader.
while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) { }
Interlocked.Increment(ref ReaderCount);
Interlocked.Exchange(ref WriteLock, 0);
}
/// <summary>
/// Releases the reader lock.
/// </summary>
public void ReleaseReaderLock()
{
Interlocked.Decrement(ref ReaderCount);
}
/// <summary>
/// Upgrades to a writer lock. The reader lock is temporarily released while obtaining the writer lock.
/// </summary>
public void UpgradeToWriterLock()
{
// Prevent any more threads from entering reader.
// If the write lock is already taken, wait for it to not be taken.
Interlocked.Decrement(ref ReaderCount);
while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) { }
// Wait for reader count to drop to 0, then take the lock again as the only reader.
while (Interlocked.CompareExchange(ref ReaderCount, 1, 0) != 0) { }
}
/// <summary>
/// Downgrades from a writer lock, back to a reader one.
/// </summary>
public void DowngradeFromWriterLock()
{
// Release the WriteLock.
Interlocked.Exchange(ref WriteLock, 0);
}
}
}

View file

@ -0,0 +1,20 @@
using System.Runtime.CompilerServices;
namespace Ryujinx.Common.Memory.PartialUnmaps
{
static class PartialUnmapHelpers
{
/// <summary>
/// Calculates a byte offset of a given field within a struct.
/// </summary>
/// <typeparam name="T">Struct type</typeparam>
/// <typeparam name="T2">Field type</typeparam>
/// <param name="storage">Parent struct</param>
/// <param name="target">Field</param>
/// <returns>The byte offset of the given field in the given struct</returns>
public static int OffsetOf<T, T2>(ref T2 storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As<T2, T>(ref storage), ref target);
}
}
}

View file

@ -0,0 +1,163 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
using System.Threading;
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
namespace Ryujinx.Common.Memory.PartialUnmaps
{
/// <summary>
/// State for partial unmaps. Intended to be used on Windows.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public partial struct PartialUnmapState
{
public NativeReaderWriterLock PartialUnmapLock;
public int PartialUnmapsCount;
public ThreadLocalMap<int> LocalCounts;
public readonly static int PartialUnmapLockOffset;
public readonly static int PartialUnmapsCountOffset;
public readonly static int LocalCountsOffset;
public readonly static IntPtr GlobalState;
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll")]
private static partial int GetCurrentThreadId();
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId);
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs (UnmanagedType.Bool)]
private static partial bool CloseHandle(IntPtr hObject);
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
/// <summary>
/// Creates a global static PartialUnmapState and populates the field offsets.
/// </summary>
static unsafe PartialUnmapState()
{
PartialUnmapState instance = new PartialUnmapState();
PartialUnmapLockOffset = OffsetOf(ref instance, ref instance.PartialUnmapLock);
PartialUnmapsCountOffset = OffsetOf(ref instance, ref instance.PartialUnmapsCount);
LocalCountsOffset = OffsetOf(ref instance, ref instance.LocalCounts);
int size = Unsafe.SizeOf<PartialUnmapState>();
GlobalState = Marshal.AllocHGlobal(size);
Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size);
}
/// <summary>
/// Resets the global state.
/// </summary>
public static unsafe void Reset()
{
int size = Unsafe.SizeOf<PartialUnmapState>();
Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size);
}
/// <summary>
/// Gets a reference to the global state.
/// </summary>
/// <returns>A reference to the global state</returns>
public static unsafe ref PartialUnmapState GetRef()
{
return ref Unsafe.AsRef<PartialUnmapState>((void*)GlobalState);
}
/// <summary>
/// Checks if an access violation handler should retry execution due to a fault caused by partial unmap.
/// </summary>
/// <remarks>
/// Due to Windows limitations, <see cref="UnmapView"/> might need to unmap more memory than requested.
/// The additional memory that was unmapped is later remapped, however this leaves a time gap where the
/// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the
/// access violation and retrying if it happened between the unmap and remap operation.
/// This method can be used to decide if retrying in such cases is necessary or not.
///
/// This version of the function is not used, but serves as a reference for the native
/// implementation in ARMeilleure.
/// </remarks>
/// <returns>True if execution should be retried, false otherwise</returns>
[SupportedOSPlatform("windows")]
public bool RetryFromAccessViolation()
{
PartialUnmapLock.AcquireReaderLock();
int threadID = GetCurrentThreadId();
int threadIndex = LocalCounts.GetOrReserve(threadID, 0);
if (threadIndex == -1)
{
// Out of thread local space... try again later.
PartialUnmapLock.ReleaseReaderLock();
return true;
}
ref int threadLocalPartialUnmapsCount = ref LocalCounts.GetValue(threadIndex);
bool retry = threadLocalPartialUnmapsCount != PartialUnmapsCount;
if (retry)
{
threadLocalPartialUnmapsCount = PartialUnmapsCount;
}
PartialUnmapLock.ReleaseReaderLock();
return retry;
}
/// <summary>
/// Iterates and trims threads in the thread -> count map that
/// are no longer active.
/// </summary>
[SupportedOSPlatform("windows")]
public void TrimThreads()
{
const uint ExitCodeStillActive = 259;
const int ThreadQueryInformation = 0x40;
Span<int> ids = LocalCounts.ThreadIds.AsSpan();
for (int i = 0; i < ids.Length; i++)
{
int id = ids[i];
if (id != 0)
{
IntPtr handle = OpenThread(ThreadQueryInformation, false, (uint)id);
if (handle == IntPtr.Zero)
{
Interlocked.CompareExchange(ref ids[i], 0, id);
}
else
{
GetExitCodeThread(handle, out uint exitCode);
if (exitCode != ExitCodeStillActive)
{
Interlocked.CompareExchange(ref ids[i], 0, id);
}
CloseHandle(handle);
}
}
}
}
}
}

View file

@ -0,0 +1,92 @@
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
namespace Ryujinx.Common.Memory.PartialUnmaps
{
/// <summary>
/// A simple fixed size thread safe map that can be used from native code.
/// Integer thread IDs map to corresponding structs.
/// </summary>
/// <typeparam name="T">The value type for the map</typeparam>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ThreadLocalMap<T> where T : unmanaged
{
public const int MapSize = 20;
public Array20<int> ThreadIds;
public Array20<T> Structs;
public static int ThreadIdsOffset;
public static int StructsOffset;
/// <summary>
/// Populates the field offsets for use when emitting native code.
/// </summary>
static ThreadLocalMap()
{
ThreadLocalMap<T> instance = new ThreadLocalMap<T>();
ThreadIdsOffset = OffsetOf(ref instance, ref instance.ThreadIds);
StructsOffset = OffsetOf(ref instance, ref instance.Structs);
}
/// <summary>
/// Gets the index of a given thread ID in the map, or reserves one.
/// When reserving a struct, its value is set to the given initial value.
/// Returns -1 when there is no space to reserve a new entry.
/// </summary>
/// <param name="threadId">Thread ID to use as a key</param>
/// <param name="initial">Initial value of the associated struct.</param>
/// <returns>The index of the entry, or -1 if none</returns>
public int GetOrReserve(int threadId, T initial)
{
// Try get a match first.
for (int i = 0; i < MapSize; i++)
{
int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, threadId);
if (compare == threadId)
{
return i;
}
}
// Try get a free entry. Since the id is assumed to be unique to this thread, we know it doesn't exist yet.
for (int i = 0; i < MapSize; i++)
{
int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, 0);
if (compare == 0)
{
Structs[i] = initial;
return i;
}
}
return -1;
}
/// <summary>
/// Gets the struct value for a given map entry.
/// </summary>
/// <param name="index">Index of the entry</param>
/// <returns>A reference to the struct value</returns>
public ref T GetValue(int index)
{
return ref Structs[index];
}
/// <summary>
/// Releases an entry from the map.
/// </summary>
/// <param name="index">Index of the entry to release</param>
public void Release(int index)
{
Interlocked.Exchange(ref ThreadIds[index], 0);
}
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Represents a pointer to an unmanaged resource.
/// </summary>
/// <typeparam name="T">Type of the unmanaged resource</typeparam>
public unsafe struct Ptr<T> : IEquatable<Ptr<T>> where T : unmanaged
{
private IntPtr _ptr;
/// <summary>
/// Null pointer.
/// </summary>
public static Ptr<T> Null => new Ptr<T>() { _ptr = IntPtr.Zero };
/// <summary>
/// True if the pointer is null, false otherwise.
/// </summary>
public bool IsNull => _ptr == IntPtr.Zero;
/// <summary>
/// Gets a reference to the value.
/// </summary>
public ref T Value => ref Unsafe.AsRef<T>((void*)_ptr);
/// <summary>
/// Creates a new pointer to an unmanaged resource.
/// </summary>
/// <remarks>
/// For data on the heap, proper pinning is necessary during
/// use. Failure to do so will result in memory corruption and crashes.
/// </remarks>
/// <param name="value">Reference to the unmanaged resource</param>
public Ptr(ref T value)
{
_ptr = (IntPtr)Unsafe.AsPointer(ref value);
}
public override bool Equals(object obj)
{
return obj is Ptr<T> other && Equals(other);
}
public bool Equals([AllowNull] Ptr<T> other)
{
return _ptr == other._ptr;
}
public override int GetHashCode()
{
return _ptr.GetHashCode();
}
public static bool operator ==(Ptr<T> left, Ptr<T> right)
{
return left.Equals(right);
}
public static bool operator !=(Ptr<T> left, Ptr<T> right)
{
return !(left == right);
}
}
}

View file

@ -0,0 +1,89 @@
using System;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A struct that can represent both a Span and Array.
/// This is useful to keep the Array representation when possible to avoid copies.
/// </summary>
/// <typeparam name="T">Element Type</typeparam>
public readonly ref struct SpanOrArray<T> where T : unmanaged
{
public readonly T[] Array;
public readonly ReadOnlySpan<T> Span;
/// <summary>
/// Create a new SpanOrArray from an array.
/// </summary>
/// <param name="array">Array to store</param>
public SpanOrArray(T[] array)
{
Array = array;
Span = ReadOnlySpan<T>.Empty;
}
/// <summary>
/// Create a new SpanOrArray from a readonly span.
/// </summary>
/// <param name="array">Span to store</param>
public SpanOrArray(ReadOnlySpan<T> span)
{
Array = null;
Span = span;
}
/// <summary>
/// Return the contained array, or convert the span if necessary.
/// </summary>
/// <returns>An array containing the data</returns>
public T[] ToArray()
{
return Array ?? Span.ToArray();
}
/// <summary>
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
/// </summary>
/// <returns>A ReadOnlySpan containing the data</returns>
public ReadOnlySpan<T> AsSpan()
{
return Array ?? Span;
}
/// <summary>
/// Cast an array to a SpanOrArray.
/// </summary>
/// <param name="array">Source array</param>
public static implicit operator SpanOrArray<T>(T[] array)
{
return new SpanOrArray<T>(array);
}
/// <summary>
/// Cast a ReadOnlySpan to a SpanOrArray.
/// </summary>
/// <param name="span">Source ReadOnlySpan</param>
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a Span to a SpanOrArray.
/// </summary>
/// <param name="span">Source Span</param>
public static implicit operator SpanOrArray<T>(Span<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a SpanOrArray to a ReadOnlySpan
/// </summary>
/// <param name="spanOrArray">Source SpanOrArray</param>
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
{
return spanOrArray.AsSpan();
}
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
public ref struct SpanReader
{
private ReadOnlySpan<byte> _input;
public int Length => _input.Length;
public SpanReader(ReadOnlySpan<byte> input)
{
_input = input;
}
public T Read<T>() where T : unmanaged
{
T value = MemoryMarshal.Cast<byte, T>(_input)[0];
_input = _input.Slice(Unsafe.SizeOf<T>());
return value;
}
public ReadOnlySpan<byte> GetSpan(int size)
{
ReadOnlySpan<byte> data = _input.Slice(0, size);
_input = _input.Slice(size);
return data;
}
public ReadOnlySpan<byte> GetSpanSafe(int size)
{
return GetSpan((int)Math.Min((uint)_input.Length, (uint)size));
}
public T ReadAt<T>(int offset) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(_input.Slice(offset))[0];
}
public ReadOnlySpan<byte> GetSpanAt(int offset, int size)
{
return _input.Slice(offset, size);
}
public void Skip(int size)
{
_input = _input.Slice(size);
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
public ref struct SpanWriter
{
private Span<byte> _output;
public int Length => _output.Length;
public SpanWriter(Span<byte> output)
{
_output = output;
}
public void Write<T>(T value) where T : unmanaged
{
MemoryMarshal.Cast<byte, T>(_output)[0] = value;
_output = _output.Slice(Unsafe.SizeOf<T>());
}
public void Write(ReadOnlySpan<byte> data)
{
data.CopyTo(_output.Slice(0, data.Length));
_output = _output.Slice(data.Length);
}
public void WriteAt<T>(int offset, T value) where T : unmanaged
{
MemoryMarshal.Cast<byte, T>(_output.Slice(offset))[0] = value;
}
public void WriteAt(int offset, ReadOnlySpan<byte> data)
{
data.CopyTo(_output.Slice(offset, data.Length));
}
public void Skip(int size)
{
_output = _output.Slice(size);
}
}
}

View file

@ -0,0 +1,654 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
public struct Array1<T> : IArray<T> where T : unmanaged
{
T _e0;
public int Length => 1;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 1);
}
public struct Array2<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array1<T> _other;
#pragma warning restore CS0169
public int Length => 2;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 2);
}
public struct Array3<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array2<T> _other;
#pragma warning restore CS0169
public int Length => 3;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 3);
}
public struct Array4<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array3<T> _other;
#pragma warning restore CS0169
public int Length => 4;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 4);
}
public struct Array5<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array4<T> _other;
#pragma warning restore CS0169
public int Length => 5;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 5);
}
public struct Array6<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array5<T> _other;
#pragma warning restore CS0169
public int Length => 6;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 6);
}
public struct Array7<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array6<T> _other;
#pragma warning restore CS0169
public int Length => 7;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 7);
}
public struct Array8<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array7<T> _other;
#pragma warning restore CS0169
public int Length => 8;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 8);
}
public struct Array9<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array8<T> _other;
#pragma warning restore CS0169
public int Length => 9;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 9);
}
public struct Array10<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array9<T> _other;
#pragma warning restore CS0169
public int Length => 10;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 10);
}
public struct Array11<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array10<T> _other;
#pragma warning restore CS0169
public int Length => 11;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 11);
}
public struct Array12<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array11<T> _other;
#pragma warning restore CS0169
public int Length => 12;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 12);
}
public struct Array13<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array12<T> _other;
#pragma warning restore CS0169
public int Length => 13;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 13);
}
public struct Array14<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array13<T> _other;
#pragma warning restore CS0169
public int Length => 14;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 14);
}
public struct Array15<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array14<T> _other;
#pragma warning restore CS0169
public int Length => 15;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 15);
}
public struct Array16<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array15<T> _other;
#pragma warning restore CS0169
public int Length => 16;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 16);
}
public struct Array17<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array16<T> _other;
#pragma warning restore CS0169
public int Length => 17;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 17);
}
public struct Array18<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array17<T> _other;
#pragma warning restore CS0169
public int Length => 18;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 18);
}
public struct Array19<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array18<T> _other;
#pragma warning restore CS0169
public int Length => 19;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 19);
}
public struct Array20<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array19<T> _other;
#pragma warning restore CS0169
public int Length => 20;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 20);
}
public struct Array21<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array20<T> _other;
#pragma warning restore CS0169
public int Length => 21;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 21);
}
public struct Array22<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array21<T> _other;
#pragma warning restore CS0169
public int Length => 22;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 22);
}
public struct Array23<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array22<T> _other;
#pragma warning restore CS0169
public int Length => 23;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 23);
}
public struct Array24<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array23<T> _other;
#pragma warning restore CS0169
public int Length => 24;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 24);
}
public struct Array25<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array24<T> _other;
#pragma warning restore CS0169
public int Length => 25;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 25);
}
public struct Array26<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array25<T> _other;
#pragma warning restore CS0169
public int Length => 26;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 26);
}
public struct Array27<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array26<T> _other;
#pragma warning restore CS0169
public int Length => 27;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 27);
}
public struct Array28<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array27<T> _other;
#pragma warning restore CS0169
public int Length => 28;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 28);
}
public struct Array29<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array28<T> _other;
#pragma warning restore CS0169
public int Length => 29;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 29);
}
public struct Array30<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array29<T> _other;
#pragma warning restore CS0169
public int Length => 30;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 30);
}
public struct Array31<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array30<T> _other;
#pragma warning restore CS0169
public int Length => 31;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 31);
}
public struct Array32<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array31<T> _other;
#pragma warning restore CS0169
public int Length => 32;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 32);
}
public struct Array33<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array32<T> _other;
#pragma warning restore CS0169
public int Length => 33;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 33);
}
public struct Array34<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array33<T> _other;
#pragma warning restore CS0169
public int Length => 34;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 34);
}
public struct Array35<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array34<T> _other;
#pragma warning restore CS0169
public int Length => 35;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 35);
}
public struct Array36<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array35<T> _other;
#pragma warning restore CS0169
public int Length => 36;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 36);
}
public struct Array37<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array36<T> _other;
#pragma warning restore CS0169
public int Length => 37;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 37);
}
public struct Array38<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array37<T> _other;
#pragma warning restore CS0169
public int Length => 38;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 38);
}
public struct Array39<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array38<T> _other;
#pragma warning restore CS0169
public int Length => 39;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 39);
}
public struct Array40<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array39<T> _other;
#pragma warning restore CS0169
public int Length => 40;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 40);
}
public struct Array41<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array40<T> _other;
#pragma warning restore CS0169
public int Length => 41;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 41);
}
public struct Array42<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array41<T> _other;
#pragma warning restore CS0169
public int Length => 42;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 42);
}
public struct Array43<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array42<T> _other;
#pragma warning restore CS0169
public int Length => 43;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 43);
}
public struct Array44<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array43<T> _other;
#pragma warning restore CS0169
public int Length => 44;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 44);
}
public struct Array45<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array44<T> _other;
#pragma warning restore CS0169
public int Length => 45;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 45);
}
public struct Array46<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array45<T> _other;
#pragma warning restore CS0169
public int Length => 46;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 46);
}
public struct Array47<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array46<T> _other;
#pragma warning restore CS0169
public int Length => 47;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 47);
}
public struct Array48<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array47<T> _other;
#pragma warning restore CS0169
public int Length => 48;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 48);
}
public struct Array49<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array48<T> _other;
#pragma warning restore CS0169
public int Length => 49;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 49);
}
public struct Array50<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array49<T> _other;
#pragma warning restore CS0169
public int Length => 50;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 50);
}
public struct Array51<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array50<T> _other;
#pragma warning restore CS0169
public int Length => 51;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 51);
}
public struct Array52<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array51<T> _other;
#pragma warning restore CS0169
public int Length => 52;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 52);
}
public struct Array53<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array52<T> _other;
#pragma warning restore CS0169
public int Length => 53;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 53);
}
public struct Array54<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array53<T> _other;
#pragma warning restore CS0169
public int Length => 54;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 54);
}
public struct Array55<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array54<T> _other;
#pragma warning restore CS0169
public int Length => 55;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 55);
}
public struct Array56<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array55<T> _other;
#pragma warning restore CS0169
public int Length => 56;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 56);
}
public struct Array57<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array56<T> _other;
#pragma warning restore CS0169
public int Length => 57;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 57);
}
public struct Array58<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array57<T> _other;
#pragma warning restore CS0169
public int Length => 58;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 58);
}
public struct Array59<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array58<T> _other;
#pragma warning restore CS0169
public int Length => 59;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 59);
}
public struct Array60<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array59<T> _other;
#pragma warning restore CS0169
public int Length => 60;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 60);
}
public struct Array61<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array60<T> _other;
#pragma warning restore CS0169
public int Length => 61;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 61);
}
public struct Array62<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array61<T> _other;
#pragma warning restore CS0169
public int Length => 62;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 62);
}
public struct Array63<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array62<T> _other;
#pragma warning restore CS0169
public int Length => 63;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 63);
}
public struct Array64<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array63<T> _other;
#pragma warning restore CS0169
public int Length => 64;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 64);
}
public struct Array73<T> : IArray<T> where T : unmanaged
{
#pragma warning disable CS0169
T _e0;
Array64<T> _other;
Array8<T> _other2;
#pragma warning restore CS0169
public int Length => 73;
public ref T this[int index] => ref AsSpan()[index];
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 73);
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray128 : IArray<byte>
{
private const int Size = 128;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray256 : IArray<byte>
{
private const int Size = 256;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray512 : IArray<byte>
{
private const int Size = 512;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray1024 : IArray<byte>
{
private const int Size = 1024;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray2048 : IArray<byte>
{
private const int Size = 2048;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray4096 : IArray<byte>
{
private const int Size = 4096;
byte _element;
public int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
}

View file

@ -0,0 +1,82 @@
using System.Diagnostics;
namespace Ryujinx.Common
{
public static class PerformanceCounter
{
private static double _ticksToNs;
/// <summary>
/// Represents the number of ticks in 1 day.
/// </summary>
public static long TicksPerDay { get; }
/// <summary>
/// Represents the number of ticks in 1 hour.
/// </summary>
public static long TicksPerHour { get; }
/// <summary>
/// Represents the number of ticks in 1 minute.
/// </summary>
public static long TicksPerMinute { get; }
/// <summary>
/// Represents the number of ticks in 1 second.
/// </summary>
public static long TicksPerSecond { get; }
/// <summary>
/// Represents the number of ticks in 1 millisecond.
/// </summary>
public static long TicksPerMillisecond { get; }
/// <summary>
/// Gets the number of ticks elapsed since the system started.
/// </summary>
public static long ElapsedTicks
{
get
{
return Stopwatch.GetTimestamp();
}
}
/// <summary>
/// Gets the number of milliseconds elapsed since the system started.
/// </summary>
public static long ElapsedMilliseconds
{
get
{
long timestamp = Stopwatch.GetTimestamp();
return timestamp / TicksPerMillisecond;
}
}
/// <summary>
/// Gets the number of nanoseconds elapsed since the system started.
/// </summary>
public static long ElapsedNanoseconds
{
get
{
long timestamp = Stopwatch.GetTimestamp();
return (long)(timestamp * _ticksToNs);
}
}
static PerformanceCounter()
{
TicksPerMillisecond = Stopwatch.Frequency / 1000;
TicksPerSecond = Stopwatch.Frequency;
TicksPerMinute = TicksPerSecond * 60;
TicksPerHour = TicksPerMinute * 60;
TicksPerDay = TicksPerHour * 24;
_ticksToNs = 1000000000.0 / Stopwatch.Frequency;
}
}
}

View file

@ -0,0 +1,75 @@
using System;
using System.Threading;
namespace Ryujinx.Common
{
public class ObjectPool<T>
where T : class
{
private T _firstItem;
private readonly T[] _items;
private readonly Func<T> _factory;
public ObjectPool(Func<T> factory, int size)
{
_items = new T[size - 1];
_factory = factory;
}
public T Allocate()
{
T instance = _firstItem;
if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))
{
instance = AllocateInternal();
}
return instance;
}
private T AllocateInternal()
{
T[] items = _items;
for (int i = 0; i < items.Length; i++)
{
T instance = items[i];
if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance))
{
return instance;
}
}
return _factory();
}
public void Release(T obj)
{
if (_firstItem == null)
{
_firstItem = obj;
}
else
{
ReleaseInternal(obj);
}
}
private void ReleaseInternal(T obj)
{
T[] items = _items;
for (int i = 0; i < items.Length; i++)
{
if (items[i] == null)
{
items[i] = obj;
break;
}
}
}
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Common
{
public static class SharedPools
{
private static class DefaultPool<T>
where T : class, new()
{
public static readonly ObjectPool<T> Instance = new ObjectPool<T>(() => new T(), 20);
}
public static ObjectPool<T> Default<T>()
where T : class, new()
{
return DefaultPool<T>.Instance;
}
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Ryujinx.Common.Pools
{
public static class ThreadStaticArray<T>
{
[ThreadStatic]
private static T[] _array;
public static ref T[] Get()
{
if (_array == null)
{
_array = new T[1];
}
return ref _array;
}
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Threading;
namespace Ryujinx.Common
{
public class ReactiveObject<T>
{
private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
private bool _isInitialized = false;
private T _value;
public event EventHandler<ReactiveEventArgs<T>> Event;
public T Value
{
get
{
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
T value = _value;
_readerWriterLock.ReleaseReaderLock();
return value;
}
set
{
_readerWriterLock.AcquireWriterLock(Timeout.Infinite);
T oldValue = _value;
bool oldIsInitialized = _isInitialized;
_isInitialized = true;
_value = value;
_readerWriterLock.ReleaseWriterLock();
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
{
Event?.Invoke(this, new ReactiveEventArgs<T>(oldValue, value));
}
}
}
public static implicit operator T(ReactiveObject<T> obj)
{
return obj.Value;
}
}
public class ReactiveEventArgs<T>
{
public T OldValue { get; }
public T NewValue { get; }
public ReactiveEventArgs(T oldValue, T newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Common
{
public class ReferenceEqualityComparer<T> : IEqualityComparer<T>
where T : class
{
public bool Equals(T x, T y)
{
return x == y;
}
public int GetHashCode([DisallowNull] T obj)
{
return obj.GetHashCode();
}
}
}

View file

@ -0,0 +1,60 @@
using Ryujinx.Common.Configuration;
using System;
using System.Reflection;
namespace Ryujinx.Common
{
// DO NOT EDIT, filled by CI
public static class ReleaseInformation
{
private const string FlatHubChannelOwner = "flathub";
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
public static string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
public static string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
public static bool IsValid()
{
return !BuildGitHash.StartsWith("%%") &&
!ReleaseChannelName.StartsWith("%%") &&
!ReleaseChannelOwner.StartsWith("%%") &&
!ReleaseChannelRepo.StartsWith("%%");
}
public static bool IsFlatHubBuild()
{
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
}
public static string GetVersion()
{
if (IsValid())
{
return BuildVersion;
}
else
{
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}
}
#if FORCE_EXTERNAL_BASE_DIR
public static string GetBaseApplicationDirectory()
{
return AppDataManager.BaseDirPath;
}
#else
public static string GetBaseApplicationDirectory()
{
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
{
return AppDataManager.BaseDirPath;
}
return AppDomain.CurrentDomain.BaseDirectory;
}
#endif
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,80 @@
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.Versioning;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("linux")]
class LinuxSystemInfo : SystemInfo
{
internal LinuxSystemInfo()
{
string cpuName = GetCpuidCpuName();
if (cpuName == null)
{
var cpuDict = new Dictionary<string, string>(StringComparer.Ordinal)
{
["model name"] = null,
["Processor"] = null,
["Hardware"] = null
};
ParseKeyValues("/proc/cpuinfo", cpuDict);
cpuName = cpuDict["model name"] ?? cpuDict["Processor"] ?? cpuDict["Hardware"] ?? "Unknown";
}
var memDict = new Dictionary<string, string>(StringComparer.Ordinal)
{
["MemTotal"] = null,
["MemAvailable"] = null
};
ParseKeyValues("/proc/meminfo", memDict);
// Entries are in KiB
ulong.TryParse(memDict["MemTotal"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong totalKiB);
ulong.TryParse(memDict["MemAvailable"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong availableKiB);
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
RamTotal = totalKiB * 1024;
RamAvailable = availableKiB * 1024;
}
private static void ParseKeyValues(string filePath, Dictionary<string, string> itemDict)
{
if (!File.Exists(filePath))
{
Logger.Error?.Print(LogClass.Application, $"File \"{filePath}\" not found");
return;
}
int count = itemDict.Count;
using (StreamReader file = new StreamReader(filePath))
{
string line;
while ((line = file.ReadLine()) != null)
{
string[] kvPair = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (kvPair.Length < 2) continue;
string key = kvPair[0];
if (itemDict.TryGetValue(key, out string value) && value == null)
{
itemDict[key] = kvPair[1];
if (--count <= 0) break;
}
}
}
}
}
}

View file

@ -0,0 +1,157 @@
using Ryujinx.Common.Logging;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("macos")]
partial class MacOSSystemInfo : SystemInfo
{
internal MacOSSystemInfo()
{
string cpuName = GetCpuidCpuName();
if (cpuName == null && sysctlbyname("machdep.cpu.brand_string", out cpuName) != 0)
{
cpuName = "Unknown";
}
ulong totalRAM = 0;
if (sysctlbyname("hw.memsize", ref totalRAM) != 0) // Bytes
{
totalRAM = 0;
}
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
RamTotal = totalRAM;
RamAvailable = GetVMInfoAvailableMemory();
}
static ulong GetVMInfoAvailableMemory()
{
var port = mach_host_self();
uint pageSize = 0;
var result = host_page_size(port, ref pageSize);
if (result != 0)
{
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}");
return 0;
}
const int flavor = 4; // HOST_VM_INFO64
uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT
VMStatistics64 stats = new();
result = host_statistics64(port, flavor, ref stats, ref count);
if (result != 0)
{
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}");
return 0;
}
return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize;
}
private const string SystemLibraryName = "libSystem.dylib";
[LibraryImport(SystemLibraryName, SetLastError = true)]
private static partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
{
if (sysctlbyname(name, oldValue, ref oldSize, IntPtr.Zero, 0) == -1)
{
int err = Marshal.GetLastWin32Error();
Logger.Error?.Print(LogClass.Application, $"Cannot retrieve '{name}'. Error Code {err}");
return err;
}
return 0;
}
private static int sysctlbyname<T>(string name, ref T oldValue)
{
unsafe
{
ulong oldValueSize = (ulong)Unsafe.SizeOf<T>();
return sysctlbyname(name, (IntPtr)Unsafe.AsPointer(ref oldValue), ref oldValueSize);
}
}
private static int sysctlbyname(string name, out string oldValue)
{
oldValue = default;
ulong strSize = 0;
int res = sysctlbyname(name, IntPtr.Zero, ref strSize);
if (res == 0)
{
byte[] rawData = new byte[strSize];
unsafe
{
fixed (byte* rawDataPtr = rawData)
{
res = sysctlbyname(name, (IntPtr)rawDataPtr, ref strSize);
}
if (res == 0)
{
oldValue = Encoding.ASCII.GetString(rawData);
}
}
}
return res;
}
[LibraryImport(SystemLibraryName, SetLastError = true)]
private static partial uint mach_host_self();
[LibraryImport(SystemLibraryName, SetLastError = true)]
private static partial int host_page_size(uint host, ref uint out_page_size);
[StructLayout(LayoutKind.Sequential, Pack = 8)]
struct VMStatistics64
{
public uint FreeCount;
public uint ActiveCount;
public uint InactiveCount;
public uint WireCount;
public ulong ZeroFillCount;
public ulong Reactivations;
public ulong Pageins;
public ulong Pageouts;
public ulong Faults;
public ulong CowFaults;
public ulong Lookups;
public ulong Hits;
public ulong Purges;
public uint PurgeableCount;
public uint SpeculativeCount;
public ulong Decompressions;
public ulong Compressions;
public ulong Swapins;
public ulong Swapouts;
public uint CompressorPageCount;
public uint ThrottledCount;
public uint ExternalPageCount;
public uint InternalPageCount;
public ulong TotalUncompressedPagesInCompressor;
}
[LibraryImport(SystemLibraryName, SetLastError = true)]
private static partial int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt);
}
}

View file

@ -0,0 +1,80 @@
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
namespace Ryujinx.Common.SystemInfo
{
public class SystemInfo
{
public string OsDescription { get; protected set; }
public string CpuName { get; protected set; }
public ulong RamTotal { get; protected set; }
public ulong RamAvailable { get; protected set; }
protected static int LogicalCoreCount => Environment.ProcessorCount;
protected SystemInfo()
{
OsDescription = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})";
CpuName = "Unknown";
}
private static string ToMiBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : $"{bytesValue / 1024 / 1024} MiB";
public void Print()
{
Logger.Notice.Print(LogClass.Application, $"Operating System: {OsDescription}");
Logger.Notice.Print(LogClass.Application, $"CPU: {CpuName}");
Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToMiBString(RamTotal)} ; Available {ToMiBString(RamAvailable)}");
}
public static SystemInfo Gather()
{
if (OperatingSystem.IsWindows())
{
return new WindowsSystemInfo();
}
else if (OperatingSystem.IsLinux())
{
return new LinuxSystemInfo();
}
else if (OperatingSystem.IsMacOS())
{
return new MacOSSystemInfo();
}
else
{
Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform");
return new SystemInfo();
}
}
// x86 exposes a 48 byte ASCII "CPU brand" string via CPUID leaves 0x80000002-0x80000004.
internal static string GetCpuidCpuName()
{
if (!X86Base.IsSupported)
{
return null;
}
// Check if CPU supports the query
if ((uint)X86Base.CpuId(unchecked((int)0x80000000), 0).Eax < 0x80000004)
{
return null;
}
int[] regs = new int[12];
for (uint i = 0; i < 3; ++i)
{
(regs[4 * i], regs[4 * i + 1], regs[4 * i + 2], regs[4 * i + 3]) = X86Base.CpuId((int)(0x80000002 + i), 0);
}
string name = Encoding.ASCII.GetString(MemoryMarshal.Cast<int, byte>(regs)).Replace('\0', ' ').Trim();
return string.IsNullOrEmpty(name) ? null : name;
}
}
}

View file

@ -0,0 +1,89 @@
using Ryujinx.Common.Logging;
using System;
using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("windows")]
partial class WindowsSystemInfo : SystemInfo
{
internal WindowsSystemInfo()
{
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
(RamTotal, RamAvailable) = GetMemoryStats();
}
private static (ulong Total, ulong Available) GetMemoryStats()
{
MemoryStatusEx memStatus = new MemoryStatusEx();
if (GlobalMemoryStatusEx(ref memStatus))
{
return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes
}
else
{
Logger.Error?.Print(LogClass.Application, $"GlobalMemoryStatusEx failed. Error {Marshal.GetLastWin32Error():X}");
}
return (0, 0);
}
private static string GetCpuNameWMI()
{
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
if (cpuObjs != null)
{
foreach (var cpuObj in cpuObjs)
{
return cpuObj["Name"].ToString().Trim();
}
}
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim();
}
[StructLayout(LayoutKind.Sequential)]
private struct MemoryStatusEx
{
public uint Length;
public uint MemoryLoad;
public ulong TotalPhys;
public ulong AvailPhys;
public ulong TotalPageFile;
public ulong AvailPageFile;
public ulong TotalVirtual;
public ulong AvailVirtual;
public ulong AvailExtendedVirtual;
public MemoryStatusEx()
{
Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
}
}
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
{
try
{
return new ManagementObjectSearcher(scope, query).Get();
}
catch (PlatformNotSupportedException ex)
{
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
catch (COMException ex)
{
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
return null;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.SystemInterop
{
public partial class DisplaySleep
{
[Flags]
enum EXECUTION_STATE : uint
{
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001
}
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
static public void Prevent()
{
if (OperatingSystem.IsWindows())
{
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED);
}
}
static public void Restore()
{
if (OperatingSystem.IsWindows())
{
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
}
}
}
}

View file

@ -0,0 +1,96 @@
using Ryujinx.Common.Logging;
using System;
using System.Globalization;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.SystemInterop
{
public static partial class ForceDpiAware
{
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetProcessDPIAware();
private const string X11LibraryName = "libX11.so.6";
[LibraryImport(X11LibraryName)]
private static partial IntPtr XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display);
[LibraryImport(X11LibraryName)]
private static partial IntPtr XGetDefault(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option);
[LibraryImport(X11LibraryName)]
private static partial int XDisplayWidth(IntPtr display, int screenNumber);
[LibraryImport(X11LibraryName)]
private static partial int XDisplayWidthMM(IntPtr display, int screenNumber);
[LibraryImport(X11LibraryName)]
private static partial int XCloseDisplay(IntPtr display);
private static readonly double _standardDpiScale = 96.0;
private static readonly double _maxScaleFactor = 1.25;
/// <summary>
/// Marks the application as DPI-Aware when running on the Windows operating system.
/// </summary>
public static void Windows()
{
// Make process DPI aware for proper window sizing on high-res screens.
if (OperatingSystem.IsWindowsVersionAtLeast(6))
{
SetProcessDPIAware();
}
}
public static double GetActualScaleFactor()
{
double userDpiScale = 96.0;
try
{
if (OperatingSystem.IsWindows())
{
userDpiScale = GdiPlusHelper.GetDpiX(IntPtr.Zero);
}
else if (OperatingSystem.IsLinux())
{
string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower();
if (xdgSessionType == null || xdgSessionType == "x11")
{
IntPtr display = XOpenDisplay(null);
string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi"));
if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale))
{
userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0);
}
XCloseDisplay(display);
}
else if (xdgSessionType == "wayland")
{
// TODO
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported");
}
else
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}");
}
}
}
catch (Exception e)
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
}
return userDpiScale;
}
public static double GetWindowScaleFactor()
{
double userDpiScale = GetActualScaleFactor();
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
}
}
}

Some files were not shown because too many files have changed in this diff Show more