mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-07-28 09:27:10 +02:00
Merge Latest Ryujinx (Unstable)
This commit is contained in:
parent
aaefc0a9e5
commit
12ab8bc3e2
1237 changed files with 48656 additions and 21399 deletions
|
@ -1,13 +1,15 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public static class AppDataManager
|
||||
{
|
||||
public const string DefaultBaseDir = "Ryujinx";
|
||||
public const string DefaultPortableDir = "portable";
|
||||
private const string DefaultBaseDir = "Ryujinx";
|
||||
private const string DefaultPortableDir = "portable";
|
||||
|
||||
// The following 3 are always part of Base Directory
|
||||
private const string GamesDir = "games";
|
||||
|
@ -29,6 +31,8 @@ namespace Ryujinx.Common.Configuration
|
|||
public static string KeysDirPath { get; private set; }
|
||||
public static string KeysDirPathUser { get; }
|
||||
|
||||
public static string LogsDirPath { get; private set; }
|
||||
|
||||
public const string DefaultNandDir = "bis";
|
||||
public const string DefaultSdcardDir = "sdcard";
|
||||
private const string DefaultModsDir = "mods";
|
||||
|
@ -75,6 +79,17 @@ namespace Ryujinx.Common.Configuration
|
|||
|
||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||
|
||||
// On macOS, check for a portable directory next to the app bundle as well.
|
||||
if (OperatingSystem.IsMacOS() && !Directory.Exists(portablePath))
|
||||
{
|
||||
string bundlePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", ".."));
|
||||
// Make sure we're actually running within an app bundle.
|
||||
if (bundlePath.EndsWith(".app"))
|
||||
{
|
||||
portablePath = Path.GetFullPath(Path.Combine(bundlePath, "..", DefaultPortableDir));
|
||||
}
|
||||
}
|
||||
|
||||
if (Directory.Exists(portablePath))
|
||||
{
|
||||
BaseDirPath = portablePath;
|
||||
|
@ -101,65 +116,227 @@ namespace Ryujinx.Common.Configuration
|
|||
|
||||
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)
|
||||
if (IsPathSymlink(BaseDirPath))
|
||||
{
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.Delete(oldConfigPath, true);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended.");
|
||||
}
|
||||
|
||||
SetupBasePaths();
|
||||
}
|
||||
|
||||
public static string GetOrCreateLogsDir()
|
||||
{
|
||||
if (Directory.Exists(LogsDirPath))
|
||||
{
|
||||
return LogsDirPath;
|
||||
}
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory.");
|
||||
LogsDirPath = SetUpLogsDir();
|
||||
|
||||
return LogsDirPath;
|
||||
}
|
||||
|
||||
private static string SetUpLogsDir()
|
||||
{
|
||||
string logDir = "";
|
||||
|
||||
if (Mode == LaunchMode.Portable)
|
||||
{
|
||||
logDir = Path.Combine(BaseDirPath, "Logs");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// NOTE: Should evaluate to "~/Library/Logs/Ryujinx/".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
// NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs".
|
||||
logDir = Path.Combine(BaseDirPath, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from.
|
||||
logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
// NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// NOTE: Should evaluate to "~/.config/Ryujinx/Logs".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logDir;
|
||||
}
|
||||
|
||||
private static void SetupBasePaths()
|
||||
{
|
||||
Directory.CreateDirectory(BaseDirPath);
|
||||
LogsDirPath = SetUpLogsDir();
|
||||
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
|
||||
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
|
||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||
}
|
||||
|
||||
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
||||
// Should be removed, when the existance of the old directory isn't checked anymore.
|
||||
// Should be removed, when the existence of the old directory isn't checked anymore.
|
||||
private static bool IsPathSymlink(string path)
|
||||
{
|
||||
FileAttributes attributes = File.GetAttributes(path);
|
||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
try
|
||||
{
|
||||
FileAttributes attributes = File.GetAttributes(path);
|
||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static void FixMacOSConfigurationFolders()
|
||||
{
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
if (!dir.Exists)
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config", DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
|
||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
string correctApplicationDataDirectoryPath =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (IsPathSymlink(correctApplicationDataDirectoryPath))
|
||||
{
|
||||
if (file.Name == ".DS_Store")
|
||||
//copy the files somewhere temporarily
|
||||
string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir);
|
||||
try
|
||||
{
|
||||
continue;
|
||||
FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error copying Ryujinx application data into the temp folder. {exception}");
|
||||
try
|
||||
{
|
||||
FileSystemInfo resolvedDirectoryInfo =
|
||||
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
|
||||
string resolvedPath = resolvedDirectoryInfo.FullName;
|
||||
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
|
||||
}
|
||||
catch (Exception symlinkException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
||||
}
|
||||
//delete the symlink
|
||||
try
|
||||
{
|
||||
//This will fail if this is an actual directory, so there is no way we can actually delete user data here.
|
||||
File.Delete(correctApplicationDataDirectoryPath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}");
|
||||
try
|
||||
{
|
||||
FileSystemInfo resolvedDirectoryInfo =
|
||||
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
|
||||
string resolvedPath = resolvedDirectoryInfo.FullName;
|
||||
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
|
||||
}
|
||||
catch (Exception symlinkException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo subDir in subDirs)
|
||||
{
|
||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
||||
//put the files back
|
||||
try
|
||||
{
|
||||
FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
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 ShowUI { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
public Key ToggleMute { get; set; }
|
||||
public Key ResScaleUp { get; set; }
|
||||
|
|
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public class Mod
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct ModMetadata
|
||||
{
|
||||
public List<Mod> Mods { get; set; }
|
||||
|
||||
public ModMetadata()
|
||||
{
|
||||
Mods = new List<Mod>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(ModMetadata))]
|
||||
public partial class ModMetadataJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
|
@ -0,0 +1,181 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Extensions
|
||||
{
|
||||
public static class SequenceReaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
|
||||
/// Useful for debugging purposes.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
|
||||
/// <param name="fileFullName">The path and name of the file to create and dump to</param>
|
||||
public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
|
||||
{
|
||||
var initialConsumed = reader.Consumed;
|
||||
|
||||
reader.Rewind(initialConsumed);
|
||||
|
||||
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
|
||||
{
|
||||
while (reader.End == false)
|
||||
{
|
||||
var span = reader.CurrentSpan;
|
||||
fileStream.Write(span);
|
||||
reader.Advance(span.Length);
|
||||
}
|
||||
}
|
||||
|
||||
reader.SetConsumed(initialConsumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
|
||||
/// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to get</typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
|
||||
/// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
|
||||
/// <remarks>
|
||||
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
|
||||
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
|
||||
/// To discourage use, it is recommended to call this method like the following:
|
||||
/// <c>
|
||||
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
|
||||
/// </c>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
|
||||
{
|
||||
int lengthRequired = Unsafe.SizeOf<T>();
|
||||
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
if (lengthRequired <= span.Length)
|
||||
{
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);
|
||||
|
||||
return ref spanOfT[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
|
||||
|
||||
Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
|
||||
|
||||
if (!reader.TryCopyTo(valueBytesSpan))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
return ref valueSpan[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="int"/> as little endian.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">A location to receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
|
||||
{
|
||||
if (!reader.TryReadLittleEndian(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to read</typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">The target that will receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
if (!reader.TryReadUnmanaged(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reader's position as bytes consumed.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
|
||||
/// <param name="consumed">The number of bytes consumed</param>
|
||||
public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
|
||||
{
|
||||
reader.Rewind(reader.Consumed);
|
||||
reader.Advance(consumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
|
||||
/// structs - see remarks for full details.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to read</typeparam>
|
||||
/// <remarks>
|
||||
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
|
||||
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
|
||||
/// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
|
||||
if (span.Length < sizeof(T))
|
||||
{
|
||||
return TryReadUnmanagedMultiSegment(ref reader, out value);
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
|
||||
|
||||
// Not enough data in the current segment, try to peek for the data we need.
|
||||
T buffer = default;
|
||||
|
||||
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
|
||||
|
||||
if (!reader.TryCopyTo(tempSpan))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,33 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver
|
||||
{
|
||||
public static class DriverUtilities
|
||||
{
|
||||
private static void AddMesaFlags(string envVar, string newFlags)
|
||||
{
|
||||
string existingFlags = Environment.GetEnvironmentVariable(envVar);
|
||||
|
||||
string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
|
||||
|
||||
OsUtils.SetEnvironmentVariableNoCaching(envVar, flags);
|
||||
}
|
||||
|
||||
public static void InitDriverConfig(bool oglThreading)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
AddMesaFlags("RADV_DEBUG", "nodcc");
|
||||
}
|
||||
|
||||
ToggleOGLThreading(oglThreading);
|
||||
}
|
||||
|
||||
public static void ToggleOGLThreading(bool enabled)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
|
||||
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
|
||||
OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
|
||||
OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Ryujinx.Common.Logging
|
|||
ServiceVi,
|
||||
SurfaceFlinger,
|
||||
TamperMachine,
|
||||
Ui,
|
||||
UI,
|
||||
Vic,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.Common.SystemInterop;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -22,6 +23,9 @@ namespace Ryujinx.Common.Logging
|
|||
|
||||
public readonly struct Log
|
||||
{
|
||||
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
|
||||
|
||||
internal readonly LogLevel Level;
|
||||
|
||||
internal Log(LogLevel level)
|
||||
|
@ -100,7 +104,12 @@ namespace Ryujinx.Common.Logging
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message)
|
||||
{
|
||||
message = message.Replace(_homeDir, _homeDirRedacted);
|
||||
|
||||
return $"{logClass} {caller}: {message}";
|
||||
}
|
||||
}
|
||||
|
||||
public static Log? Debug { get; private set; }
|
||||
|
|
|
@ -13,31 +13,82 @@ namespace Ryujinx.Common.Logging.Targets
|
|||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public FileLogTarget(string path, string name)
|
||||
: this(path, name, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
public FileLogTarget(string name, FileStream fileStream)
|
||||
{
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(fileStream);
|
||||
_formatter = new DefaultLogFormatter();
|
||||
}
|
||||
|
||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
||||
public static FileStream PrepareLogFile(string path)
|
||||
{
|
||||
// Ensure directory is present
|
||||
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
|
||||
logDir.Create();
|
||||
DirectoryInfo logDir;
|
||||
try
|
||||
{
|
||||
logDir = new DirectoryInfo(path);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logDir.Create();
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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();
|
||||
try
|
||||
{
|
||||
files[i].Delete();
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.GetVersion();
|
||||
string version = ReleaseInformation.Version;
|
||||
|
||||
// Get path for the current time
|
||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_formatter = new DefaultLogFormatter();
|
||||
try
|
||||
{
|
||||
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
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();
|
||||
|
||||
/// <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 static 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 static 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 static 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 static 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 static 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 static 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 static 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);
|
||||
}
|
||||
}
|
||||
}
|
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal file
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/>
|
||||
/// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item to store.</typeparam>
|
||||
public sealed class MemoryOwner<T> : IMemoryOwner<T>
|
||||
{
|
||||
private readonly int _length;
|
||||
private T[]? _array;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
private MemoryOwner(int length)
|
||||
{
|
||||
_length = length;
|
||||
_array = ArrayPool<T>.Shared.Rent(length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MemoryOwner<T> Rent(int length) => new(length);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MemoryOwner<T> RentCleared(int length)
|
||||
{
|
||||
MemoryOwner<T> result = new(length);
|
||||
|
||||
result._array.AsSpan(0, length).Clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy</param>
|
||||
/// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
|
||||
public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer)
|
||||
{
|
||||
MemoryOwner<T> result = new(buffer.Length);
|
||||
|
||||
buffer.CopyTo(result._array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the current instance.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> Memory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = _array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return new(array, 0, _length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses a trick made possible by the .NET 6+ runtime array layout.
|
||||
/// </remarks>
|
||||
public Span<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = _array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
T[]? array = Interlocked.Exchange(ref _array, null);
|
||||
|
||||
if (array is not null)
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal file
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>.
|
||||
/// It does not implement <see cref="IDisposable"/> to avoid being boxed, but should still be disposed. This
|
||||
/// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
|
||||
/// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
|
||||
/// For all these reasons, all usage should be with a `using` block or statement.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item to store.</typeparam>
|
||||
public readonly ref struct SpanOwner<T>
|
||||
{
|
||||
private readonly int _length;
|
||||
private readonly T[] _array;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
private SpanOwner(int length)
|
||||
{
|
||||
_length = length;
|
||||
_array = ArrayPool<T>.Shared.Rent(length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
|
||||
/// </summary>
|
||||
public static SpanOwner<T> Empty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SpanOwner<T> Rent(int length) => new(length);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use</param>
|
||||
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SpanOwner<T> RentCleared(int length)
|
||||
{
|
||||
SpanOwner<T> result = new(length);
|
||||
|
||||
result._array.AsSpan(0, length).Clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy</param>
|
||||
/// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
|
||||
public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer)
|
||||
{
|
||||
SpanOwner<T> result = new(buffer.Length);
|
||||
|
||||
buffer.CopyTo(result._array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the current instance
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses a trick made possible by the .NET 6+ runtime array layout.
|
||||
/// </remarks>
|
||||
public Span<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -744,6 +744,17 @@ namespace Ryujinx.Common.Memory
|
|||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array65<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
public readonly int Length => 65;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array73<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
|
|
|
@ -63,6 +63,18 @@ namespace Ryujinx.Common.Memory
|
|||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray3000 : IArray<byte>
|
||||
{
|
||||
private const int Size = 3000;
|
||||
|
||||
byte _element;
|
||||
|
||||
public readonly 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>
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
|
@ -9,55 +7,25 @@ namespace Ryujinx.Common
|
|||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||
|
||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||
|
||||
public static bool IsValid()
|
||||
{
|
||||
return !BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
public static bool IsValid =>
|
||||
!BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||
!ConfigFileName.StartsWith("%%");
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (OperatingSystem.IsIOS())
|
||||
{
|
||||
return "ios";
|
||||
}
|
||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
|
||||
if (IsValid())
|
||||
{
|
||||
return BuildVersion;
|
||||
}
|
||||
|
||||
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() || OperatingSystem.IsIOS())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
#endif
|
||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -41,6 +42,22 @@ namespace Ryujinx.Common
|
|||
return StreamUtils.StreamToBytes(stream);
|
||||
}
|
||||
|
||||
public static MemoryOwner<byte> ReadFileToRentedMemory(string filename)
|
||||
{
|
||||
var (assembly, path) = ResolveManifestPath(filename);
|
||||
|
||||
return ReadFileToRentedMemory(assembly, path);
|
||||
}
|
||||
|
||||
public static MemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
||||
return stream is null
|
||||
? null
|
||||
: StreamUtils.StreamToRentedMemory(stream);
|
||||
}
|
||||
|
||||
public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
|
56
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
56
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static class FileSystemUtils
|
||||
{
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
file.CopyTo(targetFilePath);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MoveDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
CopyDirectory(sourceDir, destinationDir, true);
|
||||
Directory.Delete(sourceDir, true);
|
||||
}
|
||||
|
||||
public static string SanitizeFileName(string fileName)
|
||||
{
|
||||
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
|
||||
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
|
||||
}
|
||||
}
|
||||
}
|
24
src/Ryujinx.Common/Utilities/OsUtils.cs
Normal file
24
src/Ryujinx.Common/Utilities/OsUtils.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public partial class OsUtils
|
||||
{
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
|
||||
|
||||
public static void SetEnvironmentVariableNoCaching(string key, string value)
|
||||
{
|
||||
// Set the value in the cached environment variables, too.
|
||||
Environment.SetEnvironmentVariable(key, value);
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
int res = setenv(key, value, 1);
|
||||
Debug.Assert(res != -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
@ -9,12 +10,50 @@ namespace Ryujinx.Common.Utilities
|
|||
{
|
||||
public static byte[] StreamToBytes(Stream input)
|
||||
{
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
input.CopyTo(stream);
|
||||
public static MemoryOwner<byte> StreamToRentedMemory(Stream input)
|
||||
{
|
||||
if (input is MemoryStream inputMemoryStream)
|
||||
{
|
||||
return MemoryStreamToRentedMemory(inputMemoryStream);
|
||||
}
|
||||
else if (input.CanSeek)
|
||||
{
|
||||
long bytesExpected = input.Length;
|
||||
|
||||
return stream.ToArray();
|
||||
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)bytesExpected));
|
||||
|
||||
var destSpan = ownedMemory.Span;
|
||||
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < bytesExpected)
|
||||
{
|
||||
int bytesRead = input.Read(destSpan[totalBytesRead..]);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
ownedMemory.Dispose();
|
||||
|
||||
throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}.");
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
return ownedMemory;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner<byte>.
|
||||
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
|
||||
|
||||
return MemoryStreamToRentedMemory(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||
|
@ -25,5 +64,26 @@ namespace Ryujinx.Common.Utilities
|
|||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static MemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)input.Length));
|
||||
|
||||
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
|
||||
_ = input.Read(ownedMemory.Span);
|
||||
|
||||
return ownedMemory;
|
||||
}
|
||||
|
||||
private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input)
|
||||
{
|
||||
RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
input.CopyTo(stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue