aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Common/Utilities
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Common/Utilities
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Common/Utilities')
-rw-r--r--src/Ryujinx.Common/Utilities/BitUtils.cs60
-rw-r--r--src/Ryujinx.Common/Utilities/BitfieldExtensions.cs57
-rw-r--r--src/Ryujinx.Common/Utilities/Buffers.cs59
-rw-r--r--src/Ryujinx.Common/Utilities/CommonJsonContext.cs11
-rw-r--r--src/Ryujinx.Common/Utilities/EmbeddedResources.cs148
-rw-r--r--src/Ryujinx.Common/Utilities/HexUtils.cs90
-rw-r--r--src/Ryujinx.Common/Utilities/JsonHelper.cs98
-rw-r--r--src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs302
-rw-r--r--src/Ryujinx.Common/Utilities/NetworkHelpers.cs66
-rw-r--r--src/Ryujinx.Common/Utilities/SpanHelpers.cs61
-rw-r--r--src/Ryujinx.Common/Utilities/StreamUtils.cs31
-rw-r--r--src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs34
-rw-r--r--src/Ryujinx.Common/Utilities/UInt128Utils.cs18
13 files changed, 1035 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/Utilities/BitUtils.cs b/src/Ryujinx.Common/Utilities/BitUtils.cs
new file mode 100644
index 00000000..f885ce84
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/BitUtils.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Numerics;
+
+namespace Ryujinx.Common
+{
+ public static class BitUtils
+ {
+ public static T AlignUp<T>(T value, T size)
+ where T : IBinaryInteger<T>
+ {
+ return (value + (size - T.One)) & -size;
+ }
+
+ public static T AlignDown<T>(T value, T size)
+ where T : IBinaryInteger<T>
+ {
+ return value & -size;
+ }
+
+ public static T DivRoundUp<T>(T value, T dividend)
+ where T : IBinaryInteger<T>
+ {
+ return (value + (dividend - T.One)) / dividend;
+ }
+
+ public static int Pow2RoundUp(int value)
+ {
+ value--;
+
+ value |= (value >> 1);
+ value |= (value >> 2);
+ value |= (value >> 4);
+ value |= (value >> 8);
+ value |= (value >> 16);
+
+ return ++value;
+ }
+
+ public static int Pow2RoundDown(int value)
+ {
+ return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
+ }
+
+ public static long ReverseBits64(long value)
+ {
+ return (long)ReverseBits64((ulong)value);
+ }
+
+ private static ulong ReverseBits64(ulong value)
+ {
+ value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((value & 0x5555555555555555) << 1 );
+ value = ((value & 0xcccccccccccccccc) >> 2 ) | ((value & 0x3333333333333333) << 2 );
+ value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((value & 0x0f0f0f0f0f0f0f0f) << 4 );
+ value = ((value & 0xff00ff00ff00ff00) >> 8 ) | ((value & 0x00ff00ff00ff00ff) << 8 );
+ value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16);
+
+ return (value >> 32) | (value << 32);
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs b/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs
new file mode 100644
index 00000000..ca429944
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs
@@ -0,0 +1,57 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class BitfieldExtensions
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Extract<T>(this T value, int lsb) where T : IBinaryInteger<T>
+ {
+ int bitSize = Unsafe.SizeOf<T>() * 8;
+ lsb &= bitSize - 1;
+
+ return !T.IsZero((value >>> lsb) & T.One);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Extract<T>(this T value, int lsb, int length) where T : IBinaryInteger<T>
+ {
+ int bitSize = Unsafe.SizeOf<T>() * 8;
+ lsb &= bitSize - 1;
+
+ return (value >>> lsb) & (~T.Zero >>> (bitSize - length));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ExtractSx<T>(this T value, int lsb, int length) where T : IBinaryInteger<T>
+ {
+ int bitSize = Unsafe.SizeOf<T>() * 8;
+ int shift = lsb & (bitSize - 1);
+
+ return (value << (bitSize - (shift + length))) >> (bitSize - length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Insert<T>(this T value, int lsb, bool toInsert) where T : IBinaryInteger<T>
+ {
+ int bitSize = Unsafe.SizeOf<T>() * 8;
+ lsb &= bitSize - 1;
+
+ T mask = T.One << lsb;
+
+ return (value & ~mask) | (toInsert ? mask : T.Zero);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Insert<T>(this T value, int lsb, int length, T toInsert) where T : IBinaryInteger<T>
+ {
+ int bitSize = Unsafe.SizeOf<T>() * 8;
+ lsb &= bitSize - 1;
+
+ T mask = (~T.Zero >>> (bitSize - length)) << lsb;
+
+ return (value & ~mask) | ((toInsert << lsb) & mask);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/Buffers.cs b/src/Ryujinx.Common/Utilities/Buffers.cs
new file mode 100644
index 00000000..d614bfc4
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/Buffers.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ [DebuggerDisplay("{ToString()}")]
+ [StructLayout(LayoutKind.Sequential, Size = 16)]
+ public struct Buffer16
+ {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
+
+ public byte this[int i]
+ {
+ get => Bytes[i];
+ set => Bytes[i] = value;
+ }
+
+ public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
+
+ // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Span<byte>(in Buffer16 value)
+ {
+ return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlySpan<byte>(in Buffer16 value)
+ {
+ return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T As<T>() where T : unmanaged
+ {
+ if (Unsafe.SizeOf<T>() > (uint)Unsafe.SizeOf<Buffer16>())
+ {
+ throw new ArgumentException();
+ }
+
+ return ref MemoryMarshal.GetReference(AsSpan<T>());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<T> AsSpan<T>() where T : unmanaged
+ {
+ return SpanHelpers.AsSpan<Buffer16, T>(ref this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly ReadOnlySpan<T> AsReadOnlySpan<T>() where T : unmanaged
+ {
+ return SpanHelpers.AsReadOnlySpan<Buffer16, T>(ref Unsafe.AsRef(in this));
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/CommonJsonContext.cs b/src/Ryujinx.Common/Utilities/CommonJsonContext.cs
new file mode 100644
index 00000000..d7b3f78c
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/CommonJsonContext.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.Common.Utilities
+{
+ [JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")]
+ [JsonSerializable(typeof(Dictionary<string, string>), TypeInfoPropertyName = "StringDictionary")]
+ public partial class CommonJsonContext : JsonSerializerContext
+ {
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs
new file mode 100644
index 00000000..22b55f16
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs
@@ -0,0 +1,148 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Common
+{
+ public static class EmbeddedResources
+ {
+ private readonly static Assembly ResourceAssembly;
+
+ static EmbeddedResources()
+ {
+ ResourceAssembly = Assembly.GetAssembly(typeof(EmbeddedResources));
+ }
+
+ public static byte[] Read(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return Read(assembly, path);
+ }
+
+ public static Task<byte[]> ReadAsync(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAsync(assembly, path);
+ }
+
+ public static byte[] Read(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ return StreamUtils.StreamToBytes(stream);
+ }
+ }
+
+ public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ return await StreamUtils.StreamToBytesAsync(stream);
+ }
+ }
+
+ public static string ReadAllText(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAllText(assembly, path);
+ }
+
+ public static Task<string> ReadAllTextAsync(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAllTextAsync(assembly, path);
+ }
+
+ public static string ReadAllText(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ }
+
+ public async static Task<string> ReadAllTextAsync(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ return await reader.ReadToEndAsync();
+ }
+ }
+ }
+
+ public static Stream GetStream(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return GetStream(assembly, path);
+ }
+
+ public static Stream GetStream(Assembly assembly, string filename)
+ {
+ var namespace_ = assembly.GetName().Name;
+ var manifestUri = namespace_ + "." + filename.Replace('/', '.');
+
+ var stream = assembly.GetManifestResourceStream(manifestUri);
+
+ return stream;
+ }
+
+ public static string[] GetAllAvailableResources(string path, string ext = "")
+ {
+ return ResolveManifestPath(path).Item1.GetManifestResourceNames()
+ .Where(r => r.EndsWith(ext))
+ .ToArray();
+ }
+
+ private static (Assembly, string) ResolveManifestPath(string filename)
+ {
+ var segments = filename.Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
+
+ if (segments.Length >= 2)
+ {
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (assembly.GetName().Name == segments[0])
+ {
+ return (assembly, segments[1]);
+ }
+ }
+ }
+
+ return (ResourceAssembly, filename);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/HexUtils.cs b/src/Ryujinx.Common/Utilities/HexUtils.cs
new file mode 100644
index 00000000..63587cea
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/HexUtils.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Text;
+
+namespace Ryujinx.Common
+{
+ public static class HexUtils
+ {
+ private static readonly char[] HexChars = "0123456789ABCDEF".ToCharArray();
+
+ private const int HexTableColumnWidth = 8;
+ private const int HexTableColumnSpace = 3;
+
+ // Modified for Ryujinx
+ // Original by Pascal Ganaye - CPOL License
+ // https://www.codeproject.com/Articles/36747/Quick-and-Dirty-HexDump-of-a-Byte-Array
+ public static string HexTable(byte[] bytes, int bytesPerLine = 16)
+ {
+ if (bytes == null)
+ {
+ return "<null>";
+ }
+
+ int bytesLength = bytes.Length;
+
+ int firstHexColumn =
+ HexTableColumnWidth
+ + HexTableColumnSpace;
+
+ int firstCharColumn = firstHexColumn
+ + bytesPerLine * HexTableColumnSpace
+ + (bytesPerLine - 1) / HexTableColumnWidth
+ + 2;
+
+ int lineLength = firstCharColumn
+ + bytesPerLine
+ + Environment.NewLine.Length;
+
+ char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray();
+
+ int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine;
+
+ StringBuilder result = new StringBuilder(expectedLines * lineLength);
+
+ for (int i = 0; i < bytesLength; i += bytesPerLine)
+ {
+ line[0] = HexChars[(i >> 28) & 0xF];
+ line[1] = HexChars[(i >> 24) & 0xF];
+ line[2] = HexChars[(i >> 20) & 0xF];
+ line[3] = HexChars[(i >> 16) & 0xF];
+ line[4] = HexChars[(i >> 12) & 0xF];
+ line[5] = HexChars[(i >> 8) & 0xF];
+ line[6] = HexChars[(i >> 4) & 0xF];
+ line[7] = HexChars[(i >> 0) & 0xF];
+
+ int hexColumn = firstHexColumn;
+ int charColumn = firstCharColumn;
+
+ for (int j = 0; j < bytesPerLine; j++)
+ {
+ if (j > 0 && (j & 7) == 0)
+ {
+ hexColumn++;
+ }
+
+ if (i + j >= bytesLength)
+ {
+ line[hexColumn] = ' ';
+ line[hexColumn + 1] = ' ';
+ line[charColumn] = ' ';
+ }
+ else
+ {
+ byte b = bytes[i + j];
+
+ line[hexColumn] = HexChars[(b >> 4) & 0xF];
+ line[hexColumn + 1] = HexChars[b & 0xF];
+ line[charColumn] = (b < 32 ? '·' : (char)b);
+ }
+
+ hexColumn += 3;
+ charColumn++;
+ }
+
+ result.Append(line);
+ }
+
+ return result.ToString();
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/JsonHelper.cs b/src/Ryujinx.Common/Utilities/JsonHelper.cs
new file mode 100644
index 00000000..9a2d6f18
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/JsonHelper.cs
@@ -0,0 +1,98 @@
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Ryujinx.Common.Utilities
+{
+ public class JsonHelper
+ {
+ private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy();
+ private const int DefaultFileWriteBufferSize = 4096;
+
+ /// <summary>
+ /// Creates new serializer options with default settings.
+ /// </summary>
+ /// <remarks>
+ /// It is REQUIRED for you to save returned options statically or as a part of static serializer context
+ /// in order to avoid performance issues. You can safely modify returned options for your case before storing.
+ /// </remarks>
+ public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true)
+ {
+ JsonSerializerOptions options = new()
+ {
+ DictionaryKeyPolicy = SnakeCasePolicy,
+ PropertyNamingPolicy = SnakeCasePolicy,
+ WriteIndented = indented,
+ AllowTrailingCommas = true,
+ ReadCommentHandling = JsonCommentHandling.Skip
+ };
+
+ return options;
+ }
+
+ public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo)
+ {
+ return JsonSerializer.Serialize(value, typeInfo);
+ }
+
+ public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
+ {
+ return JsonSerializer.Deserialize(value, typeInfo);
+ }
+
+ public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
+ {
+ using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough);
+ JsonSerializer.Serialize(file, value, typeInfo);
+ }
+
+ public static T DeserializeFromFile<T>(string filePath, JsonTypeInfo<T> typeInfo)
+ {
+ using FileStream file = File.OpenRead(filePath);
+ return JsonSerializer.Deserialize(file, typeInfo);
+ }
+
+ public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
+ {
+ JsonSerializer.Serialize(stream, value, typeInfo);
+ }
+
+ private class SnakeCaseNamingPolicy : JsonNamingPolicy
+ {
+ public override string ConvertName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ return name;
+ }
+
+ StringBuilder builder = new();
+
+ for (int i = 0; i < name.Length; i++)
+ {
+ char c = name[i];
+
+ if (char.IsUpper(c))
+ {
+ if (i == 0 || char.IsUpper(name[i - 1]))
+ {
+ builder.Append(char.ToLowerInvariant(c));
+ }
+ else
+ {
+ builder.Append('_');
+ builder.Append(char.ToLowerInvariant(c));
+ }
+ }
+ else
+ {
+ builder.Append(c);
+ }
+ }
+
+ return builder.ToString();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs b/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs
new file mode 100644
index 00000000..3714bec0
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs
@@ -0,0 +1,302 @@
+using MsgPack;
+using System;
+using System.Text;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class MessagePackObjectFormatter
+ {
+ public static string ToString(this MessagePackObject obj, bool pretty)
+ {
+ if (pretty)
+ {
+ return Format(obj);
+ }
+ else
+ {
+ return obj.ToString();
+ }
+ }
+
+ public static string Format(MessagePackObject obj)
+ {
+ var builder = new IndentedStringBuilder();
+
+ FormatMsgPackObj(obj, builder);
+
+ return builder.ToString();
+ }
+
+ private static void FormatMsgPackObj(MessagePackObject obj, IndentedStringBuilder builder)
+ {
+ if (obj.IsMap || obj.IsDictionary)
+ {
+ FormatMsgPackMap(obj, builder);
+ }
+ else if (obj.IsArray || obj.IsList)
+ {
+ FormatMsgPackArray(obj, builder);
+ }
+ else if (obj.IsNil)
+ {
+ builder.Append("null");
+ }
+ else
+ {
+ var literal = obj.ToObject();
+
+ if (literal is String)
+ {
+ builder.AppendQuotedString(obj.AsStringUtf16());
+ }
+ else if (literal is byte[] byteArray)
+ {
+ FormatByteArray(byteArray, builder);
+ }
+ else if (literal is MessagePackExtendedTypeObject extObject)
+ {
+ builder.Append('{');
+
+ // Indent
+ builder.IncreaseIndent()
+ .AppendLine();
+
+ // Print TypeCode field
+ builder.AppendQuotedString("TypeCode")
+ .Append(": ")
+ .Append(extObject.TypeCode)
+ .AppendLine(",");
+
+ // Print Value field
+ builder.AppendQuotedString("Value")
+ .Append(": ");
+
+ FormatByteArrayAsString(extObject.GetBody(), builder, true);
+
+ // Unindent
+ builder.DecreaseIndent()
+ .AppendLine();
+
+ builder.Append('}');
+ }
+ else
+ {
+ builder.Append(literal);
+ }
+ }
+ }
+
+ private static void FormatByteArray(byte[] arr, IndentedStringBuilder builder)
+ {
+ builder.Append("[ ");
+
+ foreach (var b in arr)
+ {
+ builder.Append("0x");
+ builder.Append(ToHexChar(b >> 4));
+ builder.Append(ToHexChar(b & 0xF));
+ builder.Append(", ");
+ }
+
+ // Remove trailing comma
+ builder.Remove(builder.Length - 2, 2);
+
+ builder.Append(" ]");
+ }
+
+ private static void FormatByteArrayAsString(byte[] arr, IndentedStringBuilder builder, bool withPrefix)
+ {
+ builder.Append('"');
+
+ if (withPrefix)
+ {
+ builder.Append("0x");
+ }
+
+ foreach (var b in arr)
+ {
+ builder.Append(ToHexChar(b >> 4));
+ builder.Append(ToHexChar(b & 0xF));
+ }
+
+ builder.Append('"');
+ }
+
+ private static void FormatMsgPackMap(MessagePackObject obj, IndentedStringBuilder builder)
+ {
+ var map = obj.AsDictionary();
+
+ builder.Append('{');
+
+ // Indent
+ builder.IncreaseIndent()
+ .AppendLine();
+
+ foreach (var item in map)
+ {
+ FormatMsgPackObj(item.Key, builder);
+
+ builder.Append(": ");
+
+ FormatMsgPackObj(item.Value, builder);
+
+ builder.AppendLine(",");
+ }
+
+ // Remove the trailing new line and comma
+ builder.TrimLastLine()
+ .Remove(builder.Length - 1, 1);
+
+ // Unindent
+ builder.DecreaseIndent()
+ .AppendLine();
+
+ builder.Append('}');
+ }
+
+ private static void FormatMsgPackArray(MessagePackObject obj, IndentedStringBuilder builder)
+ {
+ var arr = obj.AsList();
+
+ builder.Append("[ ");
+
+ foreach (var item in arr)
+ {
+ FormatMsgPackObj(item, builder);
+
+ builder.Append(", ");
+ }
+
+ // Remove trailing comma
+ builder.Remove(builder.Length - 2, 2);
+
+ builder.Append(" ]");
+ }
+
+ private static char ToHexChar(int b)
+ {
+ if (b < 10)
+ {
+ return unchecked((char)('0' + b));
+ }
+ else
+ {
+ return unchecked((char)('A' + (b - 10)));
+ }
+ }
+
+ internal class IndentedStringBuilder
+ {
+ const string DefaultIndent = " ";
+
+ private int _indentCount = 0;
+ private int _newLineIndex = 0;
+ private StringBuilder _builder;
+
+ public string IndentString { get; set; } = DefaultIndent;
+
+ public IndentedStringBuilder(StringBuilder builder)
+ {
+ _builder = builder;
+ }
+
+ public IndentedStringBuilder()
+ : this(new StringBuilder())
+ { }
+
+ public IndentedStringBuilder(string str)
+ : this(new StringBuilder(str))
+ { }
+
+ public IndentedStringBuilder(int length)
+ : this(new StringBuilder(length))
+ { }
+
+ public int Length { get => _builder.Length; }
+
+ public IndentedStringBuilder IncreaseIndent()
+ {
+ _indentCount++;
+
+ return this;
+ }
+
+ public IndentedStringBuilder DecreaseIndent()
+ {
+ _indentCount--;
+
+ return this;
+ }
+
+ public IndentedStringBuilder Append(char value)
+ {
+ _builder.Append(value);
+
+ return this;
+ }
+
+ public IndentedStringBuilder Append(string value)
+ {
+ _builder.Append(value);
+
+ return this;
+ }
+
+ public IndentedStringBuilder Append(object value)
+ {
+ this.Append(value.ToString());
+
+ return this;
+ }
+
+ public IndentedStringBuilder AppendQuotedString(string value)
+ {
+ _builder.Append('"');
+ _builder.Append(value);
+ _builder.Append('"');
+
+ return this;
+ }
+
+ public IndentedStringBuilder AppendLine()
+ {
+ _newLineIndex = _builder.Length;
+
+ _builder.AppendLine();
+
+ for (int i = 0; i < _indentCount; i++)
+ _builder.Append(IndentString);
+
+ return this;
+ }
+
+ public IndentedStringBuilder AppendLine(string value)
+ {
+ _builder.Append(value);
+
+ this.AppendLine();
+
+ return this;
+ }
+
+ public IndentedStringBuilder TrimLastLine()
+ {
+ _builder.Remove(_newLineIndex, _builder.Length - _newLineIndex);
+
+ return this;
+ }
+
+ public IndentedStringBuilder Remove(int startIndex, int length)
+ {
+ _builder.Remove(startIndex, length);
+
+ return this;
+ }
+
+ public override string ToString()
+ {
+ return _builder.ToString();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/NetworkHelpers.cs b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs
new file mode 100644
index 00000000..b2f6f45d
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs
@@ -0,0 +1,66 @@
+using System.Net.NetworkInformation;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class NetworkHelpers
+ {
+ private static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(NetworkInterface adapter, bool isPreferred)
+ {
+ IPInterfaceProperties properties = adapter.GetIPProperties();
+
+ if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0))
+ {
+ foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
+ {
+ // Only accept an IPv4 address
+ if (info.Address.GetAddressBytes().Length == 4)
+ {
+ return (properties, info);
+ }
+ }
+ }
+
+ return (null, null);
+ }
+
+ public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(string lanInterfaceId = "0")
+ {
+ if (!NetworkInterface.GetIsNetworkAvailable())
+ {
+ return (null, null);
+ }
+
+ IPInterfaceProperties targetProperties = null;
+ UnicastIPAddressInformation targetAddressInfo = null;
+
+ NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
+
+ string guid = lanInterfaceId;
+ bool hasPreference = guid != "0";
+
+ foreach (NetworkInterface adapter in interfaces)
+ {
+ bool isPreferred = adapter.Id == guid;
+
+ // Ignore loopback and non IPv4 capable interface.
+ if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
+ {
+ (IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
+
+ if (properties != null)
+ {
+ targetProperties = properties;
+ targetAddressInfo = info;
+
+ if (isPreferred || !hasPreference)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return (targetProperties, targetAddressInfo);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/SpanHelpers.cs b/src/Ryujinx.Common/Utilities/SpanHelpers.cs
new file mode 100644
index 00000000..4765eab3
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/SpanHelpers.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class SpanHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> CreateSpan<T>(scoped ref T reference, int length)
+ {
+ return MemoryMarshal.CreateSpan(ref reference, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(scoped ref T reference) where T : unmanaged
+ {
+ return CreateSpan(ref reference, 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<TSpan> AsSpan<TStruct, TSpan>(scoped ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateSpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
+ Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<byte> AsByteSpan<T>(scoped ref T reference) where T : unmanaged
+ {
+ return CreateSpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<T> CreateReadOnlySpan<T>(scoped ref T reference, int length)
+ {
+ return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<T> AsReadOnlySpan<T>(scoped ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref reference, 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<TSpan> AsReadOnlySpan<TStruct, TSpan>(scoped ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
+ Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<byte> AsReadOnlyByteSpan<T>(scoped ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs
new file mode 100644
index 00000000..da97188d
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs
@@ -0,0 +1,31 @@
+using Microsoft.IO;
+using Ryujinx.Common.Memory;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class StreamUtils
+ {
+ public static byte[] StreamToBytes(Stream input)
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ {
+ input.CopyTo(stream);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ {
+ await input.CopyToAsync(stream, cancellationToken);
+
+ return stream.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs b/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
new file mode 100644
index 00000000..c0127dc4
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
@@ -0,0 +1,34 @@
+#nullable enable
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.Common.Utilities
+{
+ /// <summary>
+ /// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
+ /// </summary>
+ /// <remarks>
+ /// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
+ /// Get rid of this converter if dotnet supports similar functionality out of the box.
+ /// </remarks>
+ /// <typeparam name="TEnum">Type of enum to serialize</typeparam>
+ public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
+ {
+ public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var enumValue = reader.GetString();
+ if (string.IsNullOrEmpty(enumValue))
+ {
+ return default;
+ }
+
+ return Enum.Parse<TEnum>(enumValue);
+ }
+
+ public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/UInt128Utils.cs b/src/Ryujinx.Common/Utilities/UInt128Utils.cs
new file mode 100644
index 00000000..af8521b4
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/UInt128Utils.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Globalization;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class UInt128Utils
+ {
+ public static UInt128 FromHex(string hex)
+ {
+ return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber));
+ }
+
+ public static UInt128 CreateRandom()
+ {
+ return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.NextInt64());
+ }
+ }
+} \ No newline at end of file